From f54c6e5f52b0e4cb6bc83c30661901620f17a185 Mon Sep 17 00:00:00 2001 From: hzchen98 Date: Tue, 12 Sep 2023 16:26:20 +0200 Subject: [PATCH 001/760] Ssmas Bid Adapter : use iframe for user sync (#10465) * change to https * user sync type iframe --- modules/ssmasBidAdapter.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/ssmasBidAdapter.js b/modules/ssmasBidAdapter.js index 3005910789b..0b70a80e757 100644 --- a/modules/ssmasBidAdapter.js +++ b/modules/ssmasBidAdapter.js @@ -113,12 +113,19 @@ export const spec = { params.push(`ccpa_consent=${uspConsent.consentString}`); } - if (syncOptions.pixelEnabled && serverResponses.length > 0) { + if (syncOptions.iframeEnabled && serverResponses.length > 0) { syncs.push({ - type: 'image', - url: `${SYNC_URL}?${params.join('&')}` + type: 'iframe', + url: `${SYNC_URL}/iframe?${params.join('&')}` }); } + + // if (syncOptions.pixelEnabled && serverResponses.length > 0) { + // syncs.push({ + // type: 'image', + // url: `${SYNC_URL}/image?${params.join('&')}` + // }); + // } return syncs; }, }; From 779146a511896f0ae9cfa1ec8aad05a094b46e89 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Tue, 12 Sep 2023 16:35:41 +0200 Subject: [PATCH 002/760] LiveIntent UserId module: fix Ajax timeout when calling the collector endpoint (#10425) * Update LiveConnect * Separate timeout for the collctor * Update package-lock.json * Fix package-lock --- modules/liveIntentIdSystem.js | 7 ++++--- package-lock.json | 18 +++++++++--------- package.json | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 2d9e6b63a35..8fab266ecce 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -12,6 +12,7 @@ import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +const DEFAULT_AJAX_TIMEOUT = 5000 const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; const LI_PROVIDER_DOMAIN = 'liveintent.com'; @@ -65,6 +66,7 @@ function parseLiveIntentCollectorConfig(collectConfig) { collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); collectConfig.collectorUrl && (config.collectorUrl = collectConfig.collectorUrl); + config.ajaxTimeout = collectConfig.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; return config; } @@ -99,9 +101,8 @@ function initializeLiveConnect(configParams) { if (configParams.url) { identityResolutionConfig.url = configParams.url } - if (configParams.ajaxTimeout) { - identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout; - } + + identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); diff --git a/package-lock.json b/package-lock.json index dbfc057de9f..b0de52e2d6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.0.0" + "live-connect-js": "^6.0.1" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16260,12 +16260,12 @@ } }, "node_modules/live-connect-js": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.0.tgz", - "integrity": "sha512-4iMSeDPpueYoGEK4U152yKg+hj7jTxkzyfJUAGxtVHxdgjsjh8QmP3kLlTLBBrR/g/L/WCX47PBQxtGW9z8Rlw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.1.tgz", + "integrity": "sha512-+TwM7cjgyutqaMNlTQKNY9nJFDPpSWfoazSHmlWxOPlimp10PSZGABIbtulNGGpYbR/Zxgc+C/uW5OxqcNEPXg==", "dependencies": { "live-connect-common": "^3.0.0", - "live-connect-handlers": "^2.0.0", + "live-connect-handlers": "^2.1.0", "tiny-hashes": "1.0.1" }, "engines": { @@ -37923,12 +37923,12 @@ } }, "live-connect-js": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.0.tgz", - "integrity": "sha512-4iMSeDPpueYoGEK4U152yKg+hj7jTxkzyfJUAGxtVHxdgjsjh8QmP3kLlTLBBrR/g/L/WCX47PBQxtGW9z8Rlw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.1.tgz", + "integrity": "sha512-+TwM7cjgyutqaMNlTQKNY9nJFDPpSWfoazSHmlWxOPlimp10PSZGABIbtulNGGpYbR/Zxgc+C/uW5OxqcNEPXg==", "requires": { "live-connect-common": "^3.0.0", - "live-connect-handlers": "^2.0.0", + "live-connect-handlers": "^2.1.0", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index a95acbb8cfe..577f10f4c17 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.0.0" + "live-connect-js": "^6.0.1" }, "optionalDependencies": { "fsevents": "^2.3.2" From 4f8cf681f28f22584868cbc00be46e07e97d1d19 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 12 Sep 2023 17:19:06 +0200 Subject: [PATCH 003/760] Grid Bid Adapter: parse timeout and bidfloor to integer and float values (#10461) Co-authored-by: v.raybaud --- modules/gridBidAdapter.js | 10 +++++----- test/spec/modules/gridBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 2e44ac46f91..aa00a84273c 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -91,7 +91,7 @@ export const spec = { let {bidderRequestId, gdprConsent, uspConsent, timeout, refererInfo, gppConsent} = bidderRequest || {}; const referer = refererInfo ? encodeURIComponent(refererInfo.page) : ''; - const tmax = timeout; + const tmax = parseInt(timeout) || null; const imp = []; const bidsMap = {}; const requests = []; @@ -133,7 +133,7 @@ export const spec = { }; if (ortb2Imp) { if (ortb2Imp.instl) { - impObj.instl = ortb2Imp.instl; + impObj.instl = parseInt(ortb2Imp.instl) || null; } if (ortb2Imp.ext) { @@ -485,7 +485,7 @@ export const spec = { */ function _getFloor (mediaTypes, bid) { const curMediaType = mediaTypes.video ? 'video' : 'banner'; - let floor = bid.params.bidFloor || bid.params.floorcpm || 0; + let floor = parseFloat(bid.params.bidFloor || bid.params.floorcpm || 0) || null; if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ @@ -595,8 +595,8 @@ function createVideoRequest(videoParams, mediaType, bidSizes) { if (!videoData.w || !videoData.h) return; - const minDur = mind || durationRangeSec[0] || videoData.minduration; - const maxDur = maxd || durationRangeSec[1] || videoData.maxduration; + const minDur = mind || durationRangeSec[0] || parseInt(videoData.minduration) || null; + const maxDur = maxd || durationRangeSec[1] || parseInt(videoData.maxduration) || null; if (minDur) { videoData.minduration = minDur; diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index da51ed058be..b12083236a2 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -941,6 +941,15 @@ describe('TheMediaGrid Adapter', function () { getDataFromLocalStorageStub.restore(); }) + it('tmax should be set as integer', function() { + let [request] = spec.buildRequests([bidRequests[0]], {...bidderRequest, timeout: '10'}); + let payload = parseRequest(request.data); + expect(payload.tmax).to.equal(10); + [request] = spec.buildRequests([bidRequests[0]], {...bidderRequest, timeout: 'ddqwdwdq'}); + payload = parseRequest(request.data); + expect(payload.tmax).to.equal(null); + }) + describe('floorModule', function () { const floorTestData = { 'currency': 'USD', @@ -975,6 +984,15 @@ describe('TheMediaGrid Adapter', function () { const payload = parseRequest(request.data); expect(payload.imp[0].bidfloor).to.equal(bidfloor); }); + it('should return the bidfloor string value if it is greater than getFloor.floor', function () { + const bidfloor = '1.80'; + const bidRequestsWithFloor = { ...bidRequest }; + bidRequestsWithFloor.params = Object.assign({bidFloor: bidfloor}, bidRequestsWithFloor.params); + const [request] = spec.buildRequests([bidRequestsWithFloor], bidderRequest); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload.imp[0].bidfloor).to.equal(1.80); + }); }); }); From b8a39f4576fe36440e3832b8168cc326301bf823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Leclerc?= Date: Tue, 12 Sep 2023 17:26:59 +0200 Subject: [PATCH 004/760] teads Bid Adapter: get OpenGraph title (#10439) * teadsBidAdapter: Also get OpenGraph title * Improv + add tests * Add tests over title + description payload length * Add tests for catch case when cross-origin --- modules/teadsBidAdapter.js | 12 ++- test/spec/modules/teadsBidAdapter_spec.js | 122 ++++++++++++++++++---- 2 files changed, 106 insertions(+), 28 deletions(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 570ee51df9c..6107d1a6e66 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -174,9 +174,13 @@ function getReferrerInfo(bidderRequest) { function getPageTitle() { try { - return window.top.document.title || document.title; + const ogTitle = window.top.document.querySelector('meta[property="og:title"]') + + return window.top.document.title || (ogTitle && ogTitle.content) || ''; } catch (e) { - return document.title; + const ogTitle = document.querySelector('meta[property="og:title"]') + + return document.title || (ogTitle && ogTitle.content) || ''; } } @@ -185,9 +189,7 @@ function getPageDescription() { try { element = window.top.document.querySelector('meta[name="description"]') || - window.top.document.querySelector('meta[property="og:description"]') || - document.querySelector('meta[name="description"]') || - document.querySelector('meta[property="og:description"]') + window.top.document.querySelector('meta[property="og:description"]') } catch (e) { element = document.querySelector('meta[name="description"]') || document.querySelector('meta[property="og:description"]') diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 9bd8c44b88a..b0d5f436e0b 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1,23 +1,20 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/teadsBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; -import {getStorageManager} from 'src/storageManager'; const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; describe('teadsBidAdapter', () => { const adapter = newBidder(spec); - let cookiesAreEnabledStub, getCookieStub; + let sandbox; beforeEach(function () { - cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); - getCookieStub = sinon.stub(storage, 'getCookie'); + sandbox = sinon.sandbox.create(); }); afterEach(function () { - cookiesAreEnabledStub.restore(); - getCookieStub.restore(); + sandbox.restore(); }); describe('inherited functions', () => { @@ -257,20 +254,99 @@ describe('teadsBidAdapter', () => { expect(payload.pageReferrer).to.deep.equal(document.referrer); }); - it('should add pageTitle info to payload', function () { - const request = spec.buildRequests(bidRequests, bidderRequestDefault); - const payload = JSON.parse(request.data); + describe('pageTitle', function () { + it('should add pageTitle info to payload based on document title', function () { + const testText = 'This is a title'; + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload based on open-graph title', function () { + const testText = 'This is a title from open-graph'; + sandbox.stub(window.top.document, 'title').value(''); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); + + it('should add pageTitle info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'title').value(testText); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.have.length(300); + }); + + it('should add pageTitle info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback title'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'title').value(testText); - expect(payload.pageTitle).to.exist; - expect(payload.pageTitle).to.deep.equal(window.top.document.title || document.title); + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageTitle).to.exist; + expect(payload.pageTitle).to.deep.equal(testText); + }); }); - it('should add pageDescription info to payload', function () { - const request = spec.buildRequests(bidRequests, bidderRequestDefault); - const payload = JSON.parse(request.data); + describe('pageDescription', function () { + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload based on open-graph description', function () { + const testText = 'This is a description from open-graph'; + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[property="og:description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); + + it('should add pageDescription info to payload sliced on 300 first characters', function () { + const testText = Array(500).join('a'); + sandbox.stub(window.top.document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); + + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.have.length(300); + }); + + it('should add pageDescription info to payload when fallbacking from window.top', function () { + const testText = 'This is a fallback description'; + sandbox.stub(window.top.document, 'querySelector').throws(); + sandbox.stub(document, 'querySelector').withArgs('meta[name="description"]').returns({ content: testText }); - expect(payload.pageDescription).to.exist; - expect(payload.pageDescription).to.deep.equal(''); + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.pageDescription).to.exist; + expect(payload.pageDescription).to.deep.equal(testText); + }); }); it('should add timeToFirstByte info to payload', function () { @@ -697,7 +773,7 @@ describe('teadsBidAdapter', () => { describe('First-party cookie Teads ID', function () { it('should not add firstPartyCookieTeadsId param to payload if cookies are not enabled' + ' and teads user id not available', function () { - cookiesAreEnabledStub.returns(false); + sandbox.stub(storage, 'cookiesAreEnabled').returns(false); const bidRequest = { ...baseBidRequest, @@ -714,8 +790,8 @@ describe('teadsBidAdapter', () => { it('should not add firstPartyCookieTeadsId param to payload if cookies are enabled ' + 'but first-party cookie and teads user id are not available', function () { - cookiesAreEnabledStub.returns(true); - getCookieStub.withArgs('_tfpvi').returns(undefined); + sandbox.stub(storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(storage, 'getCookie').withArgs('_tfpvi').returns(undefined); const bidRequest = { ...baseBidRequest, @@ -732,8 +808,8 @@ describe('teadsBidAdapter', () => { it('should add firstPartyCookieTeadsId from cookie if it\'s available ' + 'and teads user id is not', function () { - cookiesAreEnabledStub.returns(true); - getCookieStub.withArgs('_tfpvi').returns('my-teads-id'); + sandbox.stub(storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(storage, 'getCookie').withArgs('_tfpvi').returns('my-teads-id'); const bidRequest = { ...baseBidRequest, @@ -751,8 +827,8 @@ describe('teadsBidAdapter', () => { it('should add firstPartyCookieTeadsId from user id module if it\'s available ' + 'even if cookie is available too', function () { - cookiesAreEnabledStub.returns(true); - getCookieStub.withArgs('_tfpvi').returns('my-teads-id'); + sandbox.stub(storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(storage, 'getCookie').withArgs('_tfpvi').returns('my-teads-id'); const bidRequest = { ...baseBidRequest, From 4c2902f36759476482e4a0f9ab482e569b0efb7a Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 12 Sep 2023 17:33:59 +0200 Subject: [PATCH 005/760] Adagio Analytics Adapter: new endpoint and new code to track auctions (#10426) * adagio analytics adapter - track auctions * update cpm definitions and values * adagioAnalyticsAdapter: add GVLID * adagioAnalyticsAdapter: update .md file * adagioAnalyticsAdapter: add new params * adagioAnalyticsAdapter: fix tests --------- Co-authored-by: Francois ROTTA --- modules/adagioAnalyticsAdapter.js | 328 ++++++++++++++++- modules/adagioAnalyticsAdapter.md | 10 +- .../modules/adagioAnalyticsAdapter_spec.js | 343 +++++++++++++++++- 3 files changed, 660 insertions(+), 21 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 96f3f089cbd..f9b79639073 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -5,16 +5,35 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { getWindowTop } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getWindowTop, getWindowSelf, deepAccess, logInfo, logError } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]); -const VERSION = '2.0.0'; +const ADAGIO_GVLID = 617; +const VERSION = '3.0.0'; +const PREBID_VERSION = '$prebid.version$'; +const ENDPOINT = 'https://c.4dex.io/pba.gif'; +const cache = { + auctions: {}, + getAuction: function(auctionId, adUnitCode) { + return this.auctions[auctionId][adUnitCode]; + }, + updateAuction: function(auctionId, adUnitCode, values) { + this.auctions[auctionId][adUnitCode] = { + ...this.auctions[auctionId][adUnitCode], + ...values + }; + } +}; +const enc = window.encodeURIComponent; -const adagioEnqueue = function adagioEnqueue(action, data) { - getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() }); -} +/** +/* BEGIN ADAGIO.JS CODE +*/ function canAccessTopWindow() { try { @@ -24,12 +43,293 @@ function canAccessTopWindow() { } catch (error) { return false; } -} +}; + +function getCurrentWindow() { + return currentWindow; +}; + +let currentWindow; + +const adagioEnqueue = function adagioEnqueue(action, data) { + getCurrentWindow().ADAGIO.queue.push({ action, data, ts: Date.now() }); +}; + +/** +* END ADAGIO.JS CODE +*/ + +/** +* UTILS FUNCTIONS +*/ + +const guard = { + adagio: (value) => isAdagio(value), + bidTracked: (auctionId, adUnitCode) => deepAccess(cache, `auctions.${auctionId}.${adUnitCode}`, false) +}; + +function removeDuplicates(arr, getKey) { + const seen = {}; + return arr.filter(item => { + const key = getKey(item); + return seen.hasOwnProperty(key) ? false : (seen[key] = true); + }); +}; + +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +}; + +function isAdagio(value) { + return value.toLowerCase().includes('adagio') || + getAdapterNameForAlias(value).toLowerCase().includes('adagio'); +}; + +function getMediaTypeAlias(mediaType) { + const mediaTypesMap = { + banner: 'ban', + outstream: 'vidout', + instream: 'vidin', + adpod: 'vidadpod', + native: 'nat' + }; + return mediaTypesMap[mediaType] || mediaType; +}; + +/** +* sendRequest to Adagio. It filter null values and encode each query param. +* @param {Object} qp +*/ +function sendRequest(qp) { + // Removing null values + qp = Object.keys(qp).reduce((acc, key) => { + if (qp[key] !== null) { + acc[key] = qp[key]; + } + return acc; + }, {}); + + const url = `${ENDPOINT}?${Object.keys(qp).map(key => `${key}=${enc(qp[key])}`).join('&')}`; + ajax(url, null, null, {method: 'GET'}); +}; + +/** + * Send a new beacon to Adagio. It increment the version of the beacon. + * @param {string} auctionId + * @param {string} adUnitCode + */ +function sendNewBeacon(auctionId, adUnitCode) { + cache.updateAuction(auctionId, adUnitCode, { + v: (cache.getAuction(auctionId, adUnitCode).v || 0) + 1 + }); + sendRequest(cache.getAuction(auctionId, adUnitCode)); +}; + +/** + * END UTILS FUNCTIONS +*/ + +/** + * HANDLERS + * - handlerAuctionInit + * - handlerBidResponse + * - handlerBidWon + * - handlerAdRender + * + * Each handler is called when the event is fired. +*/ + +function handlerAuctionInit(event) { + const w = getCurrentWindow(); + + const prebidAuctionId = event.auctionId; + const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode); + + // Check if Adagio is on the bid requests. + // If not, we don't need to track the auction. + const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode)); + if (!adagioBidRequest) { + logInfo(`Adagio is not on the bid requests for auction '${prebidAuctionId}'`) + return; + } + + cache.auctions[prebidAuctionId] = {}; + + adUnitCodes.forEach(adUnitCode => { + const adUnits = event.adUnits.filter(adUnit => adUnit.code === adUnitCode); + + // Get all bidders configures for the ad unit. + const bidders = removeDuplicates( + adUnits.map(adUnit => adUnit.bids.map(bid => ({bidder: bid.bidder, params: bid.params}))).flat(), + bidder => bidder.bidder + ); + + // Check if Adagio is configured for the ad unit. + // If not, we don't need to track the ad unit. + const adagioBidder = bidders.find(bidder => isAdagio(bidder.bidder)); + if (!adagioBidder) { + logInfo(`Adagio is not configured for ad unit '${adUnitCode}'`); + return; + } + + // Get all media types and banner sizes configured for the ad unit. + const mediaTypes = adUnits.map(adUnit => adUnit.mediaTypes); + const mediaTypesKeys = removeDuplicates( + mediaTypes.map(mediaTypeObj => Object.keys(mediaTypeObj)).flat(), + mediaTypeKey => mediaTypeKey + ).map(mediaType => getMediaTypeAlias(mediaType)).sort(); + const bannerSizes = removeDuplicates( + mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER)) + .map(mediaType => mediaType[BANNER].sizes.map(size => size.join('x'))) + .flat(), + bannerSize => bannerSize + ).sort(); + + // Get all Adagio bids for the ad unit from the bidRequest. + // If no bids, we don't need to track the ad unit. + const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode); + if (deepAccess(adagioAdUnitBids, 'length', 0) <= 0) { + logInfo(`Adagio is not on the bid requests for ad unit '${adUnitCode}' and auction '${prebidAuctionId}'`) + return; + } + // Get Adagio params from the first bid. + // We assume that all Adagio bids for a same adunit have the same params. + const params = adagioAdUnitBids[0].params; + + // Get all media types requested for Adagio. + const adagioMediaTypes = removeDuplicates( + adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), + mediaTypeKey => mediaTypeKey + ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + + const qp = { + v: 0, + pbjsv: PREBID_VERSION, + org_id: params.organizationId, + site: params.site, + pv_id: params.pageviewId, + auct_id: params.adagioAuctionId, + adu_code: adUnitCode, + url_dmn: w.location.hostname, + dvc: params.environment, + pgtyp: params.pagetype, + plcmt: params.placement, + tname: params.testName || null, + tvname: params.testVariationName || null, + mts: mediaTypesKeys.join(','), + ban_szs: bannerSizes.join(','), + bdrs: bidders.map(bidder => getAdapterNameForAlias(bidder.bidder)).sort().join(','), + adg_mts: adagioMediaTypes.join(',') + }; + + cache.auctions[prebidAuctionId][adUnitCode] = qp; + sendNewBeacon(prebidAuctionId, adUnitCode); + }); +}; + +/** + * handlerBidResponse allow to track the adagio bid response + * and to update the auction cache with the seat ID. + * No beacon is sent here. +*/ +function handlerBidResponse(event) { + if (!guard.adagio(event.bidder)) { + return; + } + + if (!guard.bidTracked(event.auctionId, event.adUnitCode)) { + return; + } + + cache.updateAuction(event.auctionId, event.adUnitCode, { + adg_sid: event.seatId || null + }); +}; + +function handlerBidWon(event) { + if (!guard.bidTracked(event.auctionId, event.adUnitCode)) { + return; + } + + let adsCurRateToUSD = (event.currency === 'USD') ? 1 : null; + let ogCurRateToUSD = (event.originalCurrency === 'USD') ? 1 : null; + try { + if (typeof getGlobal().convertCurrency === 'function') { + // Currency module is loaded, we can calculate the conversion rate. + + // Get the conversion rate from the original currency to USD. + ogCurRateToUSD = getGlobal().convertCurrency(1, event.originalCurrency, 'USD'); + // Get the conversion rate from the ad server currency to USD. + adsCurRateToUSD = getGlobal().convertCurrency(1, event.currency, 'USD'); + } + } catch (error) { + logError('Error on Adagio Analytics Adapter - handlerBidWon', error); + } + + cache.updateAuction(event.auctionId, event.adUnitCode, { + win_bdr: getAdapterNameForAlias(event.bidder), + win_mt: getMediaTypeAlias(event.mediaType), + win_ban_sz: event.mediaType === BANNER ? `${event.width}x${event.height}` : null, + + // ad server currency + win_cpm: event.cpm, + cur: event.currency, + cur_rate: adsCurRateToUSD, + + // original currency from bidder + og_cpm: event.originalCpm, + og_cur: event.originalCurrency, + og_cur_rate: ogCurRateToUSD, + }); + sendNewBeacon(event.auctionId, event.adUnitCode); +}; + +function handlerAdRender(event, isSuccess) { + const { auctionId, adUnitCode } = event.bid; + if (!guard.bidTracked(auctionId, adUnitCode)) { + return; + } + + cache.updateAuction(auctionId, adUnitCode, { + rndr: isSuccess ? 1 : 0 + }); + sendNewBeacon(auctionId, adUnitCode); +}; + +/** + * END HANDLERS +*/ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { - track: function({ eventType, args }) { - if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { - adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + track: function(event) { + const { eventType, args } = event; + + try { + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: + handlerAuctionInit(args); + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + handlerBidResponse(args); + break; + case CONSTANTS.EVENTS.BID_WON: + handlerBidWon(args); + break; + case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + case CONSTANTS.EVENTS.AD_RENDER_FAILED: + handlerAdRender(args, eventType === CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED); + break; + } + } catch (error) { + logError('Error on Adagio Analytics Adapter', error); + } + + try { + if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { + adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + } + } catch (error) { + logError('Error on Adagio Analytics Adapter - adagio.js', error); } } }); @@ -37,11 +337,8 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; adagioAdapter.enableAnalytics = config => { - if (!canAccessTopWindow()) { - return; - } - - const w = getWindowTop(); + const w = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf(); + currentWindow = w; w.ADAGIO = w.ADAGIO || {}; w.ADAGIO.queue = w.ADAGIO.queue || []; @@ -53,7 +350,8 @@ adagioAdapter.enableAnalytics = config => { adapterManager.registerAnalyticsAdapter({ adapter: adagioAdapter, - code: 'adagio' + code: 'adagio', + gvlid: ADAGIO_GVLID, }); export default adagioAdapter; diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md index 312a26ea8da..9fc2cb0bb88 100644 --- a/modules/adagioAnalyticsAdapter.md +++ b/modules/adagioAnalyticsAdapter.md @@ -8,10 +8,10 @@ Maintainer: dev@adagio.io Analytics adapter for Adagio -# Test Parameters +# Settings -``` -{ - provider: 'adagio' -} +```js + pbjs.enableAnalytics({ + provider: 'adagio', + }); ``` diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 581f3cb1b87..39fb5d2d068 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -1,12 +1,14 @@ import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter.js'; import { expect } from 'chai'; import * as utils from 'src/utils.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); let constants = require('src/constants.json'); -describe('adagio analytics adapter', () => { +describe('adagio analytics adapter - adagio.js', () => { let sandbox; let adagioQueuePushSpy; @@ -174,3 +176,342 @@ describe('adagio analytics adapter', () => { }); }); }); + +const AUCTION_ID = '25c6d7f5-699a-4bfc-87c9-996f915341fa'; + +const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { + bidder: 'adagio', + auctionId: AUCTION_ID, + adUnitCode: '/19968336/header-bid-tag-1', + bidId: '3bd4ebb1c900e2', + partnerImpId: 'partnerImpressionID-2', + adId: 'fake_ad_id_2', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.42, + currency: 'USD', + originalCpm: 1.42, + originalCurrency: 'USD', + dealId: 'the-deal-id', + dealChannel: 'PMP', + mi: 'matched-impression', + seatBidId: 'aaaa-bbbb-cccc-dddd', + adserverTargeting: { + 'hb_bidder': 'another', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + }, + meta: { + advertiserDomains: ['example.com'] + }, + seatId: '42', +}); + +const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { + bidder: 'another', + auctionId: AUCTION_ID, + adUnitCode: '/19968336/header-bid-tag-1', + bidId: '3bd4ebb1c900e2', + partnerImpId: 'partnerImpressionID-2', + adId: 'fake_ad_id_2', + requestId: '3bd4ebb1c900e2', + width: 728, + height: 90, + mediaType: 'banner', + cpm: 1.71, + currency: 'EUR', + originalCpm: 1.62, + originalCurrency: 'GBP', + dealId: 'the-deal-id', + dealChannel: 'PMP', + mi: 'matched-impression', + seatBidId: 'aaaa-bbbb-cccc-dddd', + adserverTargeting: { + 'hb_bidder': 'another', + 'hb_adid': '3bd4ebb1c900e2', + 'hb_pb': '1.500', + 'hb_size': '728x90', + 'hb_source': 'server' + }, + meta: { + advertiserDomains: ['example.com'] + } +}); + +const PARAMS_ADG = { + organizationId: '1001', + site: 'test-com', + pageviewId: 'a68e6d70-213b-496c-be0a-c468ff387106', + environment: 'desktop', + pagetype: 'article', + placement: 'pave_top' +}; + +const MOCK = { + SET_TARGETING: { + [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, + [BID_ANOTHER.adUnitCode]: BID_ANOTHER.adserverTargeting + }, + AUCTION_INIT: { + 'auctionId': AUCTION_ID, + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 100 + ] + ] + } + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + }, { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG + }, + }, ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + }, { + 'code': '/19968336/footer-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ] + ] + } + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } ], + 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'another', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + }, { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/footer-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + }, { + 'bidderCode': 'adagio', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG, + adagioAuctionId: '6fc53663-bde5-427b-ab63-baa9ed296f47' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 + }, + BID_RESPONSE: { + adagio: BID_ADAGIO, + another: BID_ANOTHER + }, + BID_WON: { + adagio: Object.assign({}, BID_ADAGIO, { + 'status': 'rendered' + }), + another: Object.assign({}, BID_ANOTHER, { + 'status': 'rendered' + }) + }, + AD_RENDER_SUCCEEDED: { + ad: '
ad
', + adId: 'fake_ad_id_2', + bid: BID_ANOTHER + }, +}; + +describe('adagio analytics adapter', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + + sandbox.stub(events, 'getEvents').returns([]); + + adapterManager.registerAnalyticsAdapter({ + code: 'adagio', + adapter: adagioAnalyticsAdapter + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('track', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'adagio' + }); + }); + + afterEach(() => { + adagioAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', () => { + getGlobal().convertCurrency = (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + }; + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); + events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED); + + expect(server.requests.length).to.equal(3); + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.dvc).to.equal('desktop'); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal('pave_top'); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x100,640x480'); + expect(search.bdrs).to.equal('adagio,another'); + expect(search.adg_mts).to.equal('ban'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.adg_sid).to.equal('42'); + expect(search.win_bdr).to.equal('another'); + expect(search.win_mt).to.equal('ban'); + expect(search.win_ban_sz).to.equal('728x90'); + expect(search.win_cpm).to.equal('1.71'); + expect(search.cur).to.equal('EUR'); + expect(search.cur_rate).to.equal('1.2'); + expect(search.og_cpm).to.equal('1.62'); + expect(search.og_cur).to.equal('GBP'); + expect(search.og_cur_rate).to.equal('1.6'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('3'); + expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.rndr).to.equal('1'); + } + }); + }); +}); From 550a7cb040f44bb6718d35aecb497aee62856017 Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:44:52 +0200 Subject: [PATCH 006/760] [sspbc-adapter] add support for topicsFPD module (#10416) --- modules/sspBCBidAdapter.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 70db18c61e1..2b39faa02d8 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.9'; +const BIDDER_VERSION = '5.91'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -199,6 +199,22 @@ const applyClientHints = ortbRequest => { ortbRequest.user = { ...ortbRequest.user, ...ch }; }; +const applyTopics = (validBidRequest, ortbRequest) => { + const userData = validBidRequest.ortb2?.user?.data || []; + const topicsData = userData.filter(dataObj => { + const segtax = dataObj.ext?.segtax; + return segtax >= 600 && segtax <= 609; + })[0]; + + // format topics obj for exchange + if (topicsData) { + topicsData.id = `${topicsData.ext.segtax}`; + topicsData.name = 'topics'; + delete (topicsData.ext); + ortbRequest.user.data.push(topicsData); + } +}; + const applyUserIds = (validBidRequest, ortbRequest) => { const eids = validBidRequest.userIdAsEids if (eids && eids.length) { @@ -682,6 +698,7 @@ const spec = { applyGdpr(bidderRequest, payload); applyClientHints(payload); applyUserIds(validBidRequests[0], payload); + applyTopics(bidderRequest, payload); return { method: 'POST', From 51a2b9c16ee4af040cab0a024f4a3e101a264f77 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 13 Sep 2023 04:45:58 -0700 Subject: [PATCH 007/760] Yieldmo - Dont require params.video (#10467) --- modules/yieldmoBidAdapter.js | 4 ++-- test/spec/modules/yieldmoBidAdapter_spec.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 0e2c7b4bf59..d2e97f5178e 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -432,12 +432,12 @@ function openRtbImpression(bidRequest) { } }; - const mediaTypesParams = deepAccess(bidRequest, 'mediaTypes.video'); + const mediaTypesParams = deepAccess(bidRequest, 'mediaTypes.video', {}); Object.keys(mediaTypesParams) .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = mediaTypesParams[param]); - const videoParams = deepAccess(bidRequest, 'params.video'); + const videoParams = deepAccess(bidRequest, 'params.video', {}); Object.keys(videoParams) .filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param)) .forEach(param => imp.video[param] = videoParams[param]); diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 25fe176553d..229dc05e2fa 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -425,6 +425,18 @@ describe('YieldmoAdapter', function () { expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT); }); + it('should not require params.video if required props in mediaTypes.video', function () { + videoBid.mediaTypes.video = { + ...videoBid.mediaTypes.video, + ...videoBid.params.video + }; + delete videoBid.params.video; + const requests = build([videoBid]); + expect(requests.length).to.equal(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT); + }); + it('should add mediaTypes.video prop to the imp.video prop', function () { utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 40; expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40); From 66a8b6aa9369973cbcc14f3f4285114739c81257 Mon Sep 17 00:00:00 2001 From: Idan Botbol Date: Wed, 13 Sep 2023 14:55:37 +0300 Subject: [PATCH 008/760] Undertone Bid Adapter: adding gpid support to bid requests (#10414) * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * fix lint issue in undertone adapter spec * added user sync function to undertone adapter * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * added user sync function to undertone adapter * added user sync function to undertone adapter * revert package-lock.json * added user sync function to undertone adapter * Update undertoneBidAdapter.js * Update browsers.json * Undertone: added GPP support and video plcmt * Fix lint issues * Undertone: added support for GPID * identation fix --------- Co-authored-by: omerko Co-authored-by: Omer Koren Co-authored-by: AnnaPerion Co-authored-by: Oran Hollaender Co-authored-by: tamirnPerion <44399211+tamirnPerion@users.noreply.github.com> Co-authored-by: tamarm Co-authored-by: tamarm <40788385+tamarm-perion@users.noreply.github.com> Co-authored-by: Keren Gattegno --- modules/undertoneBidAdapter.js | 1 + test/spec/modules/undertoneBidAdapter_spec.js | 38 ++++++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index 9dcbefd374b..c7e8102ffc9 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -139,6 +139,7 @@ export const spec = { domain: domain, placementId: bidReq.params.placementId != undefined ? bidReq.params.placementId : null, publisherId: bidReq.params.publisherId, + gpid: deepAccess(bidReq, 'ortb2Imp.ext.gpid', deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot', '')), sizes: bidReq.sizes, params: bidReq.params }; diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index 7f9c2e7b3d5..5cf53c661a9 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -39,12 +39,19 @@ const videoBidReq = [{ maxDuration: 30 } }, - mediaTypes: {video: { - context: 'outstream', - playerSize: [640, 480], - placement: 1, - plcmt: 1 - }}, + ortb2Imp: { + ext: { + gpid: '/1111/gpid#728x90', + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480], + placement: 1, + plcmt: 1 + } + }, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' @@ -56,10 +63,19 @@ const videoBidReq = [{ placementId: '10433395', publisherId: 12345 }, - mediaTypes: {video: { - context: 'outstream', - playerSize: [640, 480] - }}, + ortb2Imp: { + ext: { + data: { + pbadslot: '/1111/pbadslot#728x90' + } + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + }, sizes: [[300, 250], [300, 600]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746' @@ -463,12 +479,14 @@ describe('Undertone Adapter', () => { expect(bidVideo.video.skippable).to.equal(true); expect(bidVideo.video.placement).to.equal(1); expect(bidVideo.video.plcmt).to.equal(1); + expect(bidVideo.gpid).to.equal('/1111/gpid#728x90'); expect(bidVideo2.video.skippable).to.equal(null); expect(bidVideo2.video.maxDuration).to.equal(null); expect(bidVideo2.video.playbackMethod).to.equal(null); expect(bidVideo2.video.placement).to.equal(null); expect(bidVideo2.video.plcmt).to.equal(null); + expect(bidVideo2.gpid).to.equal('/1111/pbadslot#728x90'); }); it('should send all userIds data to server', function () { const request = spec.buildRequests(bidReqUserIds, bidderReq); From eb683f47a8cff1d51d7e859ad0fedcd0c223ae33 Mon Sep 17 00:00:00 2001 From: PrecisoSRL <134591565+PrecisoSRL@users.noreply.github.com> Date: Wed, 13 Sep 2023 17:35:34 +0530 Subject: [PATCH 009/760] Preciso Bid Adapter : modified the request parameters (#10436) * New bid adapter : Preciso * Added deafualt statement in interpretNativeAd * removed trailing space * Added Protected Audience API (FLEDGE) support * updated with getConfig method f pr pulling ortb2 data * updated the precisoBidAdapter * updated the test cases * changed user sync url and also fixed the CORS error * removed test params from hello_world.html and 204 error fix * changed responses fields in the precisoBidAdapter.js * error fix * removed test params * reverted the test params * modified the request * removed the empty line * removed blank line in precisoBidAdapter_spec.js --------- Co-authored-by: Nikhil Gopal Chennissery --- modules/precisoBidAdapter.js | 80 +++++++++++++-------- test/spec/modules/precisoBidAdapter_spec.js | 34 +++++---- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index b62d8b0c6b4..c7f7db56fd4 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,5 +1,5 @@ -import { logMessage } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { logMessage, isFn, deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -33,16 +33,38 @@ export const spec = { location = winTop.location; logMessage(e); }; - let placements = []; - let imp = []; + let site = { 'domain': location.domain || '', 'page': location || '' } let request = { - 'id': '123456', - 'imp': imp, + id: '123456678', + imp: validBidRequests.map(request => { + const { bidId, sizes, mediaType } = request + const item = { + id: bidId, + region: request.params.region, + traffic: mediaType, + bidFloor: getBidFloor(request) + } + + if (request.mediaTypes.banner) { + item.banner = { + format: (request.mediaTypes.banner.sizes || sizes).map(size => { + return { w: size[0], h: size[1] } + }), + } + } + + if (request.schain) { + item.schain = request.schain; + } + + return item + }), + 'site': site, 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, @@ -50,9 +72,9 @@ export const spec = { 'secure': 1, 'host': location.host, 'page': location.pathname, - 'coppa': config.getConfig('coppa') === true ? 1 : 0, - 'placements': placements + 'coppa': config.getConfig('coppa') === true ? 1 : 0 }; + request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) if (bidderRequest) { if (bidderRequest.uspConsent) { @@ -66,32 +88,11 @@ export const spec = { } } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - let traff = bid.params.traffic || BANNER - placements.push({ - region: bid.params.region, - bidId: bid.bidId, - sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], - traffic: traff, - publisherId: bid.params.publisherId - }); - imp.push({ - id: bid.bidId, - sizes: bid.mediaTypes && bid.mediaTypes[traff] && bid.mediaTypes[traff].sizes ? bid.mediaTypes[traff].sizes : [], - traffic: traff, - publisherId: bid.params.publisherId - }) - if (bid.schain) { - placements.schain = bid.schain; - } - } return { method: 'POST', url: AD_URL, - data: request + data: request, + }; }, @@ -143,4 +144,21 @@ export const spec = { }; +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + registerBidder(spec); diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index db38845c11a..1a7e24d64cb 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,5 +1,5 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/precisoBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/precisoBidAdapter.js'; import { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 @@ -10,14 +10,19 @@ const BIDDER_CODE = 'preciso'; describe('PrecisoAdapter', function () { let bid = { - bidId: '23fhj33i987f', bidder: 'preciso', + mediaTypes: { + banner: { + sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] + } + }, params: { host: 'prebid', sourceid: '0', publisherId: '0', - traffic: 'banner', + mediaType: 'banner', + region: 'prebid-eu' } @@ -50,7 +55,9 @@ describe('PrecisoAdapter', 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('id', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); + + // expect(data).to.have.all.keys('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); + expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).to.be.a('number'); @@ -58,26 +65,21 @@ describe('PrecisoAdapter', function () { expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - let placement = data['placements'][0]; - expect(placement).to.have.keys('region', 'bidId', 'traffic', 'sizes', 'publisherId'); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); - expect(placement.region).to.equal('prebid-eu'); }); it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; + expect(data.imp).to.be.an('array').that.is.empty; }); }); - describe('with COPPA', function() { - beforeEach(function() { + describe('with COPPA', function () { + beforeEach(function () { sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); }); - afterEach(function() { + afterEach(function () { config.getConfig.restore(); }); @@ -90,7 +92,9 @@ describe('PrecisoAdapter', function () { describe('interpretResponse', function () { it('should get correct bid response', function () { let response = { - id: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + + bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ { bid: [ From 00b23dacbe17cc9c038f4d580546320a7fc618e4 Mon Sep 17 00:00:00 2001 From: Michael Callari Date: Wed, 13 Sep 2023 08:11:35 -0400 Subject: [PATCH 010/760] OptimeraRTD - Adding apiVersion param and v1 endpoint. (#10476) --- modules/optimeraRtdProvider.js | 26 ++++++- modules/optimeraRtdProvider.md | 6 +- test/spec/modules/optimeraRtdProvider_spec.js | 71 ++++++++++++++++++- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index dfe8f1bfcf2..04d9b9d1b9f 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -16,6 +16,7 @@ * @property {string} clientID * @property {string} optimeraKeyName * @property {string} device + * @property {string} apiVersion */ import { logInfo, logError } from '../src/utils.js'; @@ -38,7 +39,10 @@ export let optimeraKeyName = 'hb_deal_optimera'; * the targeting values. * @type {string} */ -export const scoresBaseURL = 'https://dyv1bugovvq1g.cloudfront.net/'; +export const scoresBaseURL = { + v0: 'https://dyv1bugovvq1g.cloudfront.net/', + v1: 'https://v1.oapi26b.com/', +}; /** * Optimera Score File URL. @@ -58,6 +62,12 @@ export let clientID; */ export let device = 'default'; +/** + * Optional apiVersion parameter. + * @type {string} + */ +export let apiVersion = 'v0'; + /** * Targeting object for all ad positions. * @type {string} @@ -127,6 +137,7 @@ export function onAuctionInit(auctionDetails, config, userConsent) { /** * Initialize the Module. + * moduleConfig.params.apiVersion can be either v0 or v1. */ export function init(moduleConfig) { _moduleParams = moduleConfig.params; @@ -138,6 +149,9 @@ export function init(moduleConfig) { if (_moduleParams.device) { device = _moduleParams.device; } + if (_moduleParams.apiVersion) { + apiVersion = (_moduleParams.apiVersion.includes('v1', 'v0')) ? _moduleParams.apiVersion : 'v0'; + } setScoresURL(); scoreFileRequest(); return true; @@ -162,7 +176,15 @@ export function init(moduleConfig) { export function setScoresURL() { const optimeraHost = window.location.host; const optimeraPathName = window.location.pathname; - const newScoresURL = `${scoresBaseURL}${clientID}/${optimeraHost}${optimeraPathName}.js`; + const baseUrl = scoresBaseURL[apiVersion] ? scoresBaseURL[apiVersion] : scoresBaseURL.v0; + let newScoresURL; + + if (apiVersion === 'v1') { + newScoresURL = `${baseUrl}api/products/scores?c=${clientID}&h=${optimeraHost}&p=${optimeraPathName}&s=${device}`; + } else { + newScoresURL = `${baseUrl}${clientID}/${optimeraHost}${optimeraPathName}.js`; + } + if (scoresURL !== newScoresURL) { scoresURL = newScoresURL; fetchScoreFile = true; diff --git a/modules/optimeraRtdProvider.md b/modules/optimeraRtdProvider.md index 610dec537e0..8b66deb5ad5 100644 --- a/modules/optimeraRtdProvider.md +++ b/modules/optimeraRtdProvider.md @@ -1,6 +1,6 @@ # Overview ``` -Module Name: Optimera Real Time Date Module +Module Name: Optimera Real Time Data Module Module Type: RTD Module Maintainer: mcallari@optimera.nyc ``` @@ -26,7 +26,8 @@ Configuration example for using RTD module with `optimera` provider params: { clientID: '9999', optimeraKeyName: 'optimera', - device: 'de' + device: 'de', + apiVersion: 'v0', } } ] @@ -42,3 +43,4 @@ Contact Optimera to get assistance with the params. | clientID | string | required | Optimera Client ID | | optimeraKeyName | string | optional | GAM key name for Optimera. If migrating from the Optimera bidder adapter this will default to hb_deal_optimera and can be ommitted from the configuration. | | device | string | optional | Device type code for mobile, tablet, or desktop. Either mo, tb, de | +| apiVersion | string | optional | Optimera API Versions. Either v0, or v1. ** Note: v1 wll need to be enabled specifically for your account, otherwise use v0. \ No newline at end of file diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js index aec8b79045e..8a9f000bbb9 100644 --- a/test/spec/modules/optimeraRtdProvider_spec.js +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -21,13 +21,80 @@ describe('Optimera RTD sub module', () => { }); }); -describe('Optimera RTD score file url is properly set', () => { - it('Proerly set the score file url', () => { +describe('Optimera RTD score file URL is properly set for v0', () => { + it('should properly set the score file URL', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de', + apiVersion: 'v0', + } + }] + }; + optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScores(); + expect(optimeraRTD.apiVersion).to.equal('v0'); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + }); + + it('should properly set the score file URL without apiVersion set', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de', + } + }] + }; + optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScores(); + expect(optimeraRTD.apiVersion).to.equal('v0'); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + }); + + it('should properly set the score file URL with an api version other than v0 or v1', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de', + apiVersion: 'v15', + } + }] + }; + optimeraRTD.init(conf.dataProviders[0]); optimeraRTD.setScores(); expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); }); }); +describe('Optimera RTD score file URL is properly set for v1', () => { + it('should properly set the score file URL', () => { + const conf = { + dataProviders: [{ + name: 'optimeraRTD', + params: { + clientID: '9999', + optimeraKeyName: 'optimera', + device: 'de', + apiVersion: 'v1', + } + }] + }; + optimeraRTD.init(conf.dataProviders[0]); + optimeraRTD.setScores(); + expect(optimeraRTD.apiVersion).to.equal('v1'); + expect(optimeraRTD.scoresURL).to.equal('https://v1.oapi26b.com/api/products/scores?c=9999&h=localhost:9876&p=/context.html&s=de'); + }); +}); + describe('Optimera RTD score file properly sets targeting values', () => { const scores = { 'div-0': ['A1', 'A2'], From d4203d2f5141529ef40c0bbfd3b56b0938809b27 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Wed, 13 Sep 2023 22:23:54 +1000 Subject: [PATCH 011/760] Adnuntius Bid Adaptor: Enable choosing bidType for cpm. (#10446) - enable the use of grossBid or netBid in cpm of bid response. --- .../gpt/adnuntius_example.html | 95 +++++++++++++++++++ modules/adnuntiusBidAdapter.js | 16 +++- test/spec/modules/adnuntiusBidAdapter_spec.js | 24 ++++- 3 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 integrationExamples/gpt/adnuntius_example.html diff --git a/integrationExamples/gpt/adnuntius_example.html b/integrationExamples/gpt/adnuntius_example.html new file mode 100644 index 00000000000..b61c4e0674e --- /dev/null +++ b/integrationExamples/gpt/adnuntius_example.html @@ -0,0 +1,95 @@ + + + + + + + +

Adnuntius Prebid Adaptor Test

+
Ad Slot 1
+ + +
+ +
+ + + diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index e44e2b1471a..a2b695e55e0 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -14,6 +14,7 @@ const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' const MAXIMUM_DEALS_LIMIT = 5; +const VALID_BID_TYPES = ['netBid', 'grossBid']; const checkSegment = function (segment) { if (isStr(segment)) return segment; @@ -48,6 +49,10 @@ const getUsi = function (meta, ortb2, bidderRequest) { return usi } +const validateBidType = function(bidTypeOption) { + return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; +} + const AU_ID_REGEX = new RegExp('^[0-9A-Fa-f]{1,20}$'); export const spec = { @@ -126,6 +131,15 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const adUnits = serverResponse.body.adUnits; + let validatedBidType = validateBidType(config.getConfig().bidType); + if (bidRequest.bid) { + bidRequest.bid.forEach(b => { + if (b.params && b.params.bidType) { + validatedBidType = validateBidType(b.params.bidType); + } + }); + } + function buildAdResponse(bidderCode, ad, adUnit, dealCount) { const destinationUrls = ad.destinationUrls || {}; const advertiserDomains = []; @@ -135,7 +149,7 @@ export const spec = { const adResponse = { bidderCode: bidderCode, requestId: adUnit.targetId, - cpm: (ad.bid) ? ad.bid.amount * 1000 : 0, + cpm: ad[validatedBidType] ? ad[validatedBidType].amount * 1000 : 0, width: Number(ad.creativeWidth), height: Number(ad.creativeHeight), creativeId: ad.creativeId, diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 4ddbaaa2e2a..6a77c9205ca 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -99,7 +99,10 @@ describe('adnuntiusBidAdapter', function() { const videoBidRequest = { bid: videoBidderRequest, - bidder: 'adnuntius' + bidder: 'adnuntius', + params: { + bidType: 'justsomestuff-error-handling' + } } const deals = [ @@ -749,12 +752,20 @@ describe('adnuntiusBidAdapter', function() { describe('interpretResponse', function() { it('should return valid response when passed valid server response', function() { - const interpretedResponse = spec.interpretResponse(serverResponse, singleBidRequest); + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + bidType: 'netBid', + maxDeals: 1 + } + }); + + const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(serverResponse, singleBidRequest)); expect(interpretedResponse).to.have.lengthOf(2); const deal = serverResponse.body.adUnits[0].deals[0]; expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); - expect(interpretedResponse[0].cpm).to.equal(deal.bid.amount * 1000); + expect(interpretedResponse[0].cpm).to.equal(deal.netBid.amount * 1000); expect(interpretedResponse[0].width).to.equal(Number(deal.creativeWidth)); expect(interpretedResponse[0].height).to.equal(Number(deal.creativeHeight)); expect(interpretedResponse[0].creativeId).to.equal(deal.creativeId); @@ -770,7 +781,7 @@ describe('adnuntiusBidAdapter', function() { const ad = serverResponse.body.adUnits[0].ads[0]; expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); - expect(interpretedResponse[1].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000); expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight)); expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); @@ -808,6 +819,9 @@ describe('adnuntiusBidAdapter', function() { { bidder: 'adn-alt', bidId: 'adn-0000000000000551', + params: { + bidType: 'netBid' + } } ] }; @@ -818,7 +832,7 @@ describe('adnuntiusBidAdapter', function() { const ad = serverResponse.body.adUnits[0].ads[0]; expect(interpretedResponse[0].bidderCode).to.equal('adn-alt'); - expect(interpretedResponse[0].cpm).to.equal(ad.bid.amount * 1000); + expect(interpretedResponse[0].cpm).to.equal(ad.netBid.amount * 1000); expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth)); expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight)); expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId); From 725ed6036f1110d92e78d336ecea357fd5324afd Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Wed, 13 Sep 2023 14:57:58 -0400 Subject: [PATCH 012/760] Sonobi Bid Adapter : send video playbackmethod and placement in the bid request (#10474) * Send video playbackmethod and placement in the bid request to Sonobi * added a separate field for plcmt --- modules/sonobiBidAdapter.js | 32 ++++++++++++++-------- test/spec/modules/sonobiBidAdapter_spec.js | 7 +++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 704275cc1bf..b40ff9a65c9 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -60,23 +60,15 @@ export const spec = { */ buildRequests: (validBidRequests, bidderRequest) => { const bids = validBidRequests.map(bid => { - let mediaType; - - if (deepAccess(bid, 'mediaTypes.video')) { - mediaType = 'video'; - } else if (deepAccess(bid, 'mediaTypes.banner')) { - mediaType = 'display'; - } - let slotIdentifier = _validateSlot(bid); if (/^[\/]?[\d]+[[\/].+[\/]?]?$/.test(slotIdentifier)) { slotIdentifier = slotIdentifier.charAt(0) === '/' ? slotIdentifier : '/' + slotIdentifier; return { - [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(mediaType)}` + [`${slotIdentifier}|${bid.bidId}`]: `${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(bid)}` } } else if (/^[0-9a-fA-F]{20}$/.test(slotIdentifier) && slotIdentifier.length === 20) { return { - [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(mediaType)}` + [bid.bidId]: `${slotIdentifier}|${_validateSize(bid)}|${_validateFloor(bid)}${_validateGPID(bid)}${_validateMediaType(bid)}` } } else { logError(`The ad unit code or Sonobi Placement id for slot ${bid.bidId} is invalid`); @@ -336,10 +328,28 @@ function _validateGPID(bid) { return '' } -function _validateMediaType(mediaType) { +function _validateMediaType(bidRequest) { + let mediaType; + if (deepAccess(bidRequest, 'mediaTypes.video')) { + mediaType = 'video'; + } else if (deepAccess(bidRequest, 'mediaTypes.banner')) { + mediaType = 'display'; + } + let mediaTypeValidation = ''; if (mediaType === 'video') { mediaTypeValidation = 'c=v,'; + if (deepAccess(bidRequest, 'mediaTypes.video.playbackmethod')) { + mediaTypeValidation = `${mediaTypeValidation}pm=${deepAccess(bidRequest, 'mediaTypes.video.playbackmethod').join(':')},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.placement')) { + let placement = deepAccess(bidRequest, 'mediaTypes.video.placement'); + mediaTypeValidation = `${mediaTypeValidation}p=${placement},`; + } + if (deepAccess(bidRequest, 'mediaTypes.video.plcmt')) { + let plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt'); + mediaTypeValidation = `${mediaTypeValidation}pl=${plcmt},`; + } } else if (mediaType === 'display') { mediaTypeValidation = 'c=d,'; } diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index de8d0a5bda7..b179f870e0d 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -295,7 +295,10 @@ describe('SonobiBidAdapter', function () { mediaTypes: { video: { playerSize: [640, 480], - context: 'outstream' + context: 'outstream', + playbackmethod: [1, 2, 3], + plcmt: 3, + placement: 2 } } }, @@ -339,7 +342,7 @@ describe('SonobiBidAdapter', function () { }]; let keyMakerData = { - '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,', + '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,pm=1:2:3,p=2,pl=3,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; From bc2f3f63f172578cc6a7ea7e342fd88964d062e2 Mon Sep 17 00:00:00 2001 From: hkimmgni <144719203+hkimmgni@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:35:23 -0400 Subject: [PATCH 013/760] Add rubicon size 632 and 634 (#10481) --- modules/rubiconBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 2c2a02e1949..8e083a43505 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -139,7 +139,9 @@ var sizeMap = { 576: '610x877', 578: '980x552', 580: '505x656', - 622: '192x160' + 622: '192x160', + 632: '1200x450', + 634: '340x450' }; _each(sizeMap, (item, key) => sizeMap[item] = key); From 50bcec38aa0c671406aacfde9bc570a59a0749fc Mon Sep 17 00:00:00 2001 From: AndreaC <67786179+darkstarac@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:37:10 +0200 Subject: [PATCH 014/760] AIDEM Bidder Adapter: ortbConverter (#10391) * AIDEM Bid Adapter * Added _spec.js * update * Fix Navigator in _spec.js * Removed timeout handler. * Added publisherId as required bidder params * moved publisherId into site publisher object * Added wpar to environment * Added placementId parameter * added unit tests for the wpar environment object * PlacementId is now a required parameter Added optional rateLimit parameter Added publisherId, siteId, placementId in win notice payload Added unit tests * Revert to optional placementId parameter Added missing semicolons * Extended win notice * Added arbitrary ext field to win notice * Moved aidemBidAdapter implementation to comply with ortbConverter * disabled video-specific tests --------- Co-authored-by: Giovanni Sollazzo Co-authored-by: darkstar Co-authored-by: Andrea Tumbarello --- modules/aidemBidAdapter.js | 371 +++-------------- test/spec/modules/aidemBidAdapter_spec.js | 483 +++++++--------------- 2 files changed, 222 insertions(+), 632 deletions(-) diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index 7469f26156b..c6a5cd96fb6 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,27 +1,15 @@ -import { - _each, - contains, - deepAccess, - deepSetValue, - getDNT, - isBoolean, - isNumber, - isStr, - logError, - logInfo -} from '../src/utils.js'; +import {deepAccess, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {ajax} from '../src/ajax.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'aidem'; const BASE_URL = 'https://zero.aidemsrv.com'; const LOCAL_BASE_URL = 'http://127.0.0.1:8787'; -const AVAILABLE_CURRENCIES = ['USD']; -const DEFAULT_CURRENCY = ['USD']; // NOTE - USD is the only supported currency right now; Hardcoded for bids const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const REQUIRED_VIDEO_PARAMS = [ 'mimes', 'protocols', 'context' ]; @@ -37,34 +25,63 @@ export const ERROR_CODES = { }; const endpoints = { - request: `${BASE_URL}/bid/request`, - notice: { - win: `${BASE_URL}/notice/win`, - timeout: `${BASE_URL}/notice/timeout`, - error: `${BASE_URL}/notice/error`, - } + request: `${BASE_URL}/prebidjs/ortb/v2.6/bid/request`, + // notice: { + // win: `${BASE_URL}/notice/win`, + // timeout: `${BASE_URL}/notice/timeout`, + // error: `${BASE_URL}/notice/error`, + // } }; -export function setEndPoints(env = null, path = '', mediaType = BANNER) { +export function setEndPoints(env = null, path = '') { switch (env) { case 'local': - endpoints.request = mediaType === BANNER ? `${LOCAL_BASE_URL}${path}/bid/request` : `${LOCAL_BASE_URL}${path}/bid/videorequest`; - endpoints.notice.win = `${LOCAL_BASE_URL}${path}/notice/win`; - endpoints.notice.error = `${LOCAL_BASE_URL}${path}/notice/error`; - endpoints.notice.timeout = `${LOCAL_BASE_URL}${path}/notice/timeout`; + endpoints.request = `${LOCAL_BASE_URL}${path}/prebidjs/ortb/v2.6/bid/request`; break; case 'main': - endpoints.request = mediaType === BANNER ? `${BASE_URL}${path}/bid/request` : `${BASE_URL}${path}/bid/videorequest`; - endpoints.notice.win = `${BASE_URL}${path}/notice/win`; - endpoints.notice.error = `${BASE_URL}${path}/notice/error`; - endpoints.notice.timeout = `${BASE_URL}${path}/notice/timeout`; + endpoints.request = `${BASE_URL}${path}/prebidjs/ortb/v2.6/bid/request`; break; } return endpoints; } config.getConfig('aidem', function (config) { - if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path, config.aidem.mediaType); } + if (config.aidem.env) { setEndPoints(config.aidem.env, config.aidem.path); } +}); + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + request(buildRequest, imps, bidderRequest, context) { + logInfo('Building request'); + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'at', 1); + setPrebidRequestEnvironment(request); + deepSetValue(request, 'regs', getRegs()); + deepSetValue(request, 'site.publisher.id', bidderRequest.bids[0].params.publisherId); + deepSetValue(request, 'site.id', bidderRequest.bids[0].params.siteId); + return request; + }, + imp(buildImp, bidRequest, context) { + logInfo('Building imp bidRequest', bidRequest); + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'tagId', bidRequest.params.placementId); + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const {bidRequest} = context; + const bidResponse = buildBidResponse(bid, context); + logInfo('Building bidResponse'); + logInfo('bid', bid); + logInfo('bidRequest', bidRequest); + logInfo('bidResponse', bidResponse); + if (bidResponse.mediaType === VIDEO) { + deepSetValue(bidResponse, 'vastUrl', bid.adm); + } + return bidResponse; + } }); // AIDEM Custom FN @@ -89,49 +106,6 @@ function recur(obj) { return result; } -// ================================================================================= -function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; - if (!connection) { - return 0; - } - switch (connection.type) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'cellular': - switch (connection.effectiveType) { - case 'slow-2g': - return 4; - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - case '5g': - return 7; - default: - return 3; - } - default: - return 0; - } -} - -function getDevice() { - const language = navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage; - return { - ua: navigator.userAgent, - dnt: !!getDNT(), - language: language, - connectiontype: getConnectionType(), - screen_width: screen.width, - screen_height: screen.height - }; -} - function getRegs() { let regs = {}; const consentManagement = config.getConfig('consentManagement'); @@ -157,129 +131,6 @@ function getRegs() { return regs; } -function getPageUrl(bidderRequest) { - return bidderRequest?.refererInfo?.page; -} - -function buildWinNotice(bid) { - const params = bid.params[0]; - const app = deepAccess(bid, 'meta.ext.app') - const winNoticeExt = deepAccess(bid, 'meta.ext.win_notice_ext') - return { - publisherId: params.publisherId, - siteId: params.siteId, - placementId: params.placementId, - burl: deepAccess(bid, 'meta.burl'), - cpm: bid.cpm, - currency: bid.currency, - impid: deepAccess(bid, 'meta.impid'), - dsp_id: deepAccess(bid, 'meta.dsp_id'), - adUnitCode: bid.adUnitCode, - // TODO: fix auctionId/transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - transactionId: bid.transactionId, - ttl: bid.ttl, - requestTimestamp: bid.requestTimestamp, - responseTimestamp: bid.responseTimestamp, - mediatype: bid.mediaType, - environment: app ? 'app' : 'web', - ...app, - ext: winNoticeExt, - }; -} - -function buildErrorNotice(prebidErrorResponse) { - return { - message: `Prebid.js: Server call for ${prebidErrorResponse.bidderCode} failed.`, - url: encodeURIComponent(getPageUrl(prebidErrorResponse)), - auctionId: prebidErrorResponse.auctionId, - bidderRequestId: prebidErrorResponse.bidderRequestId, - metrics: {} - }; -} - -function hasValidFloor(obj) { - if (!obj) return false; - const hasValue = !isNaN(Number(obj.value)); - const hasCurrency = contains(AVAILABLE_CURRENCIES, obj.currency); - return hasValue && hasCurrency; -} - -function getMediaType(bidRequest) { - if ((bidRequest.mediaTypes && bidRequest.mediaTypes.hasOwnProperty('video')) || bidRequest.params.hasOwnProperty('video')) { return VIDEO; } - return BANNER; -} - -function getPrebidRequestFields(bidderRequest, bidRequests) { - const payload = {}; - // Base Payload Data - deepSetValue(payload, 'id', bidderRequest.bidderRequestId); - // Impressions - setPrebidImpressionObject(bidRequests, payload); - // Device - deepSetValue(payload, 'device', getDevice()); - // Timeout - deepSetValue(payload, 'tmax', bidderRequest.timeout); - // Currency - deepSetValue(payload, 'cur', DEFAULT_CURRENCY); - // Timezone - deepSetValue(payload, 'tz', new Date().getTimezoneOffset()); - // Privacy Regs - deepSetValue(payload, 'regs', getRegs()); - // Site - setPrebidSiteObject(bidderRequest, payload); - // Environment - setPrebidRequestEnvironment(payload); - // AT auction type - deepSetValue(payload, 'at', 1); - - return payload; -} - -function setPrebidImpressionObject(bidRequests, payload) { - payload.imp = []; - _each(bidRequests, function (bidRequest) { - const impressionObject = {}; - // Placement or ad tag used to initiate the auction - deepSetValue(impressionObject, 'id', bidRequest.bidId); - // Transaction id - // TODO: `imp.tid` is not ORTB, is this intentional? - deepSetValue(impressionObject, 'tid', deepAccess(bidRequest, 'ortb2Imp.ext.tid')); - // placement id - deepSetValue(impressionObject, 'tagid', deepAccess(bidRequest, 'params.placementId', null)); - // Publisher id - deepSetValue(payload, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); - // Site id - deepSetValue(payload, 'site.id', deepAccess(bidRequest, 'params.siteId')); - const mediaType = getMediaType(bidRequest); - switch (mediaType) { - case 'banner': - setPrebidImpressionObjectBanner(bidRequest, impressionObject); - break; - case 'video': - setPrebidImpressionObjectVideo(bidRequest, impressionObject); - break; - } - - // Floor (optional) - setPrebidImpressionObjectFloor(bidRequest, impressionObject); - - impressionObject.imp_ext = {}; - - payload.imp.push(impressionObject); - }); -} - -function setPrebidSiteObject(bidderRequest, payload) { - deepSetValue(payload, 'site.domain', deepAccess(bidderRequest, 'refererInfo.domain')); - deepSetValue(payload, 'site.page', deepAccess(bidderRequest, 'refererInfo.page')); - deepSetValue(payload, 'site.referer', deepAccess(bidderRequest, 'refererInfo.ref')); - deepSetValue(payload, 'site.cat', deepAccess(bidderRequest, 'ortb2.site.cat')); - deepSetValue(payload, 'site.sectioncat', deepAccess(bidderRequest, 'ortb2.site.sectioncat')); - deepSetValue(payload, 'site.keywords', deepAccess(bidderRequest, 'ortb2.site.keywords')); - deepSetValue(payload, 'site.site_ext', deepAccess(bidderRequest, 'ortb2.site.ext')); // see https://docs.prebid.org/features/firstPartyData.html -} - function setPrebidRequestEnvironment(payload) { const __navigator = JSON.parse(JSON.stringify(recur(navigator))); delete __navigator.plugins; @@ -296,92 +147,6 @@ function setPrebidRequestEnvironment(payload) { deepSetValue(payload, 'environment.wpar.innerHeight', window.innerHeight); } -function setPrebidImpressionObjectFloor(bidRequest, impressionObject) { - const floor = deepAccess(bidRequest, 'params.floor'); - if (hasValidFloor(floor)) { - deepSetValue(impressionObject, 'floor.value', floor.value); - deepSetValue(impressionObject, 'floor.currency', floor.currency); - } -} - -function setPrebidImpressionObjectBanner(bidRequest, impressionObject) { - deepSetValue(impressionObject, 'mediatype', BANNER); - deepSetValue(impressionObject, 'banner.topframe', 1); - deepSetValue(impressionObject, 'banner.format', []); - _each(bidRequest.mediaTypes.banner.sizes, function (bannerFormat) { - const format = {}; - deepSetValue(format, 'w', bannerFormat[0]); - deepSetValue(format, 'h', bannerFormat[1]); - deepSetValue(format, 'format_ext', {}); - impressionObject.banner.format.push(format); - }); -} - -function setPrebidImpressionObjectVideo(bidRequest, impressionObject) { - deepSetValue(impressionObject, 'mediatype', VIDEO); - deepSetValue(impressionObject, 'video.format', []); - deepSetValue(impressionObject, 'video.mimes', bidRequest.mediaTypes.video.mimes); - deepSetValue(impressionObject, 'video.minDuration', bidRequest.mediaTypes.video.minduration); - deepSetValue(impressionObject, 'video.maxDuration', bidRequest.mediaTypes.video.maxduration); - deepSetValue(impressionObject, 'video.protocols', bidRequest.mediaTypes.video.protocols); - deepSetValue(impressionObject, 'video.context', bidRequest.mediaTypes.video.context); - deepSetValue(impressionObject, 'video.playbackmethod', bidRequest.mediaTypes.video.playbackmethod); - deepSetValue(impressionObject, 'skip', bidRequest.mediaTypes.video.skip); - deepSetValue(impressionObject, 'skipafter', bidRequest.mediaTypes.video.skipafter); - deepSetValue(impressionObject, 'video.pos', bidRequest.mediaTypes.video.pos); - _each(bidRequest.mediaTypes.video.playerSize, function (videoPlayerSize) { - const format = {}; - deepSetValue(format, 'w', videoPlayerSize[0]); - deepSetValue(format, 'h', videoPlayerSize[1]); - deepSetValue(format, 'format_ext', {}); - impressionObject.video.format.push(format); - }); -} - -function getPrebidResponseBidObject(openRTBResponseBidObject) { - const prebidResponseBidObject = {}; - // Common properties - deepSetValue(prebidResponseBidObject, 'requestId', openRTBResponseBidObject.impid); - deepSetValue(prebidResponseBidObject, 'cpm', parseFloat(openRTBResponseBidObject.price)); - deepSetValue(prebidResponseBidObject, 'creativeId', openRTBResponseBidObject.crid); - deepSetValue(prebidResponseBidObject, 'currency', openRTBResponseBidObject.cur ? openRTBResponseBidObject.cur.toUpperCase() : DEFAULT_CURRENCY); - deepSetValue(prebidResponseBidObject, 'width', openRTBResponseBidObject.w); - deepSetValue(prebidResponseBidObject, 'height', openRTBResponseBidObject.h); - deepSetValue(prebidResponseBidObject, 'dealId', openRTBResponseBidObject.dealid); - deepSetValue(prebidResponseBidObject, 'netRevenue', true); - deepSetValue(prebidResponseBidObject, 'ttl', 60000); - - if (openRTBResponseBidObject.mediatype === VIDEO) { - logInfo('bidObject.mediatype == VIDEO'); - deepSetValue(prebidResponseBidObject, 'mediaType', VIDEO); - deepSetValue(prebidResponseBidObject, 'vastUrl', openRTBResponseBidObject.adm); - } else { - logInfo('bidObject.mediatype == BANNER'); - deepSetValue(prebidResponseBidObject, 'mediaType', BANNER); - deepSetValue(prebidResponseBidObject, 'ad', openRTBResponseBidObject.adm); - } - setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject); - return prebidResponseBidObject; -} - -function setPrebidResponseBidObjectMeta(prebidResponseBidObject, openRTBResponseBidObject) { - logInfo('AIDEM Bid Adapter meta', openRTBResponseBidObject); - deepSetValue(prebidResponseBidObject, 'meta.advertiserDomains', deepAccess(openRTBResponseBidObject, 'meta.advertiserDomains')); - deepSetValue(prebidResponseBidObject, 'meta.ext', deepAccess(openRTBResponseBidObject, 'meta.ext')); - if (openRTBResponseBidObject.cat && Array.isArray(openRTBResponseBidObject.cat)) { - const primaryCatId = openRTBResponseBidObject.cat.shift(); - deepSetValue(prebidResponseBidObject, 'meta.primaryCatId', primaryCatId); - deepSetValue(prebidResponseBidObject, 'meta.secondaryCatIds', openRTBResponseBidObject.cat); - } - deepSetValue(prebidResponseBidObject, 'meta.id', openRTBResponseBidObject.id); - deepSetValue(prebidResponseBidObject, 'meta.dsp_id', openRTBResponseBidObject.dsp_id); - deepSetValue(prebidResponseBidObject, 'meta.adid', openRTBResponseBidObject.adid); - deepSetValue(prebidResponseBidObject, 'meta.burl', openRTBResponseBidObject.burl); - deepSetValue(prebidResponseBidObject, 'meta.impid', openRTBResponseBidObject.impid); - deepSetValue(prebidResponseBidObject, 'meta.cat', openRTBResponseBidObject.cat); - deepSetValue(prebidResponseBidObject, 'meta.cid', openRTBResponseBidObject.cid); -} - function hasValidMediaType(bidRequest) { const supported = hasBannerMediaType(bidRequest) || hasVideoMediaType(bidRequest); if (!supported) { @@ -494,48 +259,38 @@ export const spec = { return passesRateLimit(bidRequest); }, - buildRequests: function(validBidRequests, bidderRequest) { - logInfo('validBidRequests: ', validBidRequests); + buildRequests: function(bidRequests, bidderRequest) { + logInfo('bidRequests: ', bidRequests); logInfo('bidderRequest: ', bidderRequest); - const prebidRequest = getPrebidRequestFields(bidderRequest, validBidRequests); - const payloadString = JSON.stringify(prebidRequest); - + const data = converter.toORTB({bidRequests, bidderRequest}); + logInfo('request payload', data); return { method: 'POST', url: endpoints.request, - data: payloadString, + data, options: { withCredentials: true } }; }, - interpretResponse: function (serverResponse) { - const bids = []; - logInfo('serverResponse: ', serverResponse); - _each(serverResponse.body.bid, function (bidObject) { - logInfo('bidObject: ', bidObject); - if (!bidObject.price || !bidObject.adm) { - return; - } - logInfo('CPM OK'); - const bid = getPrebidResponseBidObject(bidObject); - bids.push(bid); - }); - return bids; + interpretResponse: function (serverResponse, request) { + logInfo('serverResponse body: ', serverResponse.body); + logInfo('request data: ', request.data); + const ortbBids = converter.fromORTB({response: serverResponse.body, request: request.data}).bids; + logInfo('ortbBids: ', ortbBids); + return ortbBids; }, onBidWon: function(bid) { // Bidder specific code logInfo('onBidWon bid: ', bid); - const notice = buildWinNotice(bid); - ajax(endpoints.notice.win, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); + ajax(bid.burl); }, - onBidderError: function({ bidderRequest }) { - // Bidder specific code - const notice = buildErrorNotice(bidderRequest); - ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); - }, + // onBidderError: function({ bidderRequest }) { + // const notice = buildErrorNotice(bidderRequest); + // ajax(endpoints.notice.error, null, JSON.stringify(notice), { method: 'POST', withCredentials: true }); + // }, }; registerBidder(spec); diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 8b401491ba0..3de348197b2 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -155,9 +155,9 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ { adUnitCode: 'test-div', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidId: '22c4871113f461', + bidId: '2705bfae8ea667', bidder: 'aidem', - bidderRequestId: '15246a574e859f', + bidderRequestId: '1bbb7854dfa0d8', mediaTypes: { banner: { sizes: [ @@ -171,11 +171,7 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ placementId: '13144370' }, src: 'client', - ortb2Imp: { - ext: { - tid: '54a58774-7a41-494e-9aaf-fa7b79164f0c', - }, - }, + transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' } ]; @@ -183,9 +179,9 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ { adUnitCode: 'test-div', auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidId: '22c4871113f461', + bidId: '2705bfae8ea667', bidder: 'aidem', - bidderRequestId: '15246a574e859f', + bidderRequestId: '1bbb7854dfa0d8', mediaTypes: { video: { minduration: 7, @@ -200,18 +196,23 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ placementId: '13144370' }, src: 'client', - ortb2Imp: { - ext: { - tid: '54a58774-7a41-494e-9aaf-fa7b79164f0c', - } - }, + transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' } ]; const VALID_BIDDER_REQUEST = { - auctionId: '6e9b46c3-65a8-46ea-89f4-c5071110c85c', + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', bidderCode: 'aidem', - bidderRequestId: '170ea5d2b1d073', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753' + }, + } + ], refererInfo: { page: 'test-page', domain: 'test-domain', @@ -219,182 +220,68 @@ const VALID_BIDDER_REQUEST = { }, } -// Add mediatype const SERVER_RESPONSE_BANNER = { - body: { - id: 'efa1930a-bc3e-4fd0-8368-08bc40236b4f', - bid: [ - // BANNER - { - 'id': '2e614be960ee1d', - 'impid': '2e614be960ee1d', - 'price': 7.91, - 'mediatype': 'banner', - 'adid': '24277955', - 'adm': 'creativity_banner', - 'adomain': [ - 'aidem.com' - ], - 'iurl': 'http://www.aidem.com', - 'cat': [], - 'cid': '4193561', - 'crid': '24277955', - 'w': 300, - 'h': 250, - 'ext': { - 'dspid': 85, - 'advbrandid': 1246, - 'advbrand': 'AIDEM' - } - }, - ], - cur: 'USD' - }, -} - -const SERVER_RESPONSE_VIDEO = { - body: { - id: 'efa1930a-bc3e-4fd0-8368-08bc40236b4f', - bid: [ - // VIDEO - { - 'id': '2876a29392a47c', - 'impid': '2876a29392a47c', - 'price': 7.93, - 'mediatype': 'video', - 'adid': '24277955', - 'adm': 'https://hermes.aidemsrv.com/vast-tag/cl9mzhhd502uq09l720uegb02?auction_id={{AUCTION_ID}}&cachebuster={{CACHEBUSTER}}', - 'adomain': [ - 'aidem.com' - ], - 'iurl': 'http://www.aidem.com', - 'cat': [], - 'cid': '4193561', - 'crid': '24277955', - 'w': 640, - 'h': 480, - 'ext': { - 'dspid': 85, - 'advbrandid': 1246, - 'advbrand': 'AIDEM' - } - } - ], - cur: 'USD' - }, -} - -const WIN_NOTICE_WEB = { - 'adId': '3a20ee5dc78c1e', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'creativeId': '24277955', - 'cpm': 1, - 'netRevenue': false, - 'adserverTargeting': { - 'hb_bidder': 'aidem', - 'hb_adid': '3a20ee5dc78c1e', - 'hb_pb': '1.00', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner', - 'hb_adomain': 'example.com' - }, - - 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', - 'currency': [ - 'USD' - ], - 'mediaType': 'banner', - 'meta': { - 'advertiserDomains': [ - 'cloudflare.com' - ], - 'ext': {} - }, - 'size': '300x250', - 'params': [ + 'id': '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + 'seatbid': [ { - 'placementId': '13144370', - 'siteId': '23434', - 'publisherId': '7689670753' + 'bid': [ + { + 'id': 'beeswax/aidem', + 'impid': '2705bfae8ea667', + 'price': 0.00875, + 'burl': 'imp_burl', + 'adm': 'creativity_banner', + 'adid': '2:64:162:1001', + 'adomain': [ + 'aidem.com' + ], + 'cid': '64', + 'crid': 'aidem-1001', + 'cat': [], + 'w': 300, + 'h': 250, + 'mtype': 1 + } + ], + 'seat': 'aidemdsp', + 'group': 0 } ], - 'width': 300, - 'height': 250, - 'status': 'rendered', - 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', - 'ttl': 300, - 'requestTimestamp': 1666796241007, - 'responseTimestamp': 1666796241021, - metrics: { - getMetrics() { - return { - - } - } - } + 'cur': 'USD' } -const WIN_NOTICE_APP = { - 'adId': '3a20ee5dc78c1e', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'creativeId': '24277955', - 'cpm': 1, - 'netRevenue': false, - 'adserverTargeting': { - 'hb_bidder': 'aidem', - 'hb_adid': '3a20ee5dc78c1e', - 'hb_pb': '1.00', - 'hb_size': '300x250', - 'hb_source': 'client', - 'hb_format': 'banner', - 'hb_adomain': 'example.com' - }, - - 'auctionId': '85864730-6cbc-4e56-bc3c-a4a6596dca5b', - 'currency': [ - 'USD' - ], - 'mediaType': 'banner', - 'meta': { - 'advertiserDomains': [ - 'cloudflare.com' - ], - 'ext': { - 'app': { - 'app_bundle': '{{APP_BUNDLE}}', - 'app_id': '{{APP_ID}}', - 'app_name': '{{APP_NAME}}', - 'app_store_url': '{{APP_STORE_URL}}', - 'inventory_source': '{{INVENTORY_SOURCE}}' - }, - 'win_notice_ext': { - 'seatid': '{{SEAT_ID}}' - } - } - }, - 'size': '300x250', - 'params': [ +const SERVER_RESPONSE_VIDEO = { + 'id': '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + 'seatbid': [ { - 'placementId': '13144370', - 'siteId': '23434', - 'publisherId': '7689670753' + 'bid': [ + { + 'id': 'beeswax/aidem', + 'impid': '2705bfae8ea667', + 'price': 0.00875, + 'burl': 'imp_burl', + 'adm': 'creativity_banner', + 'adid': '2:64:162:1001', + 'adomain': [ + 'aidem.com' + ], + 'cid': '64', + 'crid': 'aidem-1001', + 'cat': [], + 'w': 300, + 'h': 250, + 'mtype': 2 + } + ], + 'seat': 'aidemdsp', + 'group': 0 } ], - 'width': 300, - 'height': 250, - 'status': 'rendered', - 'transactionId': 'ce089116-4251-45c3-bdbb-3a03cb13816b', - 'ttl': 300, - 'requestTimestamp': 1666796241007, - 'responseTimestamp': 1666796241021, - metrics: { - getMetrics() { - return { + 'cur': 'USD' +} - } - } - } +const WIN_NOTICE = { + burl: 'burl' } const ERROR_NOTICE = { @@ -512,109 +399,95 @@ describe('Aidem adapter', () => { const requests = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); expect(requests).to.be.an('object'); expect(requests.method).to.be.a('string') - expect(requests.data).to.be.a('string') + expect(requests.data).to.be.a('object') expect(requests.options).to.be.an('object').that.have.a.property('withCredentials') }); it('should have a well formatted banner payload', () => { - const requests = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); - const payload = JSON.parse(requests.data) - expect(payload).to.be.a('object').that.has.all.keys( - 'id', 'imp', 'device', 'cur', 'tz', 'regs', 'site', 'environment', 'at' + const {data} = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + expect(data).to.be.a('object').that.has.all.keys( + 'id', 'imp', 'regs', 'site', 'environment', 'at', 'test' ) - expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) + expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) - expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'banner', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' + expect(data.imp[0]).to.be.a('object').that.has.all.keys( + 'banner', 'id', 'tagId' ) - expect(payload.imp[0].banner).to.be.a('object').that.has.all.keys( + expect(data.imp[0].banner).to.be.a('object').that.has.all.keys( 'format', 'topframe' ) }); - it('should have a well formatted video payload', () => { - const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); - const payload = JSON.parse(requests.data) - expect(payload).to.be.a('object').that.has.all.keys( - 'id', 'imp', 'device', 'cur', 'tz', 'regs', 'site', 'environment', 'at' - ) - expect(payload.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) - - expect(payload.imp[0]).to.be.a('object').that.has.all.keys( - 'video', 'id', 'mediatype', 'imp_ext', 'tid', 'tagid' - ) - expect(payload.imp[0].video).to.be.a('object').that.has.all.keys( - 'format', 'mimes', 'minDuration', 'maxDuration', 'protocols' - ) - }); + if (FEATURES.VIDEO) { + it('should have a well formatted video payload', () => { + const {data} = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + expect(data).to.be.a('object').that.has.all.keys( + 'id', 'imp', 'regs', 'site', 'environment', 'at', 'test' + ) + expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) - it('should have a well formatted bid floor payload if configured', () => { - const validBannerRequests = utils.deepClone(DEFAULT_VALID_BANNER_REQUESTS) - validBannerRequests[0].params.floor = { - value: 1.98, - currency: 'USD' - } - const requests = spec.buildRequests(validBannerRequests, VALID_BIDDER_REQUEST); - const payload = JSON.parse(requests.data) - const { floor } = payload.imp[0] - expect(floor).to.be.a('object').that.has.all.keys( - 'value', 'currency' - ) - }); + expect(data.imp[0]).to.be.a('object').that.has.all.keys( + 'video', 'id', 'tagId' + ) + expect(data.imp[0].video).to.be.a('object').that.has.all.keys( + 'mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h' + ) + }); + } it('should hav wpar keys in environment object', function () { - const requests = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); - const payload = JSON.parse(requests.data) - expect(payload).to.have.property('environment') - expect(payload.environment).to.be.a('object').that.have.property('wpar') - expect(payload.environment.wpar).to.be.a('object').that.has.keys('innerWidth', 'innerHeight') + const {data} = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST); + expect(data).to.have.property('environment') + expect(data.environment).to.be.a('object').that.have.property('wpar') + expect(data.environment.wpar).to.be.a('object').that.has.keys('innerWidth', 'innerHeight') }); }) describe('interpretResponse', () => { it('should return a valid bid array with a banner bid', () => { - const response = utils.deepClone(SERVER_RESPONSE_BANNER) - const interpreted = spec.interpretResponse(response) - expect(interpreted).to.be.a('array').that.has.lengthOf(1) - interpreted.forEach(value => { + const {data} = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST) + const bids = spec.interpretResponse({body: SERVER_RESPONSE_BANNER}, { data }) + expect(bids).to.be.a('array').that.has.lengthOf(1) + bids.forEach(value => { expect(value).to.be.a('object').that.has.all.keys( - 'ad', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'dealId' + 'ad', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'burl', 'seatBidId', 'creative_id' ) }) }); - it('should return a valid bid array with a banner bid', () => { - const response = utils.deepClone(SERVER_RESPONSE_VIDEO) - const interpreted = spec.interpretResponse(response) - expect(interpreted).to.be.a('array').that.has.lengthOf(1) - interpreted.forEach(value => { - expect(value).to.be.a('object').that.has.all.keys( - 'vastUrl', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'dealId' - ) - }) - }); + if (FEATURES.VIDEO) { + it('should return a valid bid array with a video bid', () => { + const {data} = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST) + const bids = spec.interpretResponse({body: SERVER_RESPONSE_VIDEO}, { data }) + expect(bids).to.be.a('array').that.has.lengthOf(1) + bids.forEach(value => { + expect(value).to.be.a('object').that.has.all.keys( + 'vastUrl', 'vastXml', 'playerHeight', 'playerWidth', 'cpm', 'creativeId', 'currency', 'height', 'mediaType', 'meta', 'netRevenue', 'requestId', 'ttl', 'width', 'burl', 'seatBidId', 'creative_id' + ) + }) + }); + } it('should return a valid bid array with netRevenue', () => { - const response = utils.deepClone(SERVER_RESPONSE_BANNER) - response.body.bid[0].isNet = true - const interpreted = spec.interpretResponse(response) - expect(interpreted).to.be.a('array').that.has.lengthOf(1) - expect(interpreted[0].netRevenue).to.be.true - }); - - it('should return an empty bid array if one of seatbid entry is missing price property', () => { - const response = utils.deepClone(SERVER_RESPONSE_BANNER) - delete response.body.bid[0].price - const interpreted = spec.interpretResponse(response) - expect(interpreted).to.be.a('array').that.has.lengthOf(0) - }); - - it('should return an empty bid array if one of seatbid entry is missing adm property', () => { - const response = utils.deepClone(SERVER_RESPONSE_BANNER) - delete response.body.bid[0].adm - const interpreted = spec.interpretResponse(response) - expect(interpreted).to.be.a('array').that.has.lengthOf(0) - }); + const {data} = spec.buildRequests(DEFAULT_VALID_VIDEO_REQUESTS, VALID_BIDDER_REQUEST) + const bids = spec.interpretResponse({body: SERVER_RESPONSE_VIDEO}, { data }) + expect(bids).to.be.a('array').that.has.lengthOf(1) + expect(bids[0].netRevenue).to.be.true + }); + + // it('should return an empty bid array if one of seatbid entry is missing price property', () => { + // const response = utils.deepClone(SERVER_RESPONSE_BANNER) + // delete response.body.bid[0].price + // const interpreted = spec.interpretResponse(response) + // expect(interpreted).to.be.a('array').that.has.lengthOf(0) + // }); + + // it('should return an empty bid array if one of seatbid entry is missing adm property', () => { + // const response = utils.deepClone(SERVER_RESPONSE_BANNER) + // delete response.body.bid[0].adm + // const interpreted = spec.interpretResponse(response) + // expect(interpreted).to.be.a('array').that.has.lengthOf(0) + // }); }) describe('onBidWon', () => { @@ -622,34 +495,29 @@ describe('Aidem adapter', () => { expect(spec.onBidWon).to.exist.and.to.be.a('function') }); - it(`should send a valid bid won notice from web environment`, function () { - spec.onBidWon(WIN_NOTICE_WEB); - expect(server.requests.length).to.equal(1); - }); - - it(`should send a valid bid won notice from app environment`, function () { - spec.onBidWon(WIN_NOTICE_APP); + it(`should send a win notice`, function () { + spec.onBidWon(WIN_NOTICE); expect(server.requests.length).to.equal(1); }); }); - describe('onBidderError', () => { - it(`should exists and type function`, function () { - expect(spec.onBidderError).to.exist.and.to.be.a('function') - }); - - it(`should send a valid error notice`, function () { - spec.onBidderError({ bidderRequest: ERROR_NOTICE }) - expect(server.requests.length).to.equal(1); - const body = JSON.parse(server.requests[0].requestBody) - expect(body).to.be.a('object').that.has.all.keys('message', 'auctionId', 'bidderRequestId', 'url', 'metrics') - // const { bids } = JSON.parse(server.requests[0].requestBody) - // expect(bids).to.be.a('array').that.has.lengthOf(1) - // _each(bids, (bid) => { - // expect(bid).to.be.a('object').that.has.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'transactionId', 'metrics') - // }) - }); - }); + // describe('onBidderError', () => { + // it(`should exists and type function`, function () { + // expect(spec.onBidderError).to.exist.and.to.be.a('function') + // }); + // + // it(`should send a valid error notice`, function () { + // spec.onBidderError({ bidderRequest: ERROR_NOTICE }) + // expect(server.requests.length).to.equal(1); + // const body = JSON.parse(server.requests[0].requestBody) + // expect(body).to.be.a('object').that.has.all.keys('message', 'auctionId', 'bidderRequestId', 'url', 'metrics') + // // const { bids } = JSON.parse(server.requests[0].requestBody) + // // expect(bids).to.be.a('array').that.has.lengthOf(1) + // // _each(bids, (bid) => { + // // expect(bid).to.be.a('object').that.has.all.keys('adUnitCode', 'auctionId', 'bidId', 'bidderRequestId', 'transactionId', 'metrics') + // // }) + // }); + // }); describe('setEndPoints', () => { it(`should exists and type function`, function () { @@ -659,64 +527,35 @@ describe('Aidem adapter', () => { it(`should not modify default endpoints`, function () { const endpoints = setEndPoints() const requestURL = new URL(endpoints.request) - const winNoticeURL = new URL(endpoints.notice.win) - const timeoutNoticeURL = new URL(endpoints.notice.timeout) - const errorNoticeURL = new URL(endpoints.notice.error) - expect(requestURL.host).to.equal('zero.aidemsrv.com') - expect(winNoticeURL.host).to.equal('zero.aidemsrv.com') - expect(timeoutNoticeURL.host).to.equal('zero.aidemsrv.com') - expect(errorNoticeURL.host).to.equal('zero.aidemsrv.com') - - expect(decodeURIComponent(requestURL.pathname)).to.equal('/bid/request') - expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/notice/win') - expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/notice/timeout') - expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/notice/error') + expect(decodeURIComponent(requestURL.pathname)).to.equal('/prebidjs/ortb/v2.6/bid/request') }); it(`should not change request endpoint`, function () { const endpoints = setEndPoints('default') const requestURL = new URL(endpoints.request) - expect(decodeURIComponent(requestURL.pathname)).to.equal('/bid/request') + expect(decodeURIComponent(requestURL.pathname)).to.equal('/prebidjs/ortb/v2.6/bid/request') }); it(`should change to local env`, function () { const endpoints = setEndPoints('local') const requestURL = new URL(endpoints.request) - const winNoticeURL = new URL(endpoints.notice.win) - const timeoutNoticeURL = new URL(endpoints.notice.timeout) - const errorNoticeURL = new URL(endpoints.notice.error) expect(requestURL.host).to.equal('127.0.0.1:8787') - expect(winNoticeURL.host).to.equal('127.0.0.1:8787') - expect(timeoutNoticeURL.host).to.equal('127.0.0.1:8787') - expect(errorNoticeURL.host).to.equal('127.0.0.1:8787') }); it(`should add a path prefix`, function () { const endpoints = setEndPoints('local', '/path') const requestURL = new URL(endpoints.request) - const winNoticeURL = new URL(endpoints.notice.win) - const timeoutNoticeURL = new URL(endpoints.notice.timeout) - const errorNoticeURL = new URL(endpoints.notice.error) - expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/bid/request') - expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/path/notice/win') - expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/path/notice/timeout') - expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/path/notice/error') + expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/prebidjs/ortb/v2.6/bid/request') }); it(`should add a path prefix and change request endpoint`, function () { const endpoints = setEndPoints('local', '/path') const requestURL = new URL(endpoints.request) - const winNoticeURL = new URL(endpoints.notice.win) - const timeoutNoticeURL = new URL(endpoints.notice.timeout) - const errorNoticeURL = new URL(endpoints.notice.error) - expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/bid/request') - expect(decodeURIComponent(winNoticeURL.pathname)).to.equal('/path/notice/win') - expect(decodeURIComponent(timeoutNoticeURL.pathname)).to.equal('/path/notice/timeout') - expect(decodeURIComponent(errorNoticeURL.pathname)).to.equal('/path/notice/error') + expect(decodeURIComponent(requestURL.pathname)).to.equal('/path/prebidjs/ortb/v2.6/bid/request') }); }); @@ -758,8 +597,7 @@ describe('Aidem adapter', () => { coppa: true }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); - const request = JSON.parse(data) - expect(request.regs.coppa_applies).to.equal(true) + expect(data.regs.coppa_applies).to.equal(true) }); it(`should set gdpr to true`, function () { @@ -771,8 +609,7 @@ describe('Aidem adapter', () => { } }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); - const request = JSON.parse(data) - expect(request.regs.gdpr_applies).to.equal(true) + expect(data.regs.gdpr_applies).to.equal(true) }); it(`should set usp_consent string`, function () { @@ -789,8 +626,7 @@ describe('Aidem adapter', () => { } }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); - const request = JSON.parse(data) - expect(request.regs.us_privacy).to.equal('1YYY') + expect(data.regs.us_privacy).to.equal('1YYY') }); it(`should not set usp_consent string`, function () { @@ -807,8 +643,7 @@ describe('Aidem adapter', () => { } }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); - const request = JSON.parse(data) - expect(request.regs.us_privacy).to.undefined + expect(data.regs.us_privacy).to.undefined }); }); }); From 294b84e37a6074c5c4f3988592033cf62feb4030 Mon Sep 17 00:00:00 2001 From: Volodymyr Hunko <98126264+WinnieThePooh2004@users.noreply.github.com> Date: Thu, 14 Sep 2023 09:26:48 +0300 Subject: [PATCH 015/760] Admixer Bid Adapter : removed ortb2 from imp array (#10450) * Update README.md update * removed ortb2 from admixer imp --------- Co-authored-by: AdmixerTech <35560933+AdmixerTech@users.noreply.github.com> Co-authored-by: AdmixerTech Co-authored-by: AdmixerTech --- modules/admixerBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 1006fef631c..6cbc36c1dcd 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -73,6 +73,7 @@ export const spec = { validRequest.forEach((bid) => { let imp = {}; Object.keys(bid).forEach(key => imp[key] = bid[key]); + imp.ortb2 && delete imp.ortb2; payload.imps.push(imp); }); return { From 6438f2749b89234bba70ff265b33214b4861cfa9 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 14 Sep 2023 04:48:11 -0700 Subject: [PATCH 016/760] PBS adapter: log and trigger BIDDER_ERROR events on server errors (#10482) --- modules/prebidServerBidAdapter/index.js | 6 +++++- test/spec/modules/prebidServerBidAdapter_spec.js | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 3cae4497354..776ad99fa25 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -483,7 +483,11 @@ export function PrebidServer() { done(); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, - onError: done, + onError(msg, error) { + logError(`Prebid server call failed: '${msg}'`, error); + bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, {error, bidderRequest})); + done(); + }, onBid: function ({adUnit, bid}) { const metrics = bid.metrics = s2sBidRequest.metrics.fork().renameWith(); metrics.checkpoint('addBidResponse'); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index c823366dbce..4b783803507 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2876,6 +2876,15 @@ describe('S2S Adapter', function () { events.emit.restore(); }); + it('triggers BIDDER_ERROR on server error', () => { + config.setConfig({ s2sConfig: CONFIG }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(400, {}, {}); + BID_REQUESTS.forEach(bidderRequest => { + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.BIDDER_ERROR, sinon.match({bidderRequest})) + }) + }) + // TODO: test dependent on pbjs_api_spec. Needs to be isolated it('does not call addBidResponse and calls done when ad unit not set', function () { config.setConfig({ s2sConfig: CONFIG }); From 61d4f5c138cda109a295b1033a42c3e6a2589040 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Thu, 14 Sep 2023 18:46:37 +0300 Subject: [PATCH 017/760] Adkernel: added mediatypes.* attributes support (#10472) --- modules/adkernelBidAdapter.js | 57 ++++++++++---------- test/spec/modules/adkernelBidAdapter_spec.js | 26 +++++++-- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 6ae19c3efdc..71d5c809e71 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -29,18 +29,19 @@ import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; * * Please contact prebid@adkernel.com and we'll add your adapter as an alias. */ -const VIDEO_PARAMS = ['pos', 'context', 'placement', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration', +const VIDEO_PARAMS = ['pos', 'context', 'placement', 'plcmt', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration', 'startdelay', 'linearity', 'skip', 'skipmin', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackend', 'boxingallowed']; const VIDEO_FPD = ['battr', 'pos']; const NATIVE_FPD = ['battr', 'api']; +const BANNER_PARAMS = ['pos']; const BANNER_FPD = ['btype', 'battr', 'pos', 'api']; const VERSION = '1.6'; const SYNC_IFRAME = 1; const SYNC_IMAGE = 2; -const SYNC_TYPES = Object.freeze({ +const SYNC_TYPES = { 1: 'iframe', 2: 'image' -}); +}; const GVLID = 14; const NATIVE_MODEL = [ @@ -275,33 +276,37 @@ function buildImp(bidRequest, secure) { var mediaType; var sizes = []; - if (deepAccess(bidRequest, 'mediaTypes.banner')) { + if (bidRequest.mediaTypes?.banner) { sizes = getAdUnitSizes(bidRequest); + let pbBanner = bidRequest.mediaTypes.banner; imp.banner = { + ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, BANNER_FPD), + ...getDefinedParamsOrEmpty(pbBanner, BANNER_PARAMS), format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; - populateImpFpd(imp.banner, bidRequest, BANNER_FPD); mediaType = BANNER; - } else if (deepAccess(bidRequest, 'mediaTypes.video')) { - let video = deepAccess(bidRequest, 'mediaTypes.video'); - imp.video = getDefinedParams(video, VIDEO_PARAMS); - populateImpFpd(imp.video, bidRequest, VIDEO_FPD); - if (video.playerSize) { - sizes = video.playerSize[0]; + } else if (bidRequest.mediaTypes?.video) { + let pbVideo = bidRequest.mediaTypes.video; + imp.video = { + ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, VIDEO_FPD), + ...getDefinedParamsOrEmpty(pbVideo, VIDEO_PARAMS) + }; + if (pbVideo.playerSize) { + sizes = pbVideo.playerSize[0]; imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {}); - } else if (video.w && video.h) { - imp.video.w = video.w; - imp.video.h = video.h; + } else if (pbVideo.w && pbVideo.h) { + imp.video.w = pbVideo.w; + imp.video.h = pbVideo.h; } mediaType = VIDEO; - } else if (deepAccess(bidRequest, 'mediaTypes.native')) { + } else if (bidRequest.mediaTypes?.native) { let nativeRequest = buildNativeRequest(bidRequest.mediaTypes.native); imp.native = { + ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, NATIVE_FPD), ver: '1.1', request: JSON.stringify(nativeRequest) }; - populateImpFpd(imp.native, bidRequest, NATIVE_FPD); mediaType = NATIVE; } else { throw new Error('Unsupported bid received'); @@ -316,6 +321,13 @@ function buildImp(bidRequest, secure) { return imp; } +function getDefinedParamsOrEmpty(object, params) { + if (object === undefined) { + return {}; + } + return getDefinedParams(object, params); +} + /** * Builds native request from native adunit */ @@ -345,19 +357,6 @@ function buildNativeRequest(nativeReq) { return request; } -/** - * Populate impression-level FPD from bid request - * @param target {Object} - * @param bidRequest {BidRequest} - * @param props {String[]} - */ -function populateImpFpd(target, bidRequest, props) { - if (bidRequest.ortb2Imp === undefined) { - return; - } - Object.assign(target, getDefinedParams(bidRequest.ortb2Imp, props)); -} - /** * Builds image asset request */ diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 90200a801c5..ac2e3785780 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -15,11 +15,13 @@ describe('Adkernel adapter', function () { auctionId: 'auc-001', mediaTypes: { banner: { - sizes: [[300, 250], [300, 200]] + sizes: [[300, 250], [300, 200]], + pos: 1 } }, ortb2Imp: { - battr: [6, 7, 9] + battr: [6, 7, 9], + pos: 2 } }, bid2_zone2 = { bidder: 'adkernel', @@ -103,7 +105,11 @@ describe('Adkernel adapter', function () { video: { context: 'instream', playerSize: [[640, 480]], - api: [1, 2] + api: [1, 2], + placement: 1, + plcmt: 1, + skip: 1, + pos: 1 } }, adUnitCode: 'ad-unit-1' @@ -346,6 +352,11 @@ describe('Adkernel adapter', function () { expect(bidRequest.imp[0].banner.battr).to.be.eql([6, 7, 9]); }); + it('should respect mediatypes attributes over FPD', function() { + expect(bidRequest.imp[0].banner).to.have.property('pos'); + expect(bidRequest.imp[0].banner.pos).to.be.eql(1); + }); + it('shouldn\'t contain gdpr nor ccpa information for default request', function () { let [_, bidRequests] = buildRequest([bid1_zone1]); expect(bidRequests[0]).to.not.have.property('regs'); @@ -438,8 +449,13 @@ describe('Adkernel adapter', function () { }); it('should have openrtb video impression parameters', function() { - expect(bidRequests[0].imp[0].video).to.have.property('api'); - expect(bidRequests[0].imp[0].video.api).to.be.eql([1, 2]); + let video = bidRequests[0].imp[0].video; + expect(video).to.have.property('api'); + expect(video.api).to.be.eql([1, 2]); + expect(video.placement).to.be.eql(1); + expect(video.plcmt).to.be.eql(1); + expect(video.skip).to.be.eql(1); + expect(video.pos).to.be.eql(1); }); }); From 80754550c118bce24c9640a9b08f6c5bc9c1dfb0 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 14 Sep 2023 10:09:21 -0700 Subject: [PATCH 018/760] Core: improve and fix bid timeout logic (#10379) * Clean up timeout logic: doCallbacksIfTimedOut * Core: simplify bid timeouts, fix s2s timeout edge case * use more descriptive varnames --- modules/adpod.js | 4 - src/adapterManager.js | 37 ++++--- src/auction.js | 70 +++---------- src/utils.js | 3 +- test/spec/auctionmanager_spec.js | 171 ++++++++++++++++++++----------- test/spec/modules/adpod_spec.js | 3 - 6 files changed, 146 insertions(+), 142 deletions(-) diff --git a/modules/adpod.js b/modules/adpod.js index 4ab8e4e5ab9..0318785d55e 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -28,7 +28,6 @@ import { import { addBidToAuction, AUCTION_IN_PROGRESS, - doCallbacksIfTimedout, getPriceByGranularity, getPriceGranularity } from '../src/auction.js'; @@ -212,9 +211,6 @@ function firePrebidCacheCall(auctionInstance, bidList, afterBidAdded) { store(bidList, function (error, cacheIds) { if (error) { logWarn(`Failed to save to the video cache: ${error}. Video bid(s) must be discarded.`); - for (let i = 0; i < bidList.length; i++) { - doCallbacksIfTimedout(auctionInstance, bidList[i]); - } } else { for (let i = 0; i < cacheIds.length; i++) { // when uuid in response is empty string then the key already existed, so this bid wasn't cached diff --git a/src/adapterManager.js b/src/adapterManager.js index 6513d41ca3c..554fde21734 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -380,13 +380,13 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request return; } - let [clientBidRequests, serverBidRequests] = bidRequests.reduce((partitions, bidRequest) => { + let [clientBidderRequests, serverBidderRequests] = bidRequests.reduce((partitions, bidRequest) => { partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC)].push(bidRequest); return partitions; }, [[], []]); var uniqueServerBidRequests = []; - serverBidRequests.forEach(serverBidRequest => { + serverBidderRequests.forEach(serverBidRequest => { var index = -1; for (var i = 0; i < uniqueServerBidRequests.length; ++i) { if (serverBidRequest.uniquePbsTid === uniqueServerBidRequests[i].uniquePbsTid) { @@ -413,14 +413,17 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request let uniquePbsTid = uniqueServerBidRequests[counter].uniquePbsTid; let adUnitsS2SCopy = uniqueServerBidRequests[counter].adUnitsS2SCopy; - let uniqueServerRequests = serverBidRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); + let uniqueServerRequests = serverBidderRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { let s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); - return doneCb.bind(bidRequest); + return function () { + onTimelyResponse(bidRequest.bidderRequestId); + doneCbs.apply(bidRequest, arguments); + } }); const bidders = getBidderCodes(s2sBidRequest.ad_units).filter((bidder) => adaptersServerSide.includes(bidder)); @@ -435,7 +438,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request // make bid requests s2sAdapter.callBids( s2sBidRequest, - serverBidRequests, + serverBidderRequests, addBidResponse, () => doneCbs.forEach(done => done()), s2sAjax @@ -449,35 +452,35 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request }); // handle client adapter requests - clientBidRequests.forEach(bidRequest => { - bidRequest.start = timestamp(); + clientBidderRequests.forEach(bidderRequest => { + bidderRequest.start = timestamp(); // TODO : Do we check for bid in pool from here and skip calling adapter again ? - const adapter = _bidderRegistry[bidRequest.bidderCode]; - config.runWithBidder(bidRequest.bidderCode, () => { + const adapter = _bidderRegistry[bidderRequest.bidderCode]; + config.runWithBidder(bidderRequest.bidderCode, () => { logMessage(`CALLING BIDDER`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); }); let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { - request: requestCallbacks.request.bind(null, bidRequest.bidderCode), + request: requestCallbacks.request.bind(null, bidderRequest.bidderCode), done: requestCallbacks.done } : undefined); - const adapterDone = doneCb.bind(bidRequest); + const adapterDone = doneCb.bind(bidderRequest); try { config.runWithBidder( - bidRequest.bidderCode, + bidderRequest.bidderCode, bind.call( adapter.callBids, adapter, - bidRequest, + bidderRequest, addBidResponse, adapterDone, ajax, - onTimelyResponse, - config.callbackWithBidder(bidRequest.bidderCode) + () => onTimelyResponse(bidderRequest.bidderRequestId), + config.callbackWithBidder(bidderRequest.bidderCode) ) ); } catch (e) { - logError(`${bidRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest}); + logError(`${bidderRequest.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`, {e, bidRequest: bidderRequest}); adapterDone(); } }); diff --git a/src/auction.js b/src/auction.js index 54621669c59..c3712c0a4df 100644 --- a/src/auction.js +++ b/src/auction.js @@ -62,7 +62,6 @@ import { adUnitsFilter, bind, deepAccess, - flatten, generateUUID, getValue, isEmpty, @@ -142,7 +141,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a const _adUnitCodes = adUnitCodes; const _auctionId = auctionId || generateUUID(); const _timeout = cbTimeout; - const _timelyBidders = new Set(); + const _timelyRequests = new Set(); const done = defer(); let _bidsRejected = []; let _callback = callback; @@ -152,7 +151,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a let _winningBids = []; let _auctionStart; let _auctionEnd; - let _timer; + let _timeoutTimer; let _auctionStatus; let _nonBids = []; @@ -183,24 +182,20 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } function startAuctionTimer() { - const timedOut = true; - const timeoutCallback = executeCallback.bind(null, timedOut); - let timer = setTimeout(timeoutCallback, _timeout); - _timer = timer; + _timeoutTimer = setTimeout(() => executeCallback(true), _timeout); } - function executeCallback(timedOut, cleartimer) { - // clear timer when done calls executeCallback - if (cleartimer) { - clearTimeout(_timer); + function executeCallback(timedOut) { + if (!timedOut) { + clearTimeout(_timeoutTimer); } if (_auctionEnd === undefined) { - let timedOutBidders = []; + let timedOutRequests = []; if (timedOut) { logMessage(`Auction ${_auctionId} timedOut`); - timedOutBidders = getTimedOutBids(_bidderRequests, _timelyBidders); - if (timedOutBidders.length) { - events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutBidders); + timedOutRequests = _bidderRequests.filter(rq => !_timelyRequests.has(rq.bidderRequestId)).flatMap(br => br.bids) + if (timedOutRequests.length) { + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutRequests); } } @@ -226,8 +221,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a logError('Error executing bidsBackHandler', null, e); } finally { // Calling timed out bidders - if (timedOutBidders.length) { - adapterManager.callTimedOutBidders(adUnits, timedOutBidders, _timeout); + if (timedOutRequests.length) { + adapterManager.callTimedOutBidders(adUnits, timedOutRequests, _timeout); } // Only automatically sync if the publisher has not chosen to "enableOverride" let userSyncConfig = config.getConfig('userSync') || {}; @@ -245,11 +240,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a // when all bidders have called done callback atleast once it means auction is complete logInfo(`Bids Received for Auction with id: ${_auctionId}`, _bidsReceived); _auctionStatus = AUCTION_COMPLETED; - executeCallback(false, true); + executeCallback(false); } - function onTimelyResponse(bidderCode) { - _timelyBidders.add(bidderCode); + function onTimelyResponse(bidderRequestId) { + _timelyRequests.add(bidderRequestId); } function callBids() { @@ -387,7 +382,6 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a addBidReceived, addBidRejected, addNoBid, - executeCallback, callBids, addWinningBid, setBidTargeting, @@ -557,12 +551,6 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM } } -export function doCallbacksIfTimedout(auctionInstance, bidResponse) { - if (bidResponse.timeToRespond > auctionInstance.getTimeout() + config.getConfig('timeoutBuffer')) { - auctionInstance.executeCallback(true); - } -} - // Add a bid to the auction. export function addBidToAuction(auctionInstance, bidResponse) { setupBidTargeting(bidResponse); @@ -570,8 +558,6 @@ export function addBidToAuction(auctionInstance, bidResponse) { useMetrics(bidResponse.metrics).timeSince('addBidResponse', 'addBidResponse.total'); auctionInstance.addBidReceived(bidResponse); events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); - - doCallbacksIfTimedout(auctionInstance, bidResponse); } // Video bids may fail if the cache is down, or there's trouble on the network. @@ -618,16 +604,11 @@ const _storeInCache = (batch) => { const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; if (error) { logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - - doCallbacksIfTimedout(auctionInstance, bidResponse); } else { if (cacheId.uuid === '') { logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); - - doCallbacksIfTimedout(auctionInstance, bidResponse); } else { bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); } @@ -1017,24 +998,3 @@ function groupByPlacement(bidsByPlacement, bid) { bidsByPlacement[bid.adUnitCode].bids.push(bid); return bidsByPlacement; } - -/** - * Returns a list of bids that we haven't received a response yet where the bidder did not call done - * @param {BidRequest[]} bidderRequests List of bids requested for auction instance - * @param {Set} timelyBidders Set of bidders which responded in time - * - * @typedef {Object} TimedOutBid - * @property {string} bidId The id representing the bid - * @property {string} bidder The string name of the bidder - * @property {string} adUnitCode The code used to uniquely identify the ad unit on the publisher's page - * @property {string} auctionId The id representing the auction - * - * @return {Array} List of bids that Prebid hasn't received a response for - */ -function getTimedOutBids(bidderRequests, timelyBidders) { - const timedOutBids = bidderRequests - .map(bid => (bid.bids || []).filter(bid => !timelyBidders.has(bid.bidder))) - .reduce(flatten, []); - - return timedOutBids; -} diff --git a/src/utils.js b/src/utils.js index 2ce4f9cc8bb..5ad8b5031bd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -925,8 +925,7 @@ export function isValidMediaTypes(mediaTypes) { export function getUserConfiguredParams(adUnits, adUnitCode, bidder) { return adUnits .filter(adUnit => adUnit.code === adUnitCode) - .map((adUnit) => adUnit.bids) - .reduce(flatten, []) + .flatMap((adUnit) => adUnit.bids) .filter((bidderData) => bidderData.bidder === bidder) .map((bidderData) => bidderData.params || {}); } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index d8598dc2063..be4a7f819cd 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -5,7 +5,7 @@ import { adjustBids, getMediaTypeGranularity, getPriceByGranularity, - addBidResponse + addBidResponse, resetAuctionState } from 'src/auction.js'; import CONSTANTS from 'src/constants.json'; import * as auctionModule from 'src/auction.js'; @@ -23,6 +23,7 @@ import {AuctionIndex} from '../../src/auctionIndex.js'; import {expect} from 'chai'; import {deepClone} from '../../src/utils.js'; import { IMAGE as ortbNativeRequest } from 'src/native.js'; +import {PrebidServer} from '../../modules/prebidServerBidAdapter/index.js'; var assert = require('assert'); @@ -48,6 +49,7 @@ function mockBid(opts) { let bidderCode = opts && opts.bidderCode; return { + adUnitCode: opts?.adUnitCode || ADUNIT_CODE, 'ad': 'creative', 'cpm': '1.99', 'width': 300, @@ -68,6 +70,11 @@ function mockBid(opts) { transactionId: this.transactionId, auctionId: this.auctionId } + }, + _ctx: { + adUnits: opts?.adUnits, + src: opts?.src, + uniquePbsTid: opts?.uniquePbsTid, } }; } @@ -96,6 +103,9 @@ function mockBidRequest(bid, opts) { 'bidderCode': bidderCode || bid.bidderCode, 'auctionId': opts && opts.auctionId, 'bidderRequestId': requestId, + src: bid?._ctx?.src, + adUnitsS2SCopy: bid?._ctx?.src === CONSTANTS.S2S.SRC ? bid?._ctx?.adUnits : undefined, + uniquePbsTid: bid?._ctx?.src === CONSTANTS.S2S.SRC ? bid?._ctx?.uniquePbsTid : undefined, 'bids': [ { 'bidder': bidderCode || bid.bidderCode, @@ -108,7 +118,8 @@ function mockBidRequest(bid, opts) { 'bidId': bid.requestId, 'bidderRequestId': requestId, 'auctionId': opts && opts.auctionId, - 'mediaTypes': mediaType + 'mediaTypes': mediaType, + src: bid?._ctx?.src } ], 'auctionStart': 1505250713622, @@ -160,6 +171,7 @@ describe('auctionmanager.js', function () { indexAuctions = []; indexStub = sinon.stub(auctionManager, 'index'); indexStub.get(() => new AuctionIndex(() => indexAuctions)); + resetAuctionState(); }); afterEach(() => { @@ -915,6 +927,11 @@ describe('auctionmanager.js', function () { beforeEach(function () { ajaxStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockAjaxBuilder); adUnits = [{ + mediaTypes: { + banner: { + sizes: [] + } + }, code: ADUNIT_CODE, transactionId: ADUNIT_CODE, bids: [ @@ -1150,12 +1167,19 @@ describe('auctionmanager.js', function () { }); describe('when auction timeout is 20', function () { - let eventsEmitSpy; + let eventsEmitSpy, auctionDone; - function setupBids(auctionId) { - bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; - let bidRequests = bids.map(bid => mockBidRequest(bid, {auctionId})); + function respondToRequest(requestIndex) { + server.requests[requestIndex].respond(200, {}, 'response body'); + } + + function runAuction() { + let bidRequests = bids.map(bid => mockBidRequest(bid, {auctionId: auction.getAuctionId()})); makeRequestsStub.returns(bidRequests); + return new Promise((resolve) => { + auctionDone = resolve; + auction.callBids(); + }) } beforeEach(function () { @@ -1164,102 +1188,127 @@ describe('auctionmanager.js', function () { transactionId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, + {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, ] }]; adUnitCodes = [ADUNIT_CODE]; eventsEmitSpy = sinon.spy(events, 'emit'); + bids = [mockBid(), mockBid({ bidderCode: BIDDER_CODE1 })]; + const spec1 = mockBidder(BIDDER_CODE, [bids[0]]); + registerBidder(spec1); + const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]); + registerBidder(spec2); + auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: () => auctionDone(), cbTimeout: 20}); }); + afterEach(function () { events.emit.restore(); }); - function respondToRequest(requestIndex) { - server.requests[requestIndex].respond(200, {}, 'response body'); - } - - it('resolves .end on timeout', (done) => { - registerBidder(mockBidder(BIDDER_CODE, [bids[0]])); - registerBidder(mockBidder(BIDDER_CODE1, [bids[1]])); + it('resolves .end on timeout', () => { let endResolved = false; - function callback() { - expect(endResolved).to.be.true; - done() - } - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback, cbTimeout: 20}); - setupBids(auction.getAuctionId()); - auction.callBids(); - respondToRequest(0); auction.end.then(() => { endResolved = true; }) + const pm = runAuction().then(() => { + expect(endResolved).to.be.true; + }); + respondToRequest(0); + return pm; }) - it('should emit BID_TIMEOUT and AUCTION_END for timed out bids', function (done) { - const spec1 = mockBidder(BIDDER_CODE, [bids[0]]); - registerBidder(spec1); - const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]); - registerBidder(spec2); - - function auctionCallback() { + it('should emit BID_TIMEOUT and AUCTION_END for timed out bids', function () { + const pm = runAuction().then(() => { const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0]; const timedOutBids = bidTimeoutCall.args[1]; assert.equal(timedOutBids.length, 1); assert.equal(timedOutBids[0].bidder, BIDDER_CODE1); // Check that additional properties are available - assert.equal(timedOutBids[0].params.placementId, 'id'); + assert.equal(timedOutBids[0].params[0].placementId, 'id'); const auctionEndCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.AUCTION_END).getCalls()[0]; const auctionProps = auctionEndCall.args[1]; assert.equal(auctionProps.adUnits, adUnits); assert.equal(auctionProps.timeout, 20); assert.equal(auctionProps.auctionStatus, AUCTION_COMPLETED) - done(); - } - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); - setupBids(auction.getAuctionId()); - - auction.callBids(); + }); respondToRequest(0); + return pm; }); - it('should NOT emit BID_TIMEOUT when all bidders responded in time', function (done) { - const spec1 = mockBidder(BIDDER_CODE, [bids[0]]); - registerBidder(spec1); - const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]); - registerBidder(spec2); - - function respondToRequest(requestIndex) { - server.requests[requestIndex].respond(200, {}, 'response body'); - } - function auctionCallback() { + it('should NOT emit BID_TIMEOUT when all bidders responded in time', function () { + const pm = runAuction().then(() => { assert.ok(eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).notCalled, 'did not emit event BID_TIMEOUT'); - done(); - } - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); - setupBids(auction.getAuctionId()); - auction.callBids(); + }); respondToRequest(0); respondToRequest(1); + return pm; }); - it('should NOT emit BID_TIMEOUT for bidders which responded in time but with an empty bid', function (done) { - const spec1 = mockBidder(BIDDER_CODE, []); - registerBidder(spec1); - const spec2 = mockBidder(BIDDER_CODE1, []); - registerBidder(spec2); - function auctionCallback() { + it('should NOT emit BID_TIMEOUT for bidders which responded in time but with an empty bid', function () { + const pm = runAuction().then(() => { const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0]; const timedOutBids = bidTimeoutCall.args[1]; assert.equal(timedOutBids.length, 1); assert.equal(timedOutBids[0].bidder, BIDDER_CODE1); - done(); - } - auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: auctionCallback, cbTimeout: 20}); - setupBids(auction.getAuctionId()); - auction.callBids(); + }); respondToRequest(0); + return pm; }); + + it('should NOT emit BID_TIMEOUT for bidders that replied through S2S', () => { + adapterManager.registerBidAdapter(new PrebidServer(), 'pbs'); + config.setConfig({ + s2sConfig: [{ + accountId: '1', + enabled: true, + defaultVendor: 'appnexus', + bidders: ['mock-s2s-1'], + adapter: 'pbs' + }, { + accountId: '1', + enabled: true, + defaultVendor: 'rubicon', + bidders: ['mock-s2s-2'], + adapter: 'pbs' + }] + }) + adUnits[0].bids.push({bidder: 'mock-s2s-1'}, {bidder: 'mock-s2s-2'}) + const s2sAdUnits = deepClone(adUnits); + bids.unshift( + mockBid({bidderCode: 'mock-s2s-1', src: CONSTANTS.S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '1'}), + mockBid({bidderCode: 'mock-s2s-2', src: CONSTANTS.S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '2'}) + ); + Object.assign(s2sAdUnits[0], { + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bids: [ + { + bidder: 'mock-s2s-1', + bid_id: bids[0].requestId + }, + { + bidder: 'mock-s2s-2', + bid_id: bids[1].requestId + } + ] + }) + + const pm = runAuction().then(() => { + const toBids = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0].args[1] + expect(toBids.map(bid => bid.bidder)).to.eql([ + 'mock-s2s-2', + BIDDER_CODE, + BIDDER_CODE1, + ]) + }); + respondToRequest(1); + return pm; + }) }); }); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index a6164f919ef..14e530c1a9b 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -47,7 +47,6 @@ describe('adpod.js', function () { addBidToAuctionStub = sinon.stub(auction, 'addBidToAuction').callsFake(function (auctionInstance, bid) { auctionBids.push(bid); }); - doCallbacksIfTimedoutStub = sinon.stub(auction, 'doCallbacksIfTimedout'); clock = sinon.useFakeTimers(); config.setConfig({ cache: { @@ -61,7 +60,6 @@ describe('adpod.js', function () { logWarnStub.restore(); logInfoStub.restore(); addBidToAuctionStub.restore(); - doCallbacksIfTimedoutStub.restore(); clock.restore(); config.resetConfig(); auctionBids = []; @@ -633,7 +631,6 @@ describe('adpod.js', function () { callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - expect(doCallbacksIfTimedoutStub.calledTwice).to.equal(true); expect(logWarnStub.calledOnce).to.equal(true); expect(auctionBids.length).to.equal(0); }); From 9e4ed1cea73f246dedd5d40f06beb6f202d0f044 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 14 Sep 2023 17:42:12 +0000 Subject: [PATCH 019/760] Prebid 8.14.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0de52e2d6d..f47dff9abee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.14.0-pre", + "version": "8.14.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 577f10f4c17..049ff5298bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.14.0-pre", + "version": "8.14.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c3409af97c8988850d0b0236c25dcf6013cc77cf Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 14 Sep 2023 17:42:13 +0000 Subject: [PATCH 020/760] Increment version to 8.15.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f47dff9abee..dffd7c0b837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.14.0", + "version": "8.15.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 049ff5298bd..e2cc34a23ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.14.0", + "version": "8.15.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5fba72dc4d0497078be8d0eff52b08b7894031a1 Mon Sep 17 00:00:00 2001 From: Maksym Pavliv <73064267+PyjamaWarrior@users.noreply.github.com> Date: Thu, 14 Sep 2023 20:43:18 +0300 Subject: [PATCH 021/760] Axis Bid Adapter : initial release (#9684) * New Adapter: Axis * minor improvements * fixed coppa and tmax * updated iabCat --------- Co-authored-by: Maksym Pavliv --- modules/axisBidAdapter.js | 210 ++++++++++++ modules/axisBidAdapter.md | 83 +++++ test/spec/modules/axisBidAdapter_spec.js | 414 +++++++++++++++++++++++ 3 files changed, 707 insertions(+) create mode 100644 modules/axisBidAdapter.js create mode 100644 modules/axisBidAdapter.md create mode 100644 test/spec/modules/axisBidAdapter_spec.js diff --git a/modules/axisBidAdapter.js b/modules/axisBidAdapter.js new file mode 100644 index 00000000000..8d7f2dd04fd --- /dev/null +++ b/modules/axisBidAdapter.js @@ -0,0 +1,210 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'axis'; +const AD_URL = 'https://prebid.axis-marketplace.com/pbjs'; +const SYNC_URL = 'https://cs.axis-marketplace.com'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { integration, token } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + integration, + token, + bidId, + schain, + bidfloor + }; + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.pos = mediaTypes[BANNER].pos; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.pos = mediaTypes[VIDEO].pos; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + placement.context = mediaTypes[VIDEO].context; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (e) { + logError(e); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && params.integration && params.token); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + iabCat: deepAccess(bidderRequest, 'ortb2.site.cat'), + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: bidderRequest.timeout || 3000, + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + } +}; + +registerBidder(spec); diff --git a/modules/axisBidAdapter.md b/modules/axisBidAdapter.md new file mode 100644 index 00000000000..d1625a56176 --- /dev/null +++ b/modules/axisBidAdapter.md @@ -0,0 +1,83 @@ +# Overview + +``` +Module Name: Axis Bidder Adapter +Module Type: Axis Bidder Adapter +Maintainer: help@axis-marketplace.com +``` + +# Description + +Connects to Axis exchange for bids. +Axis bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + pos: 1 + } + }, + bids: [ + { + bidder: 'axis', + params: { + integration: '000000', + token: '000000' + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + minduration: 5, + maxduration: 60, + pos: 1 + } + }, + bids: [ + { + bidder: 'axis', + params: { + integration: '000000', + token: '000000' + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'axis', + params: { + integration: '000000', + token: '000000' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js new file mode 100644 index 00000000000..083f05f5c0a --- /dev/null +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -0,0 +1,414 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/axisBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'axis' + +describe('AxisBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + pos: 1 + } + }, + params: { + integration: '000000', + token: '000000' + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60, + pos: 1 + } + }, + params: { + integration: '000000', + token: '000000' + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + integration: '000000', + token: '000000' + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + }, + ortb2: { + site: { + cat: ['IAB24'] + } + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://prebid.axis-marketplace.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'iabCat', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.iabCat).to.have.lengthOf(1); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.integration).to.be.a('string'); + expect(placement.token).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + expect(placement.pos).to.be.within(0, 7); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + expect(placement.pos).to.be.within(0, 7); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.property('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + width: 300, + height: 250, + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta', 'width', 'height'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.property('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.property('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function() { + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true, + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.axis-marketplace.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({}, {}, {}, { + consentString: '1---' + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.axis-marketplace.com/image?pbjs=1&ccpa=1---&coppa=0') + }); + }); +}); From 411844f5d895b56e26fe244c497495cd5c66aff8 Mon Sep 17 00:00:00 2001 From: ccorbo Date: Thu, 14 Sep 2023 14:01:17 -0400 Subject: [PATCH 022/760] feat:pass pairid [PB-1815] (#10487) Co-authored-by: Chris Corbo --- modules/ixBidAdapter.js | 4 +++- test/spec/modules/ixBidAdapter_spec.js | 16 +++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index d083fb46798..50595152b23 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -79,6 +79,7 @@ const SOURCE_RTI_MAPPING = { 'intimatemerger.com': '', '33across.com': '', 'liveintent.indexexchange.com': '', + 'google.com': '' }; const PROVIDERS = [ 'britepoolid', @@ -89,7 +90,8 @@ const PROVIDERS = [ 'connectid', 'tapadId', 'quantcastId', - 'pubProvidedId' + 'pubProvidedId', + 'pairId' ]; const REQUIRED_VIDEO_PARAMS = ['mimes', 'minduration', 'maxduration']; // note: protocol/protocols is also reqd const VIDEO_PARAMS_ALLOW_LIST = [ diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index eee0335524d..853215d95ad 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -759,7 +759,8 @@ describe('IndexexchangeAdapter', function () { // similar to uid2, but id5's getValue takes .uid id5id: { uid: 'testid5id' }, // ID5 imuid: 'testimuid', - '33acrossId': { envelope: 'v1.5fs.1000.fjdiosmclds' } + '33acrossId': { envelope: 'v1.5fs.1000.fjdiosmclds' }, + pairId: {envelope: 'testpairId'} }; const DEFAULT_USERID_PAYLOAD = [ @@ -818,6 +819,11 @@ describe('IndexexchangeAdapter', function () { uids: [{ id: DEFAULT_USERID_DATA['33acrossId'].envelope }] + }, { + source: 'google.com', + uids: [{ + id: DEFAULT_USERID_DATA['pairId'].envelope + }] } ]; @@ -1224,7 +1230,7 @@ describe('IndexexchangeAdapter', function () { const payload = extractPayload(request[0]); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(8); + expect(payload.user.eids).to.have.lengthOf(9); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); @@ -1412,7 +1418,7 @@ describe('IndexexchangeAdapter', function () { cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(8); + expect(payload.user.eids).to.have.lengthOf(9); expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); @@ -1545,7 +1551,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(10); + expect(payload.user.eids).to.have.lengthOf(11); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); @@ -1587,7 +1593,7 @@ describe('IndexexchangeAdapter', function () { }); const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(9); + expect(payload.user.eids).to.have.lengthOf(10); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); }); From ca555d334117abe97a6082291a873ac171147174 Mon Sep 17 00:00:00 2001 From: Cadent Aperture MX <43830380+EMXDigital@users.noreply.github.com> Date: Thu, 14 Sep 2023 14:02:34 -0400 Subject: [PATCH 023/760] Cadent Aperture MX Bid Adapter: address auctionId/transactionId leak (#10485) --- modules/cadentApertureMXBidAdapter.js | 4 ++-- .../modules/cadentApertureMXBidAdapter_spec.js | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index 32c0a4e4643..079ca592160 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -277,7 +277,7 @@ export const spec = { let isVideo = !!bid.mediaTypes.video; let data = { id: bid.bidId, - tid: bid.transactionId, + tid: bid.ortb2Imp?.ext?.tid, tagid, secure }; @@ -297,7 +297,7 @@ export const spec = { }); let cadentData = { - id: bidderRequest.auctionId, + id: bidderRequest.auctionId ?? bidderRequest.bidderRequestId, imp: cadentImps, device, site, diff --git a/test/spec/modules/cadentApertureMXBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js index 202659641f9..3ccb5405552 100644 --- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -237,6 +237,11 @@ describe('cadent_aperture_mx Adapter', function () { 'bidId': '30b31c2501de1e', 'auctionId': 'e19f1eff-8b27-42a6-888d-9674e5a6130c', 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'ortb2Imp': { + 'ext': { + 'tid': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ed', + }, + }, }] }; let request = spec.buildRequests(bidderRequest.bids, bidderRequest); @@ -297,12 +302,22 @@ describe('cadent_aperture_mx Adapter', function () { expect(data.id).to.equal(bidderRequest.auctionId); expect(data.imp.length).to.equal(1); expect(data.imp[0].id).to.equal('30b31c2501de1e'); - expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ec'); + expect(data.imp[0].tid).to.equal('d7b773de-ceaa-484d-89ca-d9f51b8d61ed'); expect(data.imp[0].tagid).to.equal('25251'); expect(data.imp[0].secure).to.equal(0); expect(data.imp[0].vastXml).to.equal(undefined); }); + it('populates id even when auctionId is not available', function () { + // addressing https://github.com/prebid/Prebid.js/issues/9781 + bidderRequest.auctionId = null; + request = spec.buildRequests(bidderRequest.bids, bidderRequest); + + const data = JSON.parse(request.data); + expect(data.id).not.to.be.null; + expect(data.id).not.to.equal(bidderRequest.auctionId); + }); + it('properly sends site information and protocol', function () { request = spec.buildRequests(bidderRequest.bids, bidderRequest); request = JSON.parse(request.data); From 7764e5380befe7967a367fc7cf725ef5fea6c4ae Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 14 Sep 2023 11:09:41 -0700 Subject: [PATCH 024/760] fledgeForGpt: provide `bidfloor` in auction signals (#10393) * fledgeForGpt: delay slot config to end of auction * currency utils * reducers * Set `prebid.bidfloor` and `prebid.bidfloorcur` in fledge auction signals --------- Co-authored-by: Patrick McCann --- libraries/currencyUtils/currency.js | 31 + modules/fledgeForGpt.js | 73 +- modules/prebidServerBidAdapter/index.js | 2 +- .../prebidServerBidAdapter/ortbConverter.js | 12 +- modules/priceFloors.js | 6 +- src/adapters/bidderFactory.js | 5 +- src/prebid.js | 2 +- src/targeting.js | 3 +- src/utils.js | 20 - src/utils/currency.js | 16 - src/utils/reducers.js | 44 + test/spec/libraries/currencyUtils_spec.js | 113 + test/spec/modules/fledgeForGpt_spec.js | 429 +++ test/spec/modules/fledge_spec.js | 282 -- test/spec/modules/multibid_spec.js | 21 +- .../modules/prebidServerBidAdapter_spec.js | 8 +- test/spec/unit/core/bidderFactory_spec.js | 2311 +++++++++-------- test/spec/unit/core/targeting_spec.js | 20 +- test/spec/unit/utils/reducers_spec.js | 124 + test/spec/utils_spec.js | 67 +- 20 files changed, 2000 insertions(+), 1589 deletions(-) create mode 100644 libraries/currencyUtils/currency.js delete mode 100644 src/utils/currency.js create mode 100644 src/utils/reducers.js create mode 100644 test/spec/libraries/currencyUtils_spec.js create mode 100644 test/spec/modules/fledgeForGpt_spec.js delete mode 100644 test/spec/modules/fledge_spec.js create mode 100644 test/spec/unit/utils/reducers_spec.js diff --git a/libraries/currencyUtils/currency.js b/libraries/currencyUtils/currency.js new file mode 100644 index 00000000000..924f8f200d8 --- /dev/null +++ b/libraries/currencyUtils/currency.js @@ -0,0 +1,31 @@ +import {getGlobal} from '../../src/prebidGlobal.js'; +import {keyCompare} from '../../src/utils/reducers.js'; + +/** + * Attempt to convert `amount` from the currency `fromCur` to the currency `toCur`. + * + * By default, when the conversion is not possible (currency module not present or + * throwing errors), the amount is returned unchanged. This behavior can be + * toggled off with bestEffort = false. + */ +export function convertCurrency(amount, fromCur, toCur, bestEffort = true) { + if (fromCur === toCur) return amount; + let result = amount; + try { + result = getGlobal().convertCurrency(amount, fromCur, toCur); + } catch (e) { + if (!bestEffort) throw e; + } + return result; +} + +export function currencyNormalizer(toCurrency = null, bestEffort = true, convert = convertCurrency) { + return function (amount, currency) { + if (toCurrency == null) toCurrency = currency; + return convert(amount, currency, toCurrency, bestEffort); + } +} + +export function currencyCompare(get = (obj) => [obj.cpm, obj.currency], normalize = currencyNormalizer()) { + return keyCompare(obj => normalize.apply(null, get(obj))) +} diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index e592cd38044..eddb7424f92 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -4,10 +4,15 @@ */ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; -import { getGptSlotForAdUnitCode, logInfo, logWarn } from '../src/utils.js'; +import {deepSetValue, getGptSlotForAdUnitCode, logInfo, logWarn, mergeDeep} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; +import * as events from '../src/events.js' +import CONSTANTS from '../src/constants.json'; +import {currencyCompare} from '../libraries/currencyUtils/currency.js'; +import {maximum, minimum} from '../src/utils/reducers.js'; const MODULE = 'fledgeForGpt' +const PENDING = {}; export let isEnabled = false; @@ -21,6 +26,8 @@ export function init(cfg) { if (!isEnabled) { getHook('addComponentAuction').before(addComponentAuctionHook); getHook('makeBidRequests').after(markForFledge); + events.on(CONSTANTS.EVENTS.AUCTION_INIT, onAuctionInit); + events.on(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); isEnabled = true; } logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg); @@ -28,28 +35,74 @@ export function init(cfg) { if (isEnabled) { getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); getHook('makeBidRequests').getHooks({hook: markForFledge}).remove() + events.off(CONSTANTS.EVENTS.AUCTION_INIT, onAuctionInit); + events.off(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); isEnabled = false; } logInfo(`${MODULE} disabled`, cfg); } } -export function addComponentAuctionHook(next, adUnitCode, componentAuctionConfig) { - const seller = componentAuctionConfig.seller; +function setComponentAuction(adUnitCode, auctionConfigs) { const gptSlot = getGptSlotForAdUnitCode(adUnitCode); if (gptSlot && gptSlot.setConfig) { gptSlot.setConfig({ - componentAuction: [{ - configKey: seller, - auctionConfig: componentAuctionConfig - }] + componentAuction: auctionConfigs.map(cfg => ({ + configKey: cfg.seller, + auctionConfig: cfg + })) }); - logInfo(MODULE, `register component auction config for: ${adUnitCode} x ${seller}: ${gptSlot.getAdUnitPath()}`, componentAuctionConfig); + logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); } else { - logWarn(MODULE, `unable to register component auction config for: ${adUnitCode} x ${seller}.`); + logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); } +} + +function onAuctionInit({auctionId}) { + PENDING[auctionId] = {}; +} + +function getSlotSignals(bidsReceived = [], bidRequests = []) { + let bidfloor, bidfloorcur; + if (bidsReceived.length > 0) { + const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); + bidfloor = bestBid.cpm; + bidfloorcur = bestBid.currency; + } else { + const floors = bidRequests.map(bid => typeof bid.getFloor === 'function' && bid.getFloor()).filter(f => f); + const minFloor = floors.length && floors.reduce(minimum(currencyCompare(floor => [floor.floor, floor.currency]))) + bidfloor = minFloor?.floor; + bidfloorcur = minFloor?.currency; + } + const cfg = {}; + if (bidfloor) { + deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); + bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); + } + return cfg; +} - next(adUnitCode, componentAuctionConfig); +function onAuctionEnd({auctionId, bidsReceived, bidderRequests}) { + try { + const allReqs = bidderRequests?.flatMap(br => br.bids); + Object.entries(PENDING[auctionId]).forEach(([adUnitCode, auctionConfigs]) => { + const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; + const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); + setComponentAuction(adUnitCode, auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg))) + }) + } finally { + delete PENDING[auctionId]; + } +} + +export function addComponentAuctionHook(next, auctionId, adUnitCode, componentAuctionConfig) { + if (PENDING.hasOwnProperty(auctionId)) { + !PENDING[auctionId].hasOwnProperty(adUnitCode) && (PENDING[auctionId][adUnitCode] = []); + PENDING[auctionId][adUnitCode].push(componentAuctionConfig); + } else { + logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig) + } + next(auctionId, adUnitCode, componentAuctionConfig); } function isFledgeSupported() { diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 776ad99fa25..e49dfec2f1c 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -506,7 +506,7 @@ export function PrebidServer() { } }, onFledge: ({adUnitCode, config}) => { - addComponentAuction(adUnitCode, config); + addComponentAuction(bidRequests[0].auctionId, adUnitCode, config); } }) } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 7d3a6137d40..54f71c7dc3e 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -17,13 +17,14 @@ import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.js'; import {SUPPORTED_MEDIA_TYPES} from '../../libraries/pbsExtensions/processors/mediaType.js'; import {IMP, REQUEST, RESPONSE} from '../../src/pbjsORTB.js'; -import {beConvertCurrency} from '../../src/utils/currency.js'; import {redactor} from '../../src/activities/redactor.js'; import {s2sActivityParams} from '../../src/adapterManager.js'; import {activityParams} from '../../src/activities/activityParams.js'; import {MODULE_TYPE_BIDDER} from '../../src/activities/modules.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js'; +import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; +import {minimum} from '../../src/utils/reducers.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -141,6 +142,7 @@ const PBS_CONVERTER = ortbConverter({ bidfloor(orig, imp, proxyBidRequest, context) { // for bid floors, we pass each bidRequest associated with this imp through normal bidfloor processing, // and aggregate all of them into a single, minimum floor to put in the request + const getMin = minimum(currencyCompare(floor => [floor.bidfloor, floor.bidfloorcur])); let min; for (const req of context.actualBidRequests.values()) { const floor = {}; @@ -149,14 +151,8 @@ const PBS_CONVERTER = ortbConverter({ if (floor.bidfloorcur == null || floor.bidfloor == null) { min = null; break; - } else if (min == null) { - min = floor; - } else { - const value = beConvertCurrency(floor.bidfloor, floor.bidfloorcur, min.bidfloorcur); - if (value != null && value < min.bidfloor) { - min = floor; - } } + min = min == null ? floor : getMin(min, floor); } if (min != null) { Object.assign(imp, min); diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 37167fff691..a72a79c2b40 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -27,8 +27,8 @@ import {bidderSettings} from '../src/bidderSettings.js'; import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; -import {beConvertCurrency} from '../src/utils/currency.js'; import {adjustCpm} from '../src/utils/cpm.js'; +import {convertCurrency} from '../libraries/currencyUtils/currency.js'; /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. @@ -794,8 +794,8 @@ export function setImpExtPrebidFloors(imp, bidRequest, context) { if (floorMinCur == null) { floorMinCur = imp.bidfloorcur } const ortb2ImpFloorCur = imp.ext?.prebid?.floors?.floorMinCur || imp.ext?.prebid?.floorMinCur || floorMinCur; const ortb2ImpFloorMin = imp.ext?.prebid?.floors?.floorMin || imp.ext?.prebid?.floorMin; - const convertedFloorMinValue = beConvertCurrency(imp.bidfloor, imp.bidfloorcur, floorMinCur); - const convertedOrtb2ImpFloorMinValue = ortb2ImpFloorMin && ortb2ImpFloorCur ? beConvertCurrency(ortb2ImpFloorMin, ortb2ImpFloorCur, floorMinCur) : false; + const convertedFloorMinValue = convertCurrency(imp.bidfloor, imp.bidfloorcur, floorMinCur); + const convertedOrtb2ImpFloorMinValue = ortb2ImpFloorMin && ortb2ImpFloorCur ? convertCurrency(ortb2ImpFloorMin, ortb2ImpFloorCur, floorMinCur) : false; const lowestImpFloorMin = convertedOrtb2ImpFloorMinValue && convertedOrtb2ImpFloorMinValue < convertedFloorMinValue ? convertedOrtb2ImpFloorMinValue diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index b31019a6d79..df97d820c96 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -288,14 +288,11 @@ export function newBidder(spec) { onTimelyResponse(spec.code); responses.push(resp) }, - /** Process eventual BidderAuctionResponse.fledgeAuctionConfig field in response. - * @param {Array} fledgeAuctionConfigs - */ onFledgeAuctionConfigs: (fledgeAuctionConfigs) => { fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest.adUnitCode, fledgeAuctionConfig.config); + addComponentAuction(bidRequest.auctionId, bidRequest.adUnitCode, fledgeAuctionConfig.config); } else { logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); } diff --git a/src/prebid.js b/src/prebid.js index 5c5f66d6028..decc2a7cef6 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -12,7 +12,6 @@ import { deepSetValue, flatten, generateUUID, - getHighestCpm, inIframe, insertElement, isArray, @@ -52,6 +51,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; +import {getHighestCpm} from './utils/reducers.js'; import {fillVideoDefaults} from './video.js'; const pbjsInstance = getGlobal(); diff --git a/src/targeting.js b/src/targeting.js index ed313c55684..0aa395aa9a3 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,8 +1,6 @@ import { deepAccess, deepClone, - getHighestCpm, - getOldestHighestCpmBid, groupBy, isAdUnitCodeMatchingSlot, isArray, @@ -24,6 +22,7 @@ import {hook} from './hook.js'; import {bidderSettings} from './bidderSettings.js'; import {find, includes} from './polyfill.js'; import CONSTANTS from './constants.json'; +import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; import {getTTL} from './bidTTL.js'; var pbTargetingKeys = []; diff --git a/src/utils.js b/src/utils.js index 5ad8b5031bd..c436b6385e7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -729,26 +729,6 @@ export function isApnGetTagDefined() { } } -// This function will get highest cpm value bid, in case of tie it will return the bid with lowest timeToRespond -export const getHighestCpm = getHighestCpmCallback('timeToRespond', (previous, current) => previous > current); - -// This function will get the oldest hightest cpm value bid, in case of tie it will return the bid which came in first -// Use case for tie: https://github.com/prebid/Prebid.js/issues/2448 -export const getOldestHighestCpmBid = getHighestCpmCallback('responseTimestamp', (previous, current) => previous > current); - -// This function will get the latest hightest cpm value bid, in case of tie it will return the bid which came in last -// Use case for tie: https://github.com/prebid/Prebid.js/issues/2539 -export const getLatestHighestCpmBid = getHighestCpmCallback('responseTimestamp', (previous, current) => previous < current); - -function getHighestCpmCallback(useTieBreakerProperty, tieBreakerCallback) { - return (previous, current) => { - if (previous.cpm === current.cpm) { - return tieBreakerCallback(previous[useTieBreakerProperty], current[useTieBreakerProperty]) ? current : previous; - } - return previous.cpm < current.cpm ? current : previous; - } -} - /** * Fisher–Yates shuffle * http://stackoverflow.com/a/6274398 diff --git a/src/utils/currency.js b/src/utils/currency.js deleted file mode 100644 index ab7e5faa1ea..00000000000 --- a/src/utils/currency.js +++ /dev/null @@ -1,16 +0,0 @@ -import {getGlobal} from '../prebidGlobal.js'; - -/** - * "best effort" wrapper around currency conversion; always returns an amount that may or may not be correct. - */ -export function beConvertCurrency(amount, from, to) { - if (from === to) return amount; - let result = amount; - if (typeof getGlobal().convertCurrency === 'function') { - try { - result = getGlobal().convertCurrency(amount, from, to); - } catch (e) { - } - } - return result; -} diff --git a/src/utils/reducers.js b/src/utils/reducers.js new file mode 100644 index 00000000000..28851be8aaa --- /dev/null +++ b/src/utils/reducers.js @@ -0,0 +1,44 @@ +export function simpleCompare(a, b) { + if (a === b) return 0; + return a < b ? -1 : 1; +} + +export function keyCompare(key = (item) => item) { + return (a, b) => simpleCompare(key(a), key(b)) +} + +export function reverseCompare(compare = simpleCompare) { + return (a, b) => -compare(a, b) || 0; +} + +export function tiebreakCompare(...compares) { + return function (a, b) { + for (const cmp of compares) { + const val = cmp(a, b); + if (val !== 0) return val; + } + return 0; + } +} + +export function minimum(compare = simpleCompare) { + return (min, item) => compare(item, min) < 0 ? item : min; +} + +export function maximum(compare = simpleCompare) { + return minimum(reverseCompare(compare)); +} + +const cpmCompare = keyCompare((bid) => bid.cpm); +const timestampCompare = keyCompare((bid) => bid.responseTimestamp); + +// This function will get highest cpm value bid, in case of tie it will return the bid with lowest timeToRespond +export const getHighestCpm = maximum(tiebreakCompare(cpmCompare, reverseCompare(keyCompare((bid) => bid.timeToRespond)))) + +// This function will get the oldest hightest cpm value bid, in case of tie it will return the bid which came in first +// Use case for tie: https://github.com/prebid/Prebid.js/issues/2448 +export const getOldestHighestCpmBid = maximum(tiebreakCompare(cpmCompare, reverseCompare(timestampCompare))) + +// This function will get the latest hightest cpm value bid, in case of tie it will return the bid which came in last +// Use case for tie: https://github.com/prebid/Prebid.js/issues/2539 +export const getLatestHighestCpmBid = maximum(tiebreakCompare(cpmCompare, timestampCompare)) diff --git a/test/spec/libraries/currencyUtils_spec.js b/test/spec/libraries/currencyUtils_spec.js new file mode 100644 index 00000000000..9d3d73e6a5f --- /dev/null +++ b/test/spec/libraries/currencyUtils_spec.js @@ -0,0 +1,113 @@ +import {getGlobal} from 'src/prebidGlobal.js'; +import {convertCurrency, currencyCompare, currencyNormalizer} from 'libraries/currencyUtils/currency.js'; + +describe('currency utils', () => { + let sandbox; + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }) + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('convertCurrency', () => { + Object.entries({ + 'not available': () => sandbox.stub(getGlobal(), 'convertCurrency').value(undefined), + 'throwing errors': () => sandbox.stub(getGlobal(), 'convertCurrency').callsFake(() => { throw new Error(); }), + }).forEach(([t, setup]) => { + describe(`when currency module is ${t}`, () => { + beforeEach(setup); + + it('should "convert" to the same currency', () => { + expect(convertCurrency(123, 'mock', 'mock', false)).to.eql(123); + }); + + it('should throw when suppressErrors = false', () => { + expect(() => convertCurrency(123, 'c1', 'c2', false)).to.throw(); + }); + + it('should return input value when suppressErrors = true', () => { + expect(convertCurrency(123, 'c1', 'c2', true)).to.eql(123); + }) + }) + }); + + describe('when currency module is working', () => { + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amt) => amt * 10) + }); + + it('should be used for actual conversions', () => { + expect(convertCurrency(123, 'c1', 'c2')).to.eql(1230); + sinon.assert.calledWith(getGlobal().convertCurrency, 123, 'c1', 'c2'); + }); + + it('should NOT be used when no conversion is necessary', () => { + expect(convertCurrency(123, 'cur', 'cur')).to.eql(123); + sinon.assert.notCalled(getGlobal().convertCurrency); + }) + }) + }); + + describe('Currency normalization', () => { + let mockConvert; + beforeEach(() => { + mockConvert = sinon.stub().callsFake((amt, from, to) => { + if (from === to) return amt; + return amt / from * to + }) + }); + + describe('currencyNormalizer', () => { + it('converts to toCurrency if set', () => { + const normalize = currencyNormalizer(10, true, mockConvert); + expect(normalize(1, 1)).to.eql(10); + expect(normalize(10, 100)).to.eql(1); + }); + + it('converts to first currency if toCurrency is not set', () => { + const normalize = currencyNormalizer(null, true, mockConvert); + expect(normalize(1, 1)).to.eql(1); + expect(normalize(1, 10)).to.eql(0.1); + }); + + [true, false].forEach(bestEffort => { + it(`passes bestEffort = ${bestEffort} to convert`, () => { + currencyNormalizer(null, bestEffort, mockConvert)(1, 1); + sinon.assert.calledWith(mockConvert, 1, 1, 1, bestEffort); + }) + }) + }); + + describe('currencyCompare', () => { + let compare + beforeEach(() => { + compare = currencyCompare((val) => [val.amount, val.cur], currencyNormalizer(null, false, mockConvert)) + }); + [ + [{amount: 1, cur: 1}, {amount: 1, cur: 10}, 1], + [{amount: 10, cur: 1}, {amount: 0.1, cur: 100}, 1], + [{amount: 1, cur: 1}, {amount: 10, cur: 10}, 0], + ].forEach(([a, b, expected]) => { + it(`should compare ${a.amount}/${a.cur} and ${b.amount}/${b.cur}`, () => { + expect(compare(a, b)).to.equal(expected); + expect(compare(b, a)).to.equal(-expected); + }); + }); + }) + }) +}) diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js new file mode 100644 index 00000000000..0bcd7753e55 --- /dev/null +++ b/test/spec/modules/fledgeForGpt_spec.js @@ -0,0 +1,429 @@ +import { + expect +} from 'chai'; +import * as fledge from 'modules/fledgeForGpt.js'; +import {config} from '../../../src/config.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as utils from '../../../src/utils.js'; +import {hook} from '../../../src/hook.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; +import * as events from 'src/events.js'; +import CONSTANTS from 'src/constants.json'; +import {getGlobal} from '../../../src/prebidGlobal.js'; + +describe('fledgeForGpt module', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + }); + describe('addComponentAuction', function () { + before(() => { + fledge.init({enabled: true}); + }); + + const fledgeAuctionConfig = { + seller: 'bidder', + mock: 'config' + }; + + describe('addComponentAuctionHook', function () { + let nextFnSpy, mockGptSlot; + beforeEach(function () { + nextFnSpy = sinon.spy(); + mockGptSlot = { + setConfig: sinon.stub(), + getAdUnitPath: () => 'mock/gpt/au' + }; + sandbox.stub(utils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); + }); + + it('should call next()', function () { + fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'auc', fledgeAuctionConfig); + sinon.assert.calledWith(nextFnSpy, 'aid', 'auc', fledgeAuctionConfig); + }); + + it('should collect auction configs and route them to GPT at end of auction', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); + const cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; + const cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; + fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au1', cf1); + fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au2', cf2); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + sinon.assert.calledWith(utils.getGptSlotForAdUnitCode, 'au1'); + sinon.assert.calledWith(utils.getGptSlotForAdUnitCode, 'au2'); + sinon.assert.calledWith(mockGptSlot.setConfig, { + componentAuction: [{ + configKey: 'b1', + auctionConfig: cf1, + }] + }); + sinon.assert.calledWith(mockGptSlot.setConfig, { + componentAuction: [{ + configKey: 'b2', + auctionConfig: cf2, + }] + }); + }); + + it('should drop auction configs after end of auction', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au', fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + sinon.assert.notCalled(mockGptSlot.setConfig); + }); + + describe('floor signal', () => { + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }); + + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { + if (from === to) return amount; + if (from === 'USD' && to === 'JPY') return amount * 100; + if (from === 'JPY' && to === 'USD') return amount / 100; + throw new Error('unexpected currency conversion'); + }); + }); + + Object.entries({ + 'bids': (payload, values) => { + payload.bidsReceived = values + .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) + .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]) + }, + 'no bids': (payload, values) => { + payload.bidderRequests = values + .map((val) => ({bids: [{adUnitCode: 'au', getFloor: () => ({floor: val.amount, currency: val.cur})}]})) + .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]) + } + }).forEach(([tcase, setup]) => { + describe(`when auction has ${tcase}`, () => { + Object.entries({ + 'no currencies': { + values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], + 'bids': { + bidfloor: 100, + bidfloorcur: undefined + }, + 'no bids': { + bidfloor: 1, + bidfloorcur: undefined, + } + }, + 'only zero values': { + values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], + 'bids': { + bidfloor: undefined, + bidfloorcur: undefined, + }, + 'no bids': { + bidfloor: undefined, + bidfloorcur: undefined, + } + }, + 'matching currencies': { + values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], + 'bids': { + bidfloor: 100, + bidfloorcur: 'JPY', + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + }, + 'mixed currencies': { + values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], + 'bids': { + bidfloor: 10, + bidfloorcur: 'USD' + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + } + }).forEach(([t, testConfig]) => { + const values = testConfig.values; + const {bidfloor, bidfloorcur} = testConfig[tcase]; + + describe(`with ${t}`, () => { + let payload; + beforeEach(() => { + payload = {auctionId: 'aid'}; + setup(payload, values); + }); + + it('should populate bidfloor/bidfloorcur', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); + fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au', fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, payload); + sinon.assert.calledWith(mockGptSlot.setConfig, sinon.match(arg => { + return arg.componentAuction.some(au => au.auctionConfig.auctionSignals?.prebid?.bidfloor === bidfloor && au.auctionConfig.auctionSignals?.prebid?.bidfloorcur === bidfloorcur) + })) + }) + }); + }); + }) + }) + }); + }); + }); + + describe('fledgeEnabled', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { + navigator[p] = sinon.stub(); + }); + hook.ready(); + }); + + after(function () { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }); + + afterEach(function () { + config.resetConfig(); + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + describe('with setBidderConfig()', () => { + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({bidderSequence: 'fixed'}); + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + fledgeEnabled: true, + defaultForSlots: 1, + } + }); + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + expect(bidRequests[0].defaultForSlots).to.equal(1); + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + expect(bidRequests[1].defaultForSlots).to.be.undefined; + }); + }); + + describe('with setConfig()', () => { + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({ + bidderSequence: 'fixed', + fledgeForGpt: { + enabled: true, + bidders: ['appnexus'], + defaultForSlots: 1, + } + }); + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + expect(bidRequests[0].defaultForSlots).to.equal(1); + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.be.undefined; + expect(bidRequests[1].defaultForSlots).to.be.undefined; + }); + + it('should set fledgeEnabled correctly for all bidders', function () { + config.setConfig({ + bidderSequence: 'fixed', + fledgeForGpt: { + enabled: true, + defaultForSlots: 1, + } + }); + + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + expect(bidRequests[0].defaultForSlots).to.equal(1); + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[0].fledgeEnabled).to.be.true; + expect(bidRequests[0].defaultForSlots).to.equal(1); + }); + }); + }); + + describe('ortb processors for fledge', () => { + describe('when defaultForSlots is set', () => { + it('imp.ext.ae should be set if fledge is enabled', () => { + const imp = {}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); + expect(imp.ext.ae).to.equal(1); + }); + it('imp.ext.ae should be left intact if set on adunit and fledge is enabled', () => { + const imp = {ext: {ae: 2}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); + expect(imp.ext.ae).to.equal(2); + }); + }); + describe('when defaultForSlots is not defined', () => { + it('imp.ext.ae should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + }); + it('imp.ext.ae should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: 2}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(2); + }); + }); + describe('parseExtPrebidFledge', () => { + function packageConfigs(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + }; + } + + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + function extractResult(ctx) { + return Object.fromEntries( + Object.entries(ctx) + .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .filter(([_, val]) => val != null) + ); + } + + it('should collect fledge configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({}); + }); + }); + describe('setResponseFledgeConfigs', () => { + it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + const ctx = { + impContext: { + 1: { + bidRequest: {bidId: 'bid1'}, + fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + }, + 2: { + bidRequest: {bidId: 'bid2'}, + fledgeConfigs: [{config: {id: 3}}] + }, + 3: { + bidRequest: {bidId: 'bid3'} + } + } + }; + const resp = {}; + setResponseFledgeConfigs(resp, {}, ctx); + expect(resp.fledgeAuctionConfigs).to.eql([ + {bidId: 'bid1', config: {id: 1}}, + {bidId: 'bid1', config: {id: 2}}, + {bidId: 'bid2', config: {id: 3}}, + ]); + }); + it('should not set fledgeAuctionConfigs if none exist', () => { + const resp = {}; + setResponseFledgeConfigs(resp, {}, { + impContext: { + 1: { + fledgeConfigs: [] + }, + 2: {} + } + }); + expect(resp).to.eql({}); + }); + }); + }); +}); diff --git a/test/spec/modules/fledge_spec.js b/test/spec/modules/fledge_spec.js deleted file mode 100644 index 7a670fa2381..00000000000 --- a/test/spec/modules/fledge_spec.js +++ /dev/null @@ -1,282 +0,0 @@ -import { - expect -} from 'chai'; -import * as fledge from 'modules/fledgeForGpt.js'; -import {config} from '../../../src/config.js'; -import adapterManager from '../../../src/adapterManager.js'; -import * as utils from '../../../src/utils.js'; -import {hook} from '../../../src/hook.js'; -import 'modules/appnexusBidAdapter.js'; -import 'modules/rubiconBidAdapter.js'; -import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; - -const CODE = 'sampleBidder'; -const AD_UNIT_CODE = 'mock/placement'; - -describe('fledgeForGpt module', function() { - let nextFnSpy; - before(() => { - fledge.init({enabled: true}) - }); - - const bidRequest = { - adUnitCode: AD_UNIT_CODE, - bids: [{ - bidId: '1', - bidder: CODE, - auctionId: 'first-bid-id', - adUnitCode: AD_UNIT_CODE, - transactionId: 'au', - }] - }; - const fledgeAuctionConfig = { - bidId: '1', - } - - describe('addComponentAuctionHook', function() { - beforeEach(function() { - nextFnSpy = sinon.spy(); - }); - - it('should call next() when a proper adUnitCode and fledgeAuctionConfig are provided', function() { - fledge.addComponentAuctionHook(nextFnSpy, bidRequest.adUnitCode, fledgeAuctionConfig); - expect(nextFnSpy.called).to.be.true; - }); - }); -}); - -describe('fledgeEnabled', function () { - const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])) - - before(function () { - // navigator.runAdAuction & co may not exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - Object.keys(navProps).forEach(p => { navigator[p] = sinon.stub() }); - hook.ready(); - }); - - after(function() { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); - }) - - afterEach(function () { - config.resetConfig(); - }); - - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - - describe('with setBidderConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({bidderSequence: 'fixed'}) - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - fledgeEnabled: true, - defaultForSlots: 1, - } - }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - expect(bidRequests[1].defaultForSlots).to.be.undefined; - }); - }); - - describe('with setConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({ - bidderSequence: 'fixed', - fledgeForGpt: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, - } - }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - expect(bidRequests[1].defaultForSlots).to.be.undefined; - }); - - it('should set fledgeEnabled correctly for all bidders', function () { - config.setConfig({ - bidderSequence: 'fixed', - fledgeForGpt: { - enabled: true, - defaultForSlots: 1, - } - }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - }); - }); -}); - -describe('ortb processors for fledge', () => { - describe('when defaultForSlots is set', () => { - it('imp.ext.ae should be set if fledge is enabled', () => { - const imp = {}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); - expect(imp.ext.ae).to.equal(1); - }); - it('imp.ext.ae should be left intact if set on adunit and fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); - expect(imp.ext.ae).to.equal(2); - }); - }); - describe('when defaultForSlots is not defined', () => { - it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; - setImpExtAe(imp, {}, {bidderRequest: {}}); - expect(imp.ext.ae).to.not.exist; - }) - it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); - }); - }); - describe('parseExtPrebidFledge', () => { - function packageConfigs(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } - } - } - } - - function generateImpCtx(fledgeFlags) { - return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); - } - - function generateCfg(impid, ...ids) { - return ids.map((id) => ({impid, config: {id}})); - } - - function extractResult(ctx) { - return Object.fromEntries( - Object.entries(ctx) - .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) - .filter(([_, val]) => val != null) - ); - } - - it('should collect fledge configs by imp', () => { - const ctx = { - impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) - }; - const resp = packageConfigs( - generateCfg('e1', 1, 2, 3) - .concat(generateCfg('e2', 4) - .concat(generateCfg('d1', 5, 6))) - ); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({ - e1: [1, 2, 3], - e2: [4], - }); - }); - it('should not choke if fledge config references unknown imp', () => { - const ctx = {impContext: generateImpCtx({i: 1})}; - const resp = packageConfigs(generateCfg('unknown', 1)); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({}); - }); - }); - describe('setResponseFledgeConfigs', () => { - it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { - const ctx = { - impContext: { - 1: { - bidRequest: {bidId: 'bid1'}, - fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] - }, - 2: { - bidRequest: {bidId: 'bid2'}, - fledgeConfigs: [{config: {id: 3}}] - }, - 3: { - bidRequest: {bidId: 'bid3'} - } - } - }; - const resp = {}; - setResponseFledgeConfigs(resp, {}, ctx); - expect(resp.fledgeAuctionConfigs).to.eql([ - {bidId: 'bid1', config: {id: 1}}, - {bidId: 'bid1', config: {id: 2}}, - {bidId: 'bid2', config: {id: 3}}, - ]); - }); - it('should not set fledgeAuctionConfigs if none exist', () => { - const resp = {}; - setResponseFledgeConfigs(resp, {}, { - impContext: { - 1: { - fledgeConfigs: [] - }, - 2: {} - } - }); - expect(resp).to.eql({}); - }); - }); -}); diff --git a/test/spec/modules/multibid_spec.js b/test/spec/modules/multibid_spec.js index eaf8fa33a66..c11113473ce 100644 --- a/test/spec/modules/multibid_spec.js +++ b/test/spec/modules/multibid_spec.js @@ -1,16 +1,15 @@ import {expect} from 'chai'; import { - validateMultibid, - adjustBidderRequestsHook, addBidResponseHook, + adjustBidderRequestsHook, resetMultibidUnits, + resetMultiConfig, sortByMultibid, targetBidPoolHook, - resetMultiConfig + validateMultibid } from 'modules/multibid/index.js'; -import {parse as parseQuery} from 'querystring'; import {config} from 'src/config.js'; -import * as utils from 'src/utils.js'; +import {getHighestCpm} from '../../../src/utils/reducers.js'; describe('multibid adapter', function () { let bidArray = [{ @@ -545,7 +544,7 @@ describe('multibid adapter', function () { it('it does not run filter on bidsReceived if no multibid configuration found', function () { let bids = [{...bidArray[0]}, {...bidArray[1]}]; - targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + targetBidPoolHook(callbackFn, bids, getHighestCpm); expect(result).to.not.equal(null); expect(result.bidsReceived).to.not.equal(null); @@ -562,7 +561,7 @@ describe('multibid adapter', function () { config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2}]}); - targetBidPoolHook(callbackFn, bids, utils.getHighestCpm); + targetBidPoolHook(callbackFn, bids, getHighestCpm); bids.pop(); expect(result).to.not.equal(null); @@ -584,7 +583,7 @@ describe('multibid adapter', function () { config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); - targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + targetBidPoolHook(callbackFn, modifiedBids, getHighestCpm); expect(result).to.not.equal(null); expect(result.bidsReceived).to.not.equal(null); @@ -609,7 +608,7 @@ describe('multibid adapter', function () { config.setConfig({multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}]}); - targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm); + targetBidPoolHook(callbackFn, modifiedBids, getHighestCpm); expect(result).to.not.equal(null); expect(result.bidsReceived).to.not.equal(null); @@ -642,7 +641,7 @@ describe('multibid adapter', function () { config.setConfig({ multibid: [{bidder: 'bidderA', maxBids: 2, targetBiddercodePrefix: 'bidA'}] }); - targetBidPoolHook(callbackFn, modifiedBids, utils.getHighestCpm, 3); + targetBidPoolHook(callbackFn, modifiedBids, getHighestCpm, 3); expect(result).to.not.equal(null); expect(result.bidsReceived).to.not.equal(null); @@ -670,7 +669,7 @@ describe('multibid adapter', function () { expect(bidPool.length).to.equal(6); - targetBidPoolHook(callbackFn, bidPool, utils.getHighestCpm); + targetBidPoolHook(callbackFn, bidPool, getHighestCpm); expect(result).to.not.equal(null); expect(result.bidsReceived).to.not.equal(null); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 4b783803507..717a5cd6c6d 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3476,16 +3476,16 @@ describe('S2S Adapter', function () { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); expect(addBidResponse.called).to.be.true; - sinon.assert.calledWith(fledgeStub, AU, {id: 1}); - sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 2}); }); it('calls addComponentAuction when there is no bid in the response', () => { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); expect(addBidResponse.called).to.be.false; - sinon.assert.calledWith(fledgeStub, AU, {id: 1}); - sinon.assert.calledWith(fledgeStub, AU, {id: 2}); + sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 1}); + sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 2}); }) }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 0360679ca52..4c13d830206 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -48,1040 +48,1121 @@ before(() => { let wrappedCallback = config.callbackWithBidder(CODE); -describe('bidders created by newBidder', function () { - let spec; - let bidder; - let addBidResponseStub; - let doneStub; - - beforeEach(function () { - spec = { - code: CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - getUserSyncs: sinon.stub() - }; - - addBidResponseStub = sinon.stub(); - addBidResponseStub.reject = sinon.stub(); - doneStub = sinon.stub(); - }); - - describe('when the ajax response is irrelevant', function () { - let sandbox; - let ajaxStub; - let getConfigSpy; - let aliasRegistryStub, aliasRegistry; +describe('bidderFactory', () => { + describe('bidders created by newBidder', function () { + let spec; + let bidder; + let addBidResponseStub; + let doneStub; beforeEach(function () { - sandbox = sinon.sandbox.create(); - sandbox.stub(activityRules, 'isActivityAllowed').callsFake(() => true); - ajaxStub = sandbox.stub(ajax, 'ajax'); - addBidResponseStub.reset(); - getConfigSpy = sandbox.spy(config, 'getConfig'); - doneStub.reset(); - aliasRegistry = {}; - aliasRegistryStub = sandbox.stub(adapterManager, 'aliasRegistry'); - aliasRegistryStub.get(() => aliasRegistry); - }); + spec = { + code: CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + getUserSyncs: sinon.stub() + }; - afterEach(function () { - sandbox.restore(); + addBidResponseStub = sinon.stub(); + addBidResponseStub.reject = sinon.stub(); + doneStub = sinon.stub(); }); - it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true - } + describe('when the ajax response is irrelevant', function () { + let sandbox; + let ajaxStub; + let getConfigSpy; + let aliasRegistryStub, aliasRegistry; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + sandbox.stub(activityRules, 'isActivityAllowed').callsFake(() => true); + ajaxStub = sandbox.stub(ajax, 'ajax'); + addBidResponseStub.reset(); + getConfigSpy = sandbox.spy(config, 'getConfig'); + doneStub.reset(); + aliasRegistry = {}; + aliasRegistryStub = sandbox.stub(adapterManager, 'aliasRegistry'); + aliasRegistryStub.get(() => aliasRegistry); }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - it('should let registerSyncs run with valid alias and aliasSync enabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: true - } + afterEach(function () { + sandbox.restore(); }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - it('should let registerSyncs run with invalid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } + it('should let registerSyncs run with invalid alias and aliasSync enabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: true + } + }); + spec.code = 'fakeBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); }); - spec.code = 'fakeBidder'; - const bidder = newBidder(spec); - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); - }); - it('should not let registerSyncs run with valid alias and aliasSync disabled', function () { - config.setConfig({ - userSync: { - aliasSyncEnabled: false - } + it('should let registerSyncs run with valid alias and aliasSync enabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: true + } + }); + spec.code = 'aliasBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); }); - spec.code = 'aliasBidder'; - const bidder = newBidder(spec); - aliasRegistry = {[spec.code]: CODE}; - bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); - }); - describe('transaction IDs', () => { - beforeEach(() => { - activityRules.isActivityAllowed.reset(); - ajaxStub.callsFake((_, callback) => callback.success(null, {getResponseHeader: sinon.stub()})); - spec.interpretResponse.callsFake(() => [ - { - requestId: 'bid', - cpm: 123, - ttl: 300, - creativeId: 'crid', - netRevenue: true, - currency: 'USD' + it('should let registerSyncs run with invalid alias and aliasSync disabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: false + } + }); + spec.code = 'fakeBidder'; + const bidder = newBidder(spec); + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(true); + }); + + it('should not let registerSyncs run with valid alias and aliasSync disabled', function () { + config.setConfig({ + userSync: { + aliasSyncEnabled: false } - ]) + }); + spec.code = 'aliasBidder'; + const bidder = newBidder(spec); + aliasRegistry = {[spec.code]: CODE}; + bidder.callBids({ bids: [] }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(getConfigSpy.withArgs('userSync.filterSettings').calledOnce).to.equal(false); }); - Object.entries({ - 'be hidden': false, - 'not be hidden': true, - }).forEach(([t, allowed]) => { - const expectation = allowed ? (val) => expect(val).to.exist : (val) => expect(val).to.not.exist; + describe('transaction IDs', () => { + beforeEach(() => { + activityRules.isActivityAllowed.reset(); + ajaxStub.callsFake((_, callback) => callback.success(null, {getResponseHeader: sinon.stub()})); + spec.interpretResponse.callsFake(() => [ + { + requestId: 'bid', + cpm: 123, + ttl: 300, + creativeId: 'crid', + netRevenue: true, + currency: 'USD' + } + ]) + }); - function checkBidRequest(br) { - ['auctionId', 'transactionId'].forEach((prop) => expectation(br[prop])); - } + Object.entries({ + 'be hidden': false, + 'not be hidden': true, + }).forEach(([t, allowed]) => { + const expectation = allowed ? (val) => expect(val).to.exist : (val) => expect(val).to.not.exist; - function checkBidderRequest(br) { - expectation(br.auctionId); - br.bids.forEach(checkBidRequest); - } + function checkBidRequest(br) { + ['auctionId', 'transactionId'].forEach((prop) => expectation(br[prop])); + } - it(`should ${t} from the spec logic when the transmitTid activity is${allowed ? '' : ' not'} allowed`, () => { - spec.isBidRequestValid.callsFake(br => { - checkBidRequest(br); - return true; - }); - spec.buildRequests.callsFake((bidReqs, bidderReq) => { - checkBidderRequest(bidderReq); - bidReqs.forEach(checkBidRequest); - return {method: 'POST'}; - }); - activityRules.isActivityAllowed.callsFake(() => allowed); + function checkBidderRequest(br) { + expectation(br.auctionId); + br.bids.forEach(checkBidRequest); + } - const bidder = newBidder(spec); + it(`should ${t} from the spec logic when the transmitTid activity is${allowed ? '' : ' not'} allowed`, () => { + spec.isBidRequestValid.callsFake(br => { + checkBidRequest(br); + return true; + }); + spec.buildRequests.callsFake((bidReqs, bidderReq) => { + checkBidderRequest(bidderReq); + bidReqs.forEach(checkBidRequest); + return {method: 'POST'}; + }); + activityRules.isActivityAllowed.callsFake(() => allowed); + + const bidder = newBidder(spec); + + bidder.callBids({ + bidderCode: 'mockBidder', + auctionId: 'aid', + bids: [ + { + adUnitCode: 'mockAU', + bidId: 'bid', + transactionId: 'tid', + auctionId: 'aid' + } + ] + }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + sinon.assert.calledWithMatch(activityRules.isActivityAllowed, ACTIVITY_TRANSMIT_TID, { + componentType: MODULE_TYPE_BIDDER, + componentName: 'mockBidder' + }); + sinon.assert.calledWithMatch(addBidResponseStub, sinon.match.any, { + transactionId: 'tid', + auctionId: 'aid' + }) + }); + }); - bidder.callBids({ + it('should not be hidden from request methods', (done) => { + const bidderRequest = { bidderCode: 'mockBidder', auctionId: 'aid', + getAID() { return this.auctionId }, bids: [ { adUnitCode: 'mockAU', bidId: 'bid', transactionId: 'tid', - auctionId: 'aid' + auctionId: 'aid', + getTIDs() { + return [this.auctionId, this.transactionId] + } } ] - }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - sinon.assert.calledWithMatch(activityRules.isActivityAllowed, ACTIVITY_TRANSMIT_TID, { - componentType: MODULE_TYPE_BIDDER, - componentName: 'mockBidder' + }; + activityRules.isActivityAllowed.callsFake(() => false); + spec.isBidRequestValid.returns(true); + spec.buildRequests.callsFake((reqs, bidderReq) => { + expect(bidderReq.getAID()).to.eql('aid'); + expect(reqs[0].getTIDs()).to.eql(['aid', 'tid']); + done(); }); - sinon.assert.calledWithMatch(addBidResponseStub, sinon.match.any, { - transactionId: 'tid', - auctionId: 'aid' - }) - }); + newBidder(spec).callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + }) }); - it('should not be hidden from request methods', (done) => { - const bidderRequest = { - bidderCode: 'mockBidder', - auctionId: 'aid', - getAID() { return this.auctionId }, - bids: [ - { - adUnitCode: 'mockAU', - bidId: 'bid', - transactionId: 'tid', - auctionId: 'aid', - getTIDs() { - return [this.auctionId, this.transactionId] - } - } - ] - }; - activityRules.isActivityAllowed.callsFake(() => false); - spec.isBidRequestValid.returns(true); - spec.buildRequests.callsFake((reqs, bidderReq) => { - expect(bidderReq.getAID()).to.eql('aid'); - expect(reqs[0].getTIDs()).to.eql(['aid', 'tid']); - done(); - }); - newBidder(spec).callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - }) - }); - - it('should handle bad bid requests gracefully', function () { - const bidder = newBidder(spec); - - spec.getUserSyncs.returns([]); + it('should handle bad bid requests gracefully', function () { + const bidder = newBidder(spec); - bidder.callBids({}); - bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.getUserSyncs.returns([]); - expect(ajaxStub.called).to.equal(false); - expect(spec.isBidRequestValid.called).to.equal(false); - expect(spec.buildRequests.called).to.equal(false); - expect(spec.interpretResponse.called).to.equal(false); - }); + bidder.callBids({}); + bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should call buildRequests(bidRequest) the params are valid', function () { - const bidder = newBidder(spec); + expect(ajaxStub.called).to.equal(false); + expect(spec.isBidRequestValid.called).to.equal(false); + expect(spec.buildRequests.called).to.equal(false); + expect(spec.interpretResponse.called).to.equal(false); + }); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([]); + it('should call buildRequests(bidRequest) the params are valid', function () { + const bidder = newBidder(spec); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns([]); - expect(ajaxStub.called).to.equal(false); - expect(spec.isBidRequestValid.calledTwice).to.equal(true); - expect(spec.buildRequests.calledOnce).to.equal(true); - expect(spec.buildRequests.firstCall.args[0]).to.deep.equal(MOCK_BIDS_REQUEST.bids); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should not call buildRequests the params are invalid', function () { - const bidder = newBidder(spec); + expect(ajaxStub.called).to.equal(false); + expect(spec.isBidRequestValid.calledTwice).to.equal(true); + expect(spec.buildRequests.calledOnce).to.equal(true); + expect(spec.buildRequests.firstCall.args[0]).to.deep.equal(MOCK_BIDS_REQUEST.bids); + }); - spec.isBidRequestValid.returns(false); - spec.buildRequests.returns([]); + it('should not call buildRequests the params are invalid', function () { + const bidder = newBidder(spec); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.isBidRequestValid.returns(false); + spec.buildRequests.returns([]); - expect(ajaxStub.called).to.equal(false); - expect(spec.isBidRequestValid.calledTwice).to.equal(true); - expect(spec.buildRequests.called).to.equal(false); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should filter out invalid bids before calling buildRequests', function () { - const bidder = newBidder(spec); + expect(ajaxStub.called).to.equal(false); + expect(spec.isBidRequestValid.calledTwice).to.equal(true); + expect(spec.buildRequests.called).to.equal(false); + }); - spec.isBidRequestValid.onFirstCall().returns(true); - spec.isBidRequestValid.onSecondCall().returns(false); - spec.buildRequests.returns([]); + it('should filter out invalid bids before calling buildRequests', function () { + const bidder = newBidder(spec); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.isBidRequestValid.onFirstCall().returns(true); + spec.isBidRequestValid.onSecondCall().returns(false); + spec.buildRequests.returns([]); - expect(ajaxStub.called).to.equal(false); - expect(spec.isBidRequestValid.calledTwice).to.equal(true); - expect(spec.buildRequests.calledOnce).to.equal(true); - expect(spec.buildRequests.firstCall.args[0]).to.deep.equal([MOCK_BIDS_REQUEST.bids[0]]); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should make no server requests if the spec doesn\'t return any', function () { - const bidder = newBidder(spec); + expect(ajaxStub.called).to.equal(false); + expect(spec.isBidRequestValid.calledTwice).to.equal(true); + expect(spec.buildRequests.calledOnce).to.equal(true); + expect(spec.buildRequests.firstCall.args[0]).to.deep.equal([MOCK_BIDS_REQUEST.bids[0]]); + }); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([]); + it('should make no server requests if the spec doesn\'t return any', function () { + const bidder = newBidder(spec); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns([]); - expect(ajaxStub.called).to.equal(false); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should make the appropriate POST request', function () { - const bidder = newBidder(spec); - const url = 'test.url.com'; - const data = { arg: 2 }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: url, - data: data + expect(ajaxStub.called).to.equal(false); }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should make the appropriate POST request', function () { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: url, + data: data + }); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(url); - expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(data)); - sinon.assert.match(ajaxStub.firstCall.args[3], { - method: 'POST', - contentType: 'text/plain', - withCredentials: true - }); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should make the appropriate POST request when options are passed', function () { - const bidder = newBidder(spec); - const url = 'test.url.com'; - const data = { arg: 2 }; - const options = { contentType: 'application/json' }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: url, - data: data, - options: options + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(url); + expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(data)); + sinon.assert.match(ajaxStub.firstCall.args[3], { + method: 'POST', + contentType: 'text/plain', + withCredentials: true + }); }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should make the appropriate POST request when options are passed', function () { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + const options = { contentType: 'application/json' }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: url, + data: data, + options: options + }); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(url); - expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(data)); - sinon.assert.match(ajaxStub.firstCall.args[3], { - method: 'POST', - contentType: 'application/json', - withCredentials: true - }) - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should make the appropriate GET request', function () { - const bidder = newBidder(spec); - const url = 'test.url.com'; - const data = { arg: 2 }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'GET', - url: url, - data: data + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(url); + expect(ajaxStub.firstCall.args[2]).to.equal(JSON.stringify(data)); + sinon.assert.match(ajaxStub.firstCall.args[3], { + method: 'POST', + contentType: 'application/json', + withCredentials: true + }) }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should make the appropriate GET request', function () { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'GET', + url: url, + data: data + }); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); - expect(ajaxStub.firstCall.args[2]).to.be.undefined; - sinon.assert.match(ajaxStub.firstCall.args[3], { - method: 'GET', - withCredentials: true - }) - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should make the appropriate GET request when options are passed', function () { - const bidder = newBidder(spec); - const url = 'test.url.com'; - const data = { arg: 2 }; - const opt = { withCredentials: false } - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'GET', - url: url, - data: data, - options: opt + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); + expect(ajaxStub.firstCall.args[2]).to.be.undefined; + sinon.assert.match(ajaxStub.firstCall.args[3], { + method: 'GET', + withCredentials: true + }) }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); - expect(ajaxStub.firstCall.args[2]).to.be.undefined; - sinon.assert.match(ajaxStub.firstCall.args[3], { - method: 'GET', - withCredentials: false - }) - }); - - it('should make multiple calls if the spec returns them', function () { - const bidder = newBidder(spec); - const url = 'test.url.com'; - const data = { arg: 2 }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([ - { - method: 'POST', - url: url, - data: data - }, - { + it('should make the appropriate GET request when options are passed', function () { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + const opt = { withCredentials: false } + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ method: 'GET', url: url, - data: data - } - ]); - - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + data: data, + options: opt + }); - expect(ajaxStub.calledTwice).to.equal(true); - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - describe('browsingTopics ajax option', () => { - let transmitUfpdAllowed, bidder; - beforeEach(() => { - activityRules.isActivityAllowed.reset(); - activityRules.isActivityAllowed.callsFake((activity) => activity === ACTIVITY_TRANSMIT_UFPD ? transmitUfpdAllowed : true); - bidder = newBidder(spec); - spec.isBidRequestValid.returns(true); + expect(ajaxStub.calledOnce).to.equal(true); + expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2`); + expect(ajaxStub.firstCall.args[2]).to.be.undefined; + sinon.assert.match(ajaxStub.firstCall.args[3], { + method: 'GET', + withCredentials: false + }) }); - it(`should be set to false when adapter sets browsingTopics = false`, () => { - transmitUfpdAllowed = true; + it('should make multiple calls if the spec returns them', function () { + const bidder = newBidder(spec); + const url = 'test.url.com'; + const data = { arg: 2 }; + spec.isBidRequestValid.returns(true); spec.buildRequests.returns([ + { + method: 'POST', + url: url, + data: data + }, { method: 'GET', - url: 'url', - options: { - browsingTopics: false - } + url: url, + data: data } ]); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - sinon.assert.calledWith(ajaxStub, 'url', sinon.match.any, sinon.match.any, sinon.match({ - browsingTopics: false - })); + + expect(ajaxStub.calledTwice).to.equal(true); }); - Object.entries({ - 'allowed': true, - 'not allowed': false - }).forEach(([t, allow]) => { - it(`should be set to ${allow} when transmitUfpd is ${t}`, () => { - transmitUfpdAllowed = allow; + describe('browsingTopics ajax option', () => { + let transmitUfpdAllowed, bidder; + beforeEach(() => { + activityRules.isActivityAllowed.reset(); + activityRules.isActivityAllowed.callsFake((activity) => activity === ACTIVITY_TRANSMIT_UFPD ? transmitUfpdAllowed : true); + bidder = newBidder(spec); + spec.isBidRequestValid.returns(true); + }); + + it(`should be set to false when adapter sets browsingTopics = false`, () => { + transmitUfpdAllowed = true; spec.buildRequests.returns([ { method: 'GET', - url: '1', - }, - { - method: 'POST', - url: '2', - data: {} - }, - { - method: 'GET', - url: '3', - options: { - browsingTopics: true - } - }, - { - method: 'POST', - url: '4', - data: {}, + url: 'url', options: { - browsingTopics: true + browsingTopics: false } } ]); bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - ['1', '2', '3', '4'].forEach(url => { - sinon.assert.calledWith( - ajaxStub, - url, - sinon.match.any, - sinon.match.any, - sinon.match({browsingTopics: allow}) - ); + sinon.assert.calledWith(ajaxStub, 'url', sinon.match.any, sinon.match.any, sinon.match({ + browsingTopics: false + })); + }); + + Object.entries({ + 'allowed': true, + 'not allowed': false + }).forEach(([t, allow]) => { + it(`should be set to ${allow} when transmitUfpd is ${t}`, () => { + transmitUfpdAllowed = allow; + spec.buildRequests.returns([ + { + method: 'GET', + url: '1', + }, + { + method: 'POST', + url: '2', + data: {} + }, + { + method: 'GET', + url: '3', + options: { + browsingTopics: true + } + }, + { + method: 'POST', + url: '4', + data: {}, + options: { + browsingTopics: true + } + } + ]); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + ['1', '2', '3', '4'].forEach(url => { + sinon.assert.calledWith( + ajaxStub, + url, + sinon.match.any, + sinon.match.any, + sinon.match({browsingTopics: allow}) + ); + }); }); }); }); - }); - it('should not add bids for each placement code if no requests are given', function () { - const bidder = newBidder(spec); + it('should not add bids for each placement code if no requests are given', function () { + const bidder = newBidder(spec); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([]); - spec.interpretResponse.returns([]); - spec.getUserSyncs.returns([]); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns([]); + spec.interpretResponse.returns([]); + spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.callCount).to.equal(0); - }); + expect(addBidResponseStub.callCount).to.equal(0); + }); - it('should emit BEFORE_BIDDER_HTTP events before network requests', function () { - const bidder = newBidder(spec); - const req = { - method: 'POST', - url: 'test.url.com', - data: { arg: 2 } - }; + it('should emit BEFORE_BIDDER_HTTP events before network requests', function () { + const bidder = newBidder(spec); + const req = { + method: 'POST', + url: 'test.url.com', + data: { arg: 2 } + }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([req, req]); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns([req, req]); - const eventEmitterSpy = sinon.spy(events, 'emit'); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + const eventEmitterSpy = sinon.spy(events, 'emit'); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(ajaxStub.calledTwice).to.equal(true); - expect(eventEmitterSpy.getCalls() - .filter(call => call.args[0] === CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP) - ).to.length(2); + expect(ajaxStub.calledTwice).to.equal(true); + expect(eventEmitterSpy.getCalls() + .filter(call => call.args[0] === CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP) + ).to.length(2); - eventEmitterSpy.restore(); + eventEmitterSpy.restore(); + }); }); - }); - describe('when the ajax call succeeds', function () { - let ajaxStub; - let userSyncStub; - let logErrorSpy; + describe('when the ajax call succeeds', function () { + let ajaxStub; + let userSyncStub; + let logErrorSpy; - beforeEach(function () { - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { - const fakeResponse = sinon.stub(); - fakeResponse.returns('headerContent'); - callbacks.success('response body', { getResponseHeader: fakeResponse }); + beforeEach(function () { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callbacks.success('response body', { getResponseHeader: fakeResponse }); + }); + addBidResponseStub.reset(); + doneStub.resetBehavior(); + userSyncStub = sinon.stub(userSync, 'registerSync') + logErrorSpy = sinon.spy(utils, 'logError'); }); - addBidResponseStub.reset(); - doneStub.resetBehavior(); - userSyncStub = sinon.stub(userSync, 'registerSync') - logErrorSpy = sinon.spy(utils, 'logError'); - }); - afterEach(function () { - ajaxStub.restore(); - userSyncStub.restore(); - utils.logError.restore(); - }); + afterEach(function () { + ajaxStub.restore(); + userSyncStub.restore(); + utils.logError.restore(); + }); - it('should call spec.interpretResponse() with the response content', function () { - const bidder = newBidder(spec); + it('should call spec.interpretResponse() with the response content', function () { + const bidder = newBidder(spec); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(spec.interpretResponse.calledOnce).to.equal(true); + const response = spec.interpretResponse.firstCall.args[0] + expect(response.body).to.equal('response body') + expect(response.headers.get('some-header')).to.equal('headerContent'); + expect(spec.interpretResponse.firstCall.args[1]).to.deep.equal({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + expect(doneStub.calledOnce).to.equal(true); }); - spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should call spec.interpretResponse() once for each request made', function () { + const bidder = newBidder(spec); - expect(spec.interpretResponse.calledOnce).to.equal(true); - const response = spec.interpretResponse.firstCall.args[0] - expect(response.body).to.equal('response body') - expect(response.headers.get('some-header')).to.equal('headerContent'); - expect(spec.interpretResponse.firstCall.args[1]).to.deep.equal({ - method: 'POST', - url: 'test.url.com', - data: {} + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns([ + { + method: 'POST', + url: 'test.url.com', + data: {} + }, + { + method: 'POST', + url: 'test.url.com', + data: {} + }, + ]); + spec.getUserSyncs.returns([]); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(spec.interpretResponse.calledTwice).to.equal(true); + expect(doneStub.calledOnce).to.equal(true); }); - expect(doneStub.calledOnce).to.equal(true); - }); - it('should call spec.interpretResponse() once for each request made', function () { - const bidder = newBidder(spec); + it('should only add bids for valid adUnit code into the auction, even if the bidder doesn\'t bid on all of them', function () { + const bidder = newBidder(spec); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns([ - { + const bid = { + creativeId: 'creative-id', + requestId: '1', + ad: 'ad-url.com', + cpm: 0.5, + height: 200, + width: 300, + adUnitCode: 'mock/placement', + currency: 'USD', + netRevenue: true, + ttl: 300, + bidderCode: 'sampleBidder', + sampleBidder: {advertiserId: '12345', networkId: '111222'} + }; + const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); + bidderRequest.bids[0].bidder = 'sampleBidder'; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ method: 'POST', url: 'test.url.com', data: {} - }, - { + }); + spec.getUserSyncs.returns([]); + + spec.interpretResponse.returns(bid); + + bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + let bidObject = addBidResponseStub.firstCall.args[1]; + // checking the fields added by our code + expect(bidObject.originalCpm).to.equal(bid.cpm); + expect(bidObject.originalCurrency).to.equal(bid.currency); + expect(doneStub.calledOnce).to.equal(true); + expect(logErrorSpy.callCount).to.equal(0); + expect(bidObject.meta).to.exist; + expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); + }); + + it('should call spec.getUserSyncs() with the response', function () { + const bidder = newBidder(spec); + + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ method: 'POST', url: 'test.url.com', data: {} - }, - ]); - spec.getUserSyncs.returns([]); + }); + spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(spec.interpretResponse.calledTwice).to.equal(true); - expect(doneStub.calledOnce).to.equal(true); - }); + expect(spec.getUserSyncs.calledOnce).to.equal(true); + expect(spec.getUserSyncs.firstCall.args[1].length).to.equal(1); + expect(spec.getUserSyncs.firstCall.args[1][0].body).to.equal('response body'); + expect(spec.getUserSyncs.firstCall.args[1][0].headers).to.have.property('get'); + expect(spec.getUserSyncs.firstCall.args[1][0].headers.get).to.be.a('function'); + }); - it('should only add bids for valid adUnit code into the auction, even if the bidder doesn\'t bid on all of them', function () { - const bidder = newBidder(spec); + it('should register usersync pixels', function () { + const bidder = newBidder(spec); - const bid = { - creativeId: 'creative-id', - requestId: '1', - ad: 'ad-url.com', - cpm: 0.5, - height: 200, - width: 300, - adUnitCode: 'mock/placement', - currency: 'USD', - netRevenue: true, - ttl: 300, - bidderCode: 'sampleBidder', - sampleBidder: {advertiserId: '12345', networkId: '111222'} - }; - const bidderRequest = Object.assign({}, MOCK_BIDS_REQUEST); - bidderRequest.bids[0].bidder = 'sampleBidder'; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + spec.isBidRequestValid.returns(false); + spec.buildRequests.returns([]); + spec.getUserSyncs.returns([{ + type: 'iframe', + url: 'usersync.com' + }]); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(userSyncStub.called).to.equal(true); + expect(userSyncStub.firstCall.args[0]).to.equal('iframe'); + expect(userSyncStub.firstCall.args[1]).to.equal(spec.code); + expect(userSyncStub.firstCall.args[2]).to.equal('usersync.com'); }); - spec.getUserSyncs.returns([]); - spec.interpretResponse.returns(bid); + it('should logError and reject bid when required bid response params are missing', function () { + const bidder = newBidder(spec); - bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + const bid = { + requestId: '1', + ad: 'ad-url.com', + cpm: 0.5, + height: 200, + width: 300, + placementCode: 'mock/placement' + }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - let bidObject = addBidResponseStub.firstCall.args[1]; - // checking the fields added by our code - expect(bidObject.originalCpm).to.equal(bid.cpm); - expect(bidObject.originalCurrency).to.equal(bid.currency); - expect(doneStub.calledOnce).to.equal(true); - expect(logErrorSpy.callCount).to.equal(0); - expect(bidObject.meta).to.exist; - expect(bidObject.meta).to.deep.equal({advertiserId: '12345', networkId: '111222'}); - }); + spec.interpretResponse.returns(bid); - it('should call spec.getUserSyncs() with the response', function () { - const bidder = newBidder(spec); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + expect(logErrorSpy.calledOnce).to.equal(true); + expect(addBidResponseStub.reject.calledOnce).to.be.true; }); - spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should logError and reject bid when required response params are undefined', function () { + const bidder = newBidder(spec); - expect(spec.getUserSyncs.calledOnce).to.equal(true); - expect(spec.getUserSyncs.firstCall.args[1].length).to.equal(1); - expect(spec.getUserSyncs.firstCall.args[1][0].body).to.equal('response body'); - expect(spec.getUserSyncs.firstCall.args[1][0].headers).to.have.property('get'); - expect(spec.getUserSyncs.firstCall.args[1][0].headers.get).to.be.a('function'); - }); + const bid = { + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'requestId': '1', + 'creativeId': 'some-id', + 'currency': undefined, + 'netRevenue': true, + 'ttl': 360 + }; - it('should register usersync pixels', function () { - const bidder = newBidder(spec); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); - spec.isBidRequestValid.returns(false); - spec.buildRequests.returns([]); - spec.getUserSyncs.returns([{ - type: 'iframe', - url: 'usersync.com' - }]); + spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(userSyncStub.called).to.equal(true); - expect(userSyncStub.firstCall.args[0]).to.equal('iframe'); - expect(userSyncStub.firstCall.args[1]).to.equal(spec.code); - expect(userSyncStub.firstCall.args[2]).to.equal('usersync.com'); - }); + expect(logErrorSpy.calledOnce).to.equal(true); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + }); - it('should logError and reject bid when required bid response params are missing', function () { - const bidder = newBidder(spec); + it('should require requestId from interpretResponse', () => { + const bidder = newBidder(spec); + const bid = { + 'ad': 'creative', + 'cpm': '1.99', + 'creativeId': 'some-id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + spec.interpretResponse.returns(bid); - const bid = { - requestId: '1', - ad: 'ad-url.com', - cpm: 0.5, - height: 200, - width: 300, - placementCode: 'mock/placement' + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.called).to.be.false; + expect(addBidResponseStub.reject.calledOnce).to.be.true; + }); + }); + + describe('when the ajax call fails', function () { + let ajaxStub; + let callBidderErrorStub; + let eventEmitterStub; + let xhrErrorMock = { + status: 500, + statusText: 'Internal Server Error' }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + + beforeEach(function () { + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + callbacks.error('ajax call failed.', xhrErrorMock); + }); + callBidderErrorStub = sinon.stub(adapterManager, 'callBidderError'); + eventEmitterStub = sinon.stub(events, 'emit'); + addBidResponseStub.reset(); + doneStub.reset(); }); - spec.getUserSyncs.returns([]); - spec.interpretResponse.returns(bid); + afterEach(function () { + ajaxStub.restore(); + callBidderErrorStub.restore(); + eventEmitterStub.restore(); + }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should not spec.interpretResponse()', function () { + const bidder = newBidder(spec); - expect(logErrorSpy.calledOnce).to.equal(true); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - }); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); - it('should logError and reject bid when required response params are undefined', function () { - const bidder = newBidder(spec); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bid = { - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'requestId': '1', - 'creativeId': 'some-id', - 'currency': undefined, - 'netRevenue': true, - 'ttl': 360 - }; + expect(spec.interpretResponse.called).to.equal(false); + expect(doneStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); + expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); + expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); + sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + error: xhrErrorMock, + bidderRequest: MOCK_BIDS_REQUEST + }); + }); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + it('should not add bids for each adunit code into the auction', function () { + const bidder = newBidder(spec); + + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.interpretResponse.returns([]); + spec.getUserSyncs.returns([]); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(addBidResponseStub.callCount).to.equal(0); + expect(doneStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); + expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); + expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); + sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + error: xhrErrorMock, + bidderRequest: MOCK_BIDS_REQUEST + }); }); - spec.getUserSyncs.returns([]); - spec.interpretResponse.returns(bid); + it('should call spec.getUserSyncs() with no responses', function () { + const bidder = newBidder(spec); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); - expect(logErrorSpy.calledOnce).to.equal(true); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - }); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should require requestId from interpretResponse', () => { - const bidder = newBidder(spec); - const bid = { - 'ad': 'creative', - 'cpm': '1.99', - 'creativeId': 'some-id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 - }; - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} + expect(spec.getUserSyncs.calledOnce).to.equal(true); + expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); + expect(doneStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); + expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); + expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); + sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + error: xhrErrorMock, + bidderRequest: MOCK_BIDS_REQUEST + }); }); - spec.getUserSyncs.returns([]); - spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should call spec.getUserSyncs() with no responses', function () { + const bidder = newBidder(spec); - expect(addBidResponseStub.called).to.be.false; - expect(addBidResponseStub.reject.calledOnce).to.be.true; + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); + spec.getUserSyncs.returns([]); + + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(spec.getUserSyncs.calledOnce).to.equal(true); + expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); + expect(doneStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.calledOnce).to.equal(true); + expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); + expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); + expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); + sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + error: xhrErrorMock, + bidderRequest: MOCK_BIDS_REQUEST + }); + }); }); }); - describe('when the ajax call fails', function () { - let ajaxStub; - let callBidderErrorStub; - let eventEmitterStub; - let xhrErrorMock = { - status: 500, - statusText: 'Internal Server Error' - }; + describe('registerBidder', function () { + let registerBidAdapterStub; + let aliasBidAdapterStub; beforeEach(function () { - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { - callbacks.error('ajax call failed.', xhrErrorMock); - }); - callBidderErrorStub = sinon.stub(adapterManager, 'callBidderError'); - eventEmitterStub = sinon.stub(events, 'emit'); - addBidResponseStub.reset(); - doneStub.reset(); + registerBidAdapterStub = sinon.stub(adapterManager, 'registerBidAdapter'); + aliasBidAdapterStub = sinon.stub(adapterManager, 'aliasBidAdapter'); }); afterEach(function () { - ajaxStub.restore(); - callBidderErrorStub.restore(); - eventEmitterStub.restore(); + registerBidAdapterStub.restore(); + aliasBidAdapterStub.restore(); }); - it('should not spec.interpretResponse()', function () { - const bidder = newBidder(spec); + function newEmptySpec() { + return { + code: CODE, + isBidRequestValid: function() { }, + buildRequests: function() { }, + interpretResponse: function() { }, + }; + } - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} - }); - spec.getUserSyncs.returns([]); - - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(spec.interpretResponse.called).to.equal(false); - expect(doneStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); - expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); - expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { - error: xhrErrorMock, - bidderRequest: MOCK_BIDS_REQUEST - }); - }); + it('should register a bidder with the adapterManager', function () { + registerBidder(newEmptySpec()); + expect(registerBidAdapterStub.calledOnce).to.equal(true); + expect(registerBidAdapterStub.firstCall.args[0]).to.have.property('callBids'); + expect(registerBidAdapterStub.firstCall.args[0].callBids).to.be.a('function'); - it('should not add bids for each adunit code into the auction', function () { - const bidder = newBidder(spec); + expect(registerBidAdapterStub.firstCall.args[1]).to.equal(CODE); + expect(registerBidAdapterStub.firstCall.args[2]).to.be.undefined; + }); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} - }); - spec.interpretResponse.returns([]); - spec.getUserSyncs.returns([]); - - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(addBidResponseStub.callCount).to.equal(0); - expect(doneStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); - expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); - expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { - error: xhrErrorMock, - bidderRequest: MOCK_BIDS_REQUEST - }); + it('should register a bidder with the appropriate mediaTypes', function () { + const thisSpec = Object.assign(newEmptySpec(), { supportedMediaTypes: ['video'] }); + registerBidder(thisSpec); + expect(registerBidAdapterStub.calledOnce).to.equal(true); + expect(registerBidAdapterStub.firstCall.args[2]).to.deep.equal({supportedMediaTypes: ['video']}); }); - it('should call spec.getUserSyncs() with no responses', function () { - const bidder = newBidder(spec); + it('should register bidders with the appropriate aliases', function () { + const thisSpec = Object.assign(newEmptySpec(), { aliases: ['foo', 'bar'] }); + registerBidder(thisSpec); - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} - }); - spec.getUserSyncs.returns([]); - - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(spec.getUserSyncs.calledOnce).to.equal(true); - expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); - expect(doneStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); - expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); - expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { - error: xhrErrorMock, - bidderRequest: MOCK_BIDS_REQUEST - }); - }); + expect(registerBidAdapterStub.calledThrice).to.equal(true); - it('should call spec.getUserSyncs() with no responses', function () { - const bidder = newBidder(spec); + // Make sure our later calls don't override the bidder code from previous calls. + expect(registerBidAdapterStub.firstCall.args[0].getBidderCode()).to.equal(CODE); + expect(registerBidAdapterStub.secondCall.args[0].getBidderCode()).to.equal('foo') + expect(registerBidAdapterStub.thirdCall.args[0].getBidderCode()).to.equal('bar') - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} - }); - spec.getUserSyncs.returns([]); - - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(spec.getUserSyncs.calledOnce).to.equal(true); - expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); - expect(doneStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.calledOnce).to.equal(true); - expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); - expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); - expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { - error: xhrErrorMock, - bidderRequest: MOCK_BIDS_REQUEST - }); + expect(registerBidAdapterStub.firstCall.args[1]).to.equal(CODE); + expect(registerBidAdapterStub.secondCall.args[1]).to.equal('foo') + expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') }); - }); -}); -describe('registerBidder', function () { - let registerBidAdapterStub; - let aliasBidAdapterStub; + it('should register alias with their gvlid', function() { + const aliases = [ + { + code: 'foo', + gvlid: 1 + }, + { + code: 'bar', + gvlid: 2 + }, + { + code: 'baz' + } + ] + const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); + registerBidder(thisSpec); - beforeEach(function () { - registerBidAdapterStub = sinon.stub(adapterManager, 'registerBidAdapter'); - aliasBidAdapterStub = sinon.stub(adapterManager, 'aliasBidAdapter'); - }); + expect(registerBidAdapterStub.getCall(1).args[0].getSpec().gvlid).to.equal(1); + expect(registerBidAdapterStub.getCall(2).args[0].getSpec().gvlid).to.equal(2); + expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); + }) - afterEach(function () { - registerBidAdapterStub.restore(); - aliasBidAdapterStub.restore(); - }); + it('should register alias with skipPbsAliasing', function() { + const aliases = [ + { + code: 'foo', + skipPbsAliasing: true + }, + { + code: 'bar', + skipPbsAliasing: false + }, + { + code: 'baz' + } + ] + const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); + registerBidder(thisSpec); - function newEmptySpec() { - return { - code: CODE, - isBidRequestValid: function() { }, - buildRequests: function() { }, - interpretResponse: function() { }, - }; - } - - it('should register a bidder with the adapterManager', function () { - registerBidder(newEmptySpec()); - expect(registerBidAdapterStub.calledOnce).to.equal(true); - expect(registerBidAdapterStub.firstCall.args[0]).to.have.property('callBids'); - expect(registerBidAdapterStub.firstCall.args[0].callBids).to.be.a('function'); - - expect(registerBidAdapterStub.firstCall.args[1]).to.equal(CODE); - expect(registerBidAdapterStub.firstCall.args[2]).to.be.undefined; - }); + expect(registerBidAdapterStub.getCall(1).args[0].getSpec().skipPbsAliasing).to.equal(true); + expect(registerBidAdapterStub.getCall(2).args[0].getSpec().skipPbsAliasing).to.equal(false); + expect(registerBidAdapterStub.getCall(3).args[0].getSpec().skipPbsAliasing).to.equal(undefined); + }) + }) - it('should register a bidder with the appropriate mediaTypes', function () { - const thisSpec = Object.assign(newEmptySpec(), { supportedMediaTypes: ['video'] }); - registerBidder(thisSpec); - expect(registerBidAdapterStub.calledOnce).to.equal(true); - expect(registerBidAdapterStub.firstCall.args[2]).to.deep.equal({supportedMediaTypes: ['video']}); - }); + describe('validate bid response: ', function () { + let spec; + let indexStub, adUnits, bidderRequests; + let addBidResponseStub; + let doneStub; + let ajaxStub; + let logErrorSpy; + + let bids = [{ + 'ad': 'creative', + 'cpm': '1.99', + 'width': 300, + 'height': 250, + 'requestId': '1', + 'creativeId': 'some-id', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 360 + }]; + + beforeEach(function () { + spec = { + code: CODE, + isBidRequestValid: sinon.stub(), + buildRequests: sinon.stub(), + interpretResponse: sinon.stub(), + }; - it('should register bidders with the appropriate aliases', function () { - const thisSpec = Object.assign(newEmptySpec(), { aliases: ['foo', 'bar'] }); - registerBidder(thisSpec); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({ + method: 'POST', + url: 'test.url.com', + data: {} + }); - expect(registerBidAdapterStub.calledThrice).to.equal(true); + addBidResponseStub = sinon.stub(); + addBidResponseStub.reject = sinon.stub(); + doneStub = sinon.stub(); + ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { + const fakeResponse = sinon.stub(); + fakeResponse.returns('headerContent'); + callbacks.success('response body', { getResponseHeader: fakeResponse }); + }); + logErrorSpy = sinon.spy(utils, 'logError'); + indexStub = sinon.stub(auctionManager, 'index'); + adUnits = []; + bidderRequests = []; + indexStub.get(() => stubAuctionIndex({adUnits: adUnits, bidderRequests: bidderRequests})) + }); - // Make sure our later calls don't override the bidder code from previous calls. - expect(registerBidAdapterStub.firstCall.args[0].getBidderCode()).to.equal(CODE); - expect(registerBidAdapterStub.secondCall.args[0].getBidderCode()).to.equal('foo') - expect(registerBidAdapterStub.thirdCall.args[0].getBidderCode()).to.equal('bar') + afterEach(function () { + ajaxStub.restore(); + logErrorSpy.restore(); + indexStub.restore; + }); - expect(registerBidAdapterStub.firstCall.args[1]).to.equal(CODE); - expect(registerBidAdapterStub.secondCall.args[1]).to.equal('foo') - expect(registerBidAdapterStub.thirdCall.args[1]).to.equal('bar') - }); + if (FEATURES.NATIVE) { + it('should add native bids that do have required assets', function () { + adUnits = [{ + transactionId: 'au', + nativeParams: { + title: {'required': true}, + } + }] + decorateAdUnitsWithNativeParams(adUnits); + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; - it('should register alias with their gvlid', function() { - const aliases = [ - { - code: 'foo', - gvlid: 1 - }, - { - code: 'bar', - gvlid: 2 - }, - { - code: 'baz' - } - ] - const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); - registerBidder(thisSpec); + let bids1 = Object.assign({}, + bids[0], + { + 'mediaType': 'native', + 'native': { + 'title': 'Native Creative', + 'clickUrl': 'https://www.link.example', + } + } + ); - expect(registerBidAdapterStub.getCall(1).args[0].getSpec().gvlid).to.equal(1); - expect(registerBidAdapterStub.getCall(2).args[0].getSpec().gvlid).to.equal(2); - expect(registerBidAdapterStub.getCall(3).args[0].getSpec().gvlid).to.equal(undefined); - }) + const bidder = newBidder(spec); - it('should register alias with skipPbsAliasing', function() { - const aliases = [ - { - code: 'foo', - skipPbsAliasing: true - }, - { - code: 'bar', - skipPbsAliasing: false - }, - { - code: 'baz' - } - ] - const thisSpec = Object.assign(newEmptySpec(), { aliases: aliases }); - registerBidder(thisSpec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(registerBidAdapterStub.getCall(1).args[0].getSpec().skipPbsAliasing).to.equal(true); - expect(registerBidAdapterStub.getCall(2).args[0].getSpec().skipPbsAliasing).to.equal(false); - expect(registerBidAdapterStub.getCall(3).args[0].getSpec().skipPbsAliasing).to.equal(undefined); - }) -}) + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); + }); -describe('validate bid response: ', function () { - let spec; - let indexStub, adUnits, bidderRequests; - let addBidResponseStub; - let doneStub; - let ajaxStub; - let logErrorSpy; - - let bids = [{ - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'requestId': '1', - 'creativeId': 'some-id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 - }]; - - beforeEach(function () { - spec = { - code: CODE, - isBidRequestValid: sinon.stub(), - buildRequests: sinon.stub(), - interpretResponse: sinon.stub(), - }; - - spec.isBidRequestValid.returns(true); - spec.buildRequests.returns({ - method: 'POST', - url: 'test.url.com', - data: {} - }); + it('should not add native bids that do not have required assets', function () { + adUnits = [{ + transactionId: 'au', + nativeParams: { + title: {'required': true}, + }, + }]; + decorateAdUnitsWithNativeParams(adUnits); + let bidRequest = { + bids: [{ + bidId: '1', + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + params: { + param: 5 + }, + mediaType: 'native', + }] + }; + let bids1 = Object.assign({}, + bids[0], + { + bidderCode: CODE, + mediaType: 'native', + native: { + title: undefined, + clickUrl: 'https://www.link.example', + } + } + ); - addBidResponseStub = sinon.stub(); - addBidResponseStub.reject = sinon.stub(); - doneStub = sinon.stub(); - ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { - const fakeResponse = sinon.stub(); - fakeResponse.returns('headerContent'); - callbacks.success('response body', { getResponseHeader: fakeResponse }); - }); - logErrorSpy = sinon.spy(utils, 'logError'); - indexStub = sinon.stub(auctionManager, 'index'); - adUnits = []; - bidderRequests = []; - indexStub.get(() => stubAuctionIndex({adUnits: adUnits, bidderRequests: bidderRequests})) - }); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - afterEach(function () { - ajaxStub.restore(); - logErrorSpy.restore(); - indexStub.restore; - }); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + expect(logErrorSpy.calledWithMatch('Ignoring bid: Native bid missing some required properties.')).to.equal(true); + }); + } - if (FEATURES.NATIVE) { - it('should add native bids that do have required assets', function () { + it('should add bid when renderer is present on outstream bids', function () { adUnits = [{ transactionId: 'au', - nativeParams: { - title: {'required': true}, + mediaTypes: { + video: {context: 'outstream'} } }] - decorateAdUnitsWithNativeParams(adUnits); let bidRequest = { bids: [{ bidId: '1', auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', transactionId: 'au', + adUnitCode: 'mock/placement', params: { param: 5 }, - mediaType: 'native', }] }; let bids1 = Object.assign({}, bids[0], { - 'mediaType': 'native', - 'native': { - 'title': 'Native Creative', - 'clickUrl': 'https://www.link.example', - } + bidderCode: CODE, + mediaType: 'video', + renderer: {render: () => true, url: 'render.js'}, } ); @@ -1095,413 +1176,335 @@ describe('validate bid response: ', function () { expect(logErrorSpy.callCount).to.equal(0); }); - it('should not add native bids that do not have required assets', function () { - adUnits = [{ - transactionId: 'au', - nativeParams: { - title: {'required': true}, - }, - }]; - decorateAdUnitsWithNativeParams(adUnits); + it('should add banner bids that have no width or height but single adunit size', function () { let bidRequest = { bids: [{ + bidder: CODE, bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', - transactionId: 'au', params: { param: 5 }, - mediaType: 'native', + sizes: [[300, 250]], }] }; + bidderRequests = [bidRequest]; let bids1 = Object.assign({}, bids[0], { - bidderCode: CODE, - mediaType: 'native', - native: { - title: undefined, - clickUrl: 'https://www.link.example', - } + width: undefined, + height: undefined } ); const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - expect(logErrorSpy.calledWithMatch('Ignoring bid: Native bid missing some required properties.')).to.equal(true); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + expect(logErrorSpy.callCount).to.equal(0); }); - } - - it('should add bid when renderer is present on outstream bids', function () { - adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: {context: 'outstream'} - } - }] - let bidRequest = { - bids: [{ - bidId: '1', - auctionId: 'first-bid-id', - transactionId: 'au', - adUnitCode: 'mock/placement', - params: { - param: 5 - }, - }] - }; - - let bids1 = Object.assign({}, - bids[0], - { - bidderCode: CODE, - mediaType: 'video', - renderer: {render: () => true, url: 'render.js'}, - } - ); - - const bidder = newBidder(spec); - - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - expect(logErrorSpy.callCount).to.equal(0); - }); - - it('should add banner bids that have no width or height but single adunit size', function () { - let bidRequest = { - bids: [{ - bidder: CODE, - bidId: '1', - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', - params: { - param: 5 - }, - sizes: [[300, 250]], - }] - }; - bidderRequests = [bidRequest]; - let bids1 = Object.assign({}, - bids[0], - { - width: undefined, - height: undefined - } - ); - - const bidder = newBidder(spec); - - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - expect(logErrorSpy.callCount).to.equal(0); - }); - - it('should disregard auctionId/transactionId set by the adapter', () => { - let bidderRequest = { - bids: [{ - bidder: CODE, - bidId: '1', - auctionId: 'aid', - transactionId: 'tid', - adUnitCode: 'au', - }] - }; - const bidder = newBidder(spec); - spec.interpretResponse.returns(Object.assign({}, bids[0], {transactionId: 'ignored', auctionId: 'ignored'})); - bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - sinon.assert.calledWith(addBidResponseStub, sinon.match.any, sinon.match({ - transactionId: 'tid', - auctionId: 'aid' - })); - }) - - describe(' Check for alternateBiddersList ', function() { - let bidRequest; - let bids1; - let logWarnSpy; - let bidderSettingStub, aliasRegistryStub; - let aliasRegistry; - beforeEach(function () { - bidRequest = { + it('should disregard auctionId/transactionId set by the adapter', () => { + let bidderRequest = { bids: [{ - bidId: '1', bidder: CODE, - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', - transactionId: 'au', + bidId: '1', + auctionId: 'aid', + transactionId: 'tid', + adUnitCode: 'au', }] }; + const bidder = newBidder(spec); + spec.interpretResponse.returns(Object.assign({}, bids[0], {transactionId: 'ignored', auctionId: 'ignored'})); + bidder.callBids(bidderRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, sinon.match.any, sinon.match({ + transactionId: 'tid', + auctionId: 'aid' + })); + }) - bids1 = Object.assign({}, - bids[0], - { - bidderCode: 'validalternatebidder', - adapterCode: 'knownadapter1' - } - ); - logWarnSpy = sinon.spy(utils, 'logWarn'); - bidderSettingStub = sinon.stub(bidderSettings, 'get'); - aliasRegistry = {}; - aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry'); - aliasRegistryStub.get(() => aliasRegistry); - }); + describe(' Check for alternateBiddersList ', function() { + let bidRequest; + let bids1; + let logWarnSpy; + let bidderSettingStub, aliasRegistryStub; + let aliasRegistry; - afterEach(function () { - logWarnSpy.restore(); - bidderSettingStub.restore(); - aliasRegistryStub.restore(); - }); + beforeEach(function () { + bidRequest = { + bids: [{ + bidId: '1', + bidder: CODE, + auctionId: 'first-bid-id', + adUnitCode: 'mock/placement', + transactionId: 'au', + }] + }; - it('should log warning when bidder is unknown and allowAlternateBidderCodes flag is false', function () { - bidderSettingStub.returns(false); + bids1 = Object.assign({}, + bids[0], + { + bidderCode: 'validalternatebidder', + adapterCode: 'knownadapter1' + } + ); + logWarnSpy = sinon.spy(utils, 'logWarn'); + bidderSettingStub = sinon.stub(bidderSettings, 'get'); + aliasRegistry = {}; + aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry'); + aliasRegistryStub.get(() => aliasRegistry); + }); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + afterEach(function () { + logWarnSpy.restore(); + bidderSettingStub.restore(); + aliasRegistryStub.restore(); + }); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - expect(logWarnSpy.callCount).to.equal(1); - }); + it('should log warning when bidder is unknown and allowAlternateBidderCodes flag is false', function () { + bidderSettingStub.returns(false); - it('should reject the bid, when allowAlternateBidderCodes flag is undefined (default should be false)', function () { - bidderSettingStub.returns(undefined); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + expect(logWarnSpy.callCount).to.equal(1); + }); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - }); + it('should reject the bid, when allowAlternateBidderCodes flag is undefined (default should be false)', function () { + bidderSettingStub.returns(undefined); - it('should log warning when the particular bidder is not specified in allowedAlternateBidderCodes and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['invalidAlternateBidder02']); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + }); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - expect(logWarnSpy.callCount).to.equal(1); - }); + it('should log warning when the particular bidder is not specified in allowedAlternateBidderCodes and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['invalidAlternateBidder02']); - it('should accept the bid, when allowedAlternateBidderCodes is empty and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + expect(logWarnSpy.callCount).to.equal(1); + }); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); - expect(logErrorSpy.callCount).to.equal(0); - }); + it('should accept the bid, when allowedAlternateBidderCodes is empty and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(); - it('should accept the bid, when allowedAlternateBidderCodes is marked as * and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['*']); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + expect(logErrorSpy.callCount).to.equal(0); + }); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); - expect(logErrorSpy.callCount).to.equal(0); - }); + it('should accept the bid, when allowedAlternateBidderCodes is marked as * and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['*']); - it('should accept the bid, when allowedAlternateBidderCodes is marked as * (with space) and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([' * ']); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + expect(logErrorSpy.callCount).to.equal(0); + }); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); - expect(logErrorSpy.callCount).to.equal(0); - }); + it('should accept the bid, when allowedAlternateBidderCodes is marked as * (with space) and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([' * ']); + + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should not accept the bid, when allowedAlternateBidderCodes is marked as empty array and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([]); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + expect(logErrorSpy.callCount).to.equal(0); + }); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should not accept the bid, when allowedAlternateBidderCodes is marked as empty array and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns([]); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - expect(logWarnSpy.callCount).to.equal(1); - }); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should accept the bid, when allowedAlternateBidderCodes contains bidder name and allowAlternateBidderCodes flag is true', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); - bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['validAlternateBidder']); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + expect(logWarnSpy.callCount).to.equal(1); + }); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should accept the bid, when allowedAlternateBidderCodes contains bidder name and allowAlternateBidderCodes flag is true', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(true); + bidderSettingStub.withArgs(CODE, 'allowedAlternateBidderCodes').returns(['validAlternateBidder']); - expect(addBidResponseStub.called).to.equal(true); - expect(logWarnSpy.callCount).to.equal(0); - expect(logErrorSpy.callCount).to.equal(0); - }); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should not accept the bid, when bidder is an alias but bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(false); - aliasRegistry = {'validAlternateBidder': CODE}; + expect(addBidResponseStub.called).to.equal(true); + expect(logWarnSpy.callCount).to.equal(0); + expect(logErrorSpy.callCount).to.equal(0); + }); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should not accept the bid, when bidder is an alias but bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(false); + aliasRegistry = {'validAlternateBidder': CODE}; - expect(addBidResponseStub.called).to.equal(false); - expect(logWarnSpy.callCount).to.equal(1); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - }); + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should not accept the bid, when bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { - bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(false); + expect(addBidResponseStub.called).to.equal(false); + expect(logWarnSpy.callCount).to.equal(1); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + }); - const bidder = newBidder(spec); - spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + it('should not accept the bid, when bidderSetting is missing for the bidder. It should fallback to standard setting and reject the bid', function () { + bidderSettingStub.withArgs(CODE, 'allowAlternateBidderCodes').returns(false); + + const bidder = newBidder(spec); + spec.interpretResponse.returns(bids1); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.called).to.equal(false); - expect(addBidResponseStub.reject.calledOnce).to.be.true; - expect(logWarnSpy.callCount).to.equal(1); + expect(addBidResponseStub.called).to.equal(false); + expect(addBidResponseStub.reject.calledOnce).to.be.true; + expect(logWarnSpy.callCount).to.equal(1); + }); }); - }); - describe('when interpretResponse returns BidderAuctionResponse', function() { - const bidRequest = { - bids: [{ + describe('when interpretResponse returns BidderAuctionResponse', function() { + const bidRequest = { + auctionId: 'aid', + bids: [{ + bidId: '1', + bidder: CODE, + auctionId: 'aid', + adUnitCode: 'mock/placement', + transactionId: 'au', + }] + }; + const fledgeAuctionConfig = { bidId: '1', - bidder: CODE, - auctionId: 'first-bid-id', - adUnitCode: 'mock/placement', - transactionId: 'au', - }] - }; - const fledgeAuctionConfig = { - bidId: '1', - config: { - foo: 'bar' - } - } - describe('when response has FLEDGE auction config', function() { - let fledgeStub; - - function fledgeHook(next, ...args) { - fledgeStub(...args); + config: { + foo: 'bar' + } } + describe('when response has FLEDGE auction config', function() { + let fledgeStub; - before(() => { - addComponentAuction.before(fledgeHook); - }); + function fledgeHook(next, ...args) { + fledgeStub(...args); + } - after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); - }) + before(() => { + addComponentAuction.before(fledgeHook); + }); - beforeEach(function () { - fledgeStub = sinon.stub(); - }); + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) - it('should unwrap bids', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [] + beforeEach(function () { + fledgeStub = sinon.stub(); }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }); - it('should call fledgeManager with FLEDGE configs', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [fledgeAuctionConfig] + it('should unwrap bids', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + fledgeAuctionConfigs: [] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(fledgeStub.calledOnce).to.equal(true); - sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }) + it('should call fledgeManager with FLEDGE configs', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + fledgeAuctionConfigs: [fledgeAuctionConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: [], - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + expect(fledgeStub.calledOnce).to.equal(true); + sinon.assert.calledWith(fledgeStub, bidRequest.auctionId, 'mock/placement', fledgeAuctionConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }) - expect(fledgeStub.calledOnce).to.be.true; - sinon.assert.calledWith(fledgeStub, 'mock/placement', fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(false); + it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: [], + fledgeAuctionConfigs: [fledgeAuctionConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(fledgeStub.calledOnce).to.be.true; + sinon.assert.calledWith(fledgeStub, bidRequest.auctionId, 'mock/placement', fledgeAuctionConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(false); + }) }) }) - }) -}); + }); -describe('bid response isValid', () => { - describe('size check', () => { - let req, index; + describe('bid response isValid', () => { + describe('size check', () => { + let req, index; - beforeEach(() => { - req = { - ...MOCK_BIDS_REQUEST.bids[0], - mediaTypes: { - banner: { - sizes: [[1, 2], [3, 4]] + beforeEach(() => { + req = { + ...MOCK_BIDS_REQUEST.bids[0], + mediaTypes: { + banner: { + sizes: [[1, 2], [3, 4]] + } } } - } - }); + }); - function mkResponse(width, height) { - return { - requestId: req.bidId, - width, - height, - cpm: 1, - ttl: 60, - creativeId: '123', - netRevenue: true, - currency: 'USD', - mediaType: 'banner', + function mkResponse(width, height) { + return { + requestId: req.bidId, + width, + height, + cpm: 1, + ttl: 60, + creativeId: '123', + netRevenue: true, + currency: 'USD', + mediaType: 'banner', + } } - } - function checkValid(bid) { - return isValid('au', bid, {index: stubAuctionIndex({bidRequests: [req]})}); - } + function checkValid(bid) { + return isValid('au', bid, {index: stubAuctionIndex({bidRequests: [req]})}); + } - it('should succeed when response has a size that was in request', () => { - expect(checkValid(mkResponse(3, 4))).to.be.true; - }); - }) -}); + it('should succeed when response has a size that was in request', () => { + expect(checkValid(mkResponse(3, 4))).to.be.true; + }); + }) + }); +}) diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index f16d6208087..4716e5749cb 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1,13 +1,19 @@ -import { expect } from 'chai'; -import { targeting as targetingInstance, filters, getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm } from 'src/targeting.js'; -import { config } from 'src/config.js'; -import { createBidReceived } from 'test/fixtures/fixtures.js'; +import {expect} from 'chai'; +import { + filters, + getHighestCpmBidsFromBidPool, + sortByDealAndPriceBucketOrCpm, + targeting as targetingInstance +} from 'src/targeting.js'; +import {config} from 'src/config.js'; +import {createBidReceived} from 'test/fixtures/fixtures.js'; import CONSTANTS from 'src/constants.json'; -import { auctionManager } from 'src/auctionManager.js'; +import {auctionManager} from 'src/auctionManager.js'; import * as utils from 'src/utils.js'; import {deepClone} from 'src/utils.js'; import {createBid} from '../../../../src/bidfactory.js'; import {hook} from '../../../../src/hook.js'; +import {getHighestCpm} from '../../../../src/utils/reducers.js'; function mkBid(bid, status = CONSTANTS.STATUS.GOOD) { return Object.assign(createBid(status), bid); @@ -451,7 +457,7 @@ describe('targeting tests', function () { } }); - const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, 2); expect(bids.length).to.equal(3); expect(bids[0].adId).to.equal('8383838'); @@ -467,7 +473,7 @@ describe('targeting tests', function () { } }); - const bids = getHighestCpmBidsFromBidPool(bidsReceived, utils.getHighestCpm, 2); + const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, 2); expect(bids.length).to.equal(3); expect(bids[0].adId).to.equal('8383838'); diff --git a/test/spec/unit/utils/reducers_spec.js b/test/spec/unit/utils/reducers_spec.js new file mode 100644 index 00000000000..95bf3b74041 --- /dev/null +++ b/test/spec/unit/utils/reducers_spec.js @@ -0,0 +1,124 @@ +import { + tiebreakCompare, + keyCompare, + simpleCompare, + minimum, + maximum, + getHighestCpm, + getOldestHighestCpmBid, getLatestHighestCpmBid, reverseCompare +} from '../../../../src/utils/reducers.js'; +import assert from 'assert'; + +describe('reducers', () => { + describe('simpleCompare', () => { + Object.entries({ + '<': [10, 20, -1], + '===': [123, 123, 0], + '>': [30, -10, 1] + }).forEach(([t, [a, b, expected]]) => { + it(`returns ${expected} when a ${t} b`, () => { + expect(simpleCompare(a, b)).to.equal(expected); + }) + }) + }); + + describe('keyCompare', () => { + Object.entries({ + '<': [{k: -123}, {k: 0}, -1], + '===': [{k: 0}, {k: 0}, 0], + '>': [{k: 2}, {k: 1}, 1] + }).forEach(([t, [a, b, expected]]) => { + it(`returns ${expected} when key(a) ${t} key(b)`, () => { + expect(keyCompare(item => item.k)(a, b)).to.equal(expected); + }) + }) + }); + + describe('tiebreakCompare', () => { + Object.entries({ + 'first compare says a < b': [{main: 1, tie: 2}, {main: 2, tie: 1}, -1], + 'first compare says a > b': [{main: 2, tie: 1}, {main: 1, tie: 2}, 1], + 'first compare ties, second says a < b': [{main: 0, tie: 1}, {main: 0, tie: 2}, -1], + 'first compare ties, second says a > b': [{main: 0, tie: 2}, {main: 0, tie: 1}, 1], + 'all compares tie': [{main: 0, tie: 0}, {main: 0, tie: 0}, 0] + }).forEach(([t, [a, b, expected]]) => { + it(`should return ${expected} when ${t}`, () => { + const cmp = tiebreakCompare(keyCompare(item => item.main), keyCompare(item => item.tie)); + expect(cmp(a, b)).to.equal(expected); + }) + }) + }); + + const SAMPLE_ARR = [-10, 20, 20, 123, 400]; + + Object.entries({ + 'minimum': [minimum, ['minimum', -10], ['maximum', 400]], + 'maximum': [maximum, ['maximum', 400], ['minimum', -10]] + }).forEach(([t, [fn, simple, reversed]]) => { + describe(t, () => { + it(`should find ${simple[0]} using simple compare`, () => { + expect(SAMPLE_ARR.reduce(fn(simpleCompare))).to.equal(simple[1]); + }); + it(`should find ${reversed[0]} using reverse compare`, () => { + expect(SAMPLE_ARR.reduce(fn(reverseCompare()))).to.equal(reversed[1]); + }); + }) + }); + + describe('getHighestCpm', function () { + it('should pick the highest cpm', function () { + let a = { + cpm: 2, + timeToRespond: 100 + }; + let b = { + cpm: 1, + timeToRespond: 100 + }; + expect(getHighestCpm(a, b)).to.eql(a); + expect(getHighestCpm(b, a)).to.eql(a); + }); + + it('should pick the lowest timeToRespond cpm in case of tie', function () { + let a = { + cpm: 1, + timeToRespond: 100 + }; + let b = { + cpm: 1, + timeToRespond: 50 + }; + expect(getHighestCpm(a, b)).to.eql(b); + expect(getHighestCpm(b, a)).to.eql(b); + }); + }); + + describe('getOldestHighestCpmBid', () => { + it('should pick the oldest in case of tie using responseTimeStamp', function () { + let a = { + cpm: 1, + responseTimestamp: 1000 + }; + let b = { + cpm: 1, + responseTimestamp: 2000 + }; + expect(getOldestHighestCpmBid(a, b)).to.eql(a); + expect(getOldestHighestCpmBid(b, a)).to.eql(a); + }); + }); + describe('getLatestHighestCpmBid', () => { + it('should pick the latest in case of tie using responseTimeStamp', function () { + let a = { + cpm: 1, + responseTimestamp: 1000 + }; + let b = { + cpm: 1, + responseTimestamp: 2000 + }; + expect(getLatestHighestCpmBid(a, b)).to.eql(b); + expect(getLatestHighestCpmBid(b, a)).to.eql(b); + }); + }); +}) diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 6e7f8ba0e8f..40126f7f20c 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -2,6 +2,7 @@ import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; import {expect} from 'chai'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; +import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; import {binarySearch, deepEqual, memoize, waitForElementToLoad} from 'src/utils.js'; var assert = require('assert'); @@ -538,72 +539,6 @@ describe('Utils', function () { }); }); - describe('getHighestCpm', function () { - it('should pick the existing highest cpm', function () { - let previous = { - cpm: 2, - timeToRespond: 100 - }; - let current = { - cpm: 1, - timeToRespond: 100 - }; - assert.equal(utils.getHighestCpm(previous, current), previous); - }); - - it('should pick the new highest cpm', function () { - let previous = { - cpm: 1, - timeToRespond: 100 - }; - let current = { - cpm: 2, - timeToRespond: 100 - }; - assert.equal(utils.getHighestCpm(previous, current), current); - }); - - it('should pick the fastest cpm in case of tie', function () { - let previous = { - cpm: 1, - timeToRespond: 100 - }; - let current = { - cpm: 1, - timeToRespond: 50 - }; - assert.equal(utils.getHighestCpm(previous, current), current); - }); - - it('should pick the oldest in case of tie using responseTimeStamp', function () { - let previous = { - cpm: 1, - timeToRespond: 100, - responseTimestamp: 1000 - }; - let current = { - cpm: 1, - timeToRespond: 50, - responseTimestamp: 2000 - }; - assert.equal(utils.getOldestHighestCpmBid(previous, current), previous); - }); - - it('should pick the latest in case of tie using responseTimeStamp', function () { - let previous = { - cpm: 1, - timeToRespond: 100, - responseTimestamp: 1000 - }; - let current = { - cpm: 1, - timeToRespond: 50, - responseTimestamp: 2000 - }; - assert.equal(utils.getLatestHighestCpmBid(previous, current), current); - }); - }); - describe('polyfill test', function () { it('should not add polyfill to array', function() { var arr = ['hello', 'world']; From 425cf20b55c8b1cccae5cc66157ce7154d091c94 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 14 Sep 2023 12:01:42 -0700 Subject: [PATCH 025/760] userId: group UIDs by source when possible (#10488) --- modules/userId/eids.js | 37 ++++++----------- test/spec/modules/userId_spec.js | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index dc362b41136..aad570f20df 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -32,34 +32,23 @@ function createEidObject(userIdData, subModuleKey) { return null; } -// this function will generate eids array for all available IDs in bidRequest.userId -// this function will be called by userId module -// if any adapter does not want any particular userId to be passed then adapter can use Array.filter(e => e.source != 'tdid') export function createEidsArray(bidRequestUserId) { - let eids = []; - - for (const subModuleKey in bidRequestUserId) { - if (bidRequestUserId.hasOwnProperty(subModuleKey)) { - if (subModuleKey === 'pubProvidedId') { - eids = eids.concat(bidRequestUserId['pubProvidedId']); - } else if (Array.isArray(bidRequestUserId[subModuleKey])) { - bidRequestUserId[subModuleKey].forEach((config, index, arr) => { - const eid = createEidObject(config, subModuleKey); - - if (eid) { - eids.push(eid); - } - }) - } else { - const eid = createEidObject(bidRequestUserId[subModuleKey], subModuleKey); - if (eid) { - eids.push(eid); - } - } + const allEids = {}; + function collect(eid) { + const key = JSON.stringify([eid.source?.toLowerCase(), eid.ext]); + if (allEids.hasOwnProperty(key)) { + allEids[key].uids.push(...eid.uids); + } else { + allEids[key] = eid; } } - return eids; + Object.entries(bidRequestUserId).forEach(([name, values]) => { + values = Array.isArray(values) ? values : [values]; + const eids = name === 'pubProvidedId' ? values : values.map(value => createEidObject(value, name)); + eids.filter(eid => eid != null).forEach(collect); + }) + return Object.values(allEids); } /** diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 6a2d259fdd7..17a865796a2 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -382,6 +382,75 @@ describe('User ID', function () { }); }); + describe('createEidsArray', () => { + beforeEach(() => { + init(config); + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1', null, null, + {'mockId1': {source: 'mock1source', atype: 1}}), + createMockIdSubmodule('mockId2v1', null, null, + {'mockId2v1': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 1})}}), + createMockIdSubmodule('mockId2v2', null, null, + {'mockId2v2': {source: 'mock2source', atype: 2, getEidExt: () => ({v: 2})}}), + ]); + }); + + it('should group UIDs by source and ext', () => { + const eids = createEidsArray({ + mockId1: ['mock-1-1', 'mock-1-2'], + mockId2v1: ['mock-2-1', 'mock-2-2'], + mockId2v2: ['mock-2-1', 'mock-2-2'] + }); + expect(eids).to.eql([ + { + source: 'mock1source', + uids: [ + { + id: 'mock-1-1', + atype: 1, + }, + { + id: 'mock-1-2', + atype: 1, + } + ] + }, + { + source: 'mock2source', + ext: { + v: 1 + }, + uids: [ + { + id: 'mock-2-1', + atype: 2, + }, + { + id: 'mock-2-2', + atype: 2, + } + ] + }, + { + source: 'mock2source', + ext: { + v: 2 + }, + uids: [ + { + id: 'mock-2-1', + atype: 2, + }, + { + id: 'mock-2-2', + atype: 2, + } + ] + } + ]) + }) + }) + it('pbjs.getUserIds', function (done) { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); From 7810865a1841374e3495b134d14596da4418938d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 15 Sep 2023 06:05:07 -0700 Subject: [PATCH 026/760] integration examples: add native example without adserver (#10490) --- .../gpt/prebidServer_native_example.html | 2 +- .../noadserver/native_noadserver.html | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100755 integrationExamples/noadserver/native_noadserver.html diff --git a/integrationExamples/gpt/prebidServer_native_example.html b/integrationExamples/gpt/prebidServer_native_example.html index c590f0bcee5..a5fb0ffa894 100644 --- a/integrationExamples/gpt/prebidServer_native_example.html +++ b/integrationExamples/gpt/prebidServer_native_example.html @@ -114,7 +114,7 @@ s2sConfig: { accountId: '1', enabled: true, //default value set to false - bidders: ['appnexus'], + bidders: ['appnexuspsp'], timeout: 1000, //default value is 1000 adapter: 'prebidServer', //if we have any other s2s adapter, default value is s2s endpoint: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' diff --git a/integrationExamples/noadserver/native_noadserver.html b/integrationExamples/noadserver/native_noadserver.html new file mode 100755 index 00000000000..81c71d2acfd --- /dev/null +++ b/integrationExamples/noadserver/native_noadserver.html @@ -0,0 +1,173 @@ + + + + + + + + + + + + + +

Prebid Native

+
+
+ +
+
+ + + + From 5fc475ed7423dcc295dafc6a552097b191f049d5 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 15 Sep 2023 06:43:23 -0700 Subject: [PATCH 027/760] priceFloors: fix bug where `default` does not work on adUnit-level floors (#10475) * priceFloors: fix bug where `default` does not work on adUnit-level floors * do not require adUnits to repeat the same schema * Improve schema selection and validation for adUnit-level floors --- modules/priceFloors.js | 49 +++++-- test/spec/modules/priceFloors_spec.js | 183 ++++++++++++++++++++++++-- 2 files changed, 211 insertions(+), 21 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index a72a79c2b40..e62e615ea86 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -13,7 +13,8 @@ import { mergeDeep, parseGPTSingleSizeArray, parseUrl, - pick + pick, + deepEqual } from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {config} from '../src/config.js'; @@ -40,10 +41,13 @@ const MODULE_NAME = 'Price Floors'; */ const ajax = ajaxBuilder(10000); +// eslint-disable-next-line symbol-description +const SYN_FIELD = Symbol(); + /** * @summary Allowed fields for rules to have */ -export let allowedFields = ['gptSlot', 'adUnitCode', 'size', 'domain', 'mediaType']; +export let allowedFields = [SYN_FIELD, 'gptSlot', 'adUnitCode', 'size', 'domain', 'mediaType']; /** * @summary This is a flag to indicate if a AJAX call is processing for a floors request @@ -104,6 +108,7 @@ function getAdUnitCode(request, response, {index = auctionManager.index} = {}) { * @summary floor field types with their matching functions to resolve the actual matched value */ export let fieldMatchingFunctions = { + [SYN_FIELD]: () => '*', 'size': (bidRequest, bidResponse) => parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).transactionId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, @@ -117,6 +122,7 @@ export let fieldMatchingFunctions = { * Returns array of Tuple [exact match, catch all] for each field in rules file */ function enumeratePossibleFieldValues(floorFields, bidObject, responseObject) { + if (!floorFields.length) return []; // generate combination of all exact matches and catch all for each field type return floorFields.reduce((accum, field) => { let exactMatch = fieldMatchingFunctions[field](bidObject, responseObject) || '*'; @@ -132,7 +138,9 @@ function enumeratePossibleFieldValues(floorFields, bidObject, responseObject) { */ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) { let fieldValues = enumeratePossibleFieldValues(deepAccess(floorData, 'schema.fields') || [], bidObject, responseObject); - if (!fieldValues.length) return { matchingFloor: floorData.default }; + if (!fieldValues.length) { + return {matchingFloor: undefined} + } // look to see if a request for this context was made already let matchingInput = fieldValues.map(field => field[0]).join('-'); @@ -146,9 +154,9 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {}) let matchingData = { floorMin: floorData.floorMin || 0, - floorRuleValue: isNaN(floorData.values[matchingRule]) ? floorData.default : floorData.values[matchingRule], + floorRuleValue: floorData.values[matchingRule], matchingData: allPossibleMatches[0], // the first possible match is an "exact" so contains all data relevant for anlaytics adapters - matchingRule + matchingRule: matchingRule === floorData.meta?.defaultRule ? undefined : matchingRule }; // use adUnit floorMin as priority! const floorMin = deepAccess(bidObject, 'ortb2Imp.ext.prebid.floors.floorMin'); @@ -300,14 +308,20 @@ function normalizeRulesForAuction(floorData, adUnitCode) { * Only called if no set config or fetch level data has returned */ export function getFloorDataFromAdUnits(adUnits) { + const schemaAu = adUnits.find(au => au.floors?.schema != null); return adUnits.reduce((accum, adUnit) => { - if (isFloorsDataValid(adUnit.floors)) { + if (adUnit.floors?.schema != null && !deepEqual(adUnit.floors.schema, schemaAu?.floors?.schema)) { + logError(`${MODULE_NAME}: adUnit '${adUnit.code}' declares a different schema from one previously declared by adUnit '${schemaAu.code}'. Floor config for '${adUnit.code}' will be ignored.`) + return accum; + } + const floors = Object.assign({}, schemaAu?.floors, {values: undefined}, adUnit.floors) + if (isFloorsDataValid(floors)) { // if values already exist we want to not overwrite them if (!accum.values) { - accum = getFloorsDataForAuction(adUnit.floors, adUnit.code); + accum = getFloorsDataForAuction(floors, adUnit.code); accum.location = 'adUnit'; } else { - let newRules = getFloorsDataForAuction(adUnit.floors, adUnit.code).values; + let newRules = getFloorsDataForAuction(floors, adUnit.code).values; // copy over the new rules into our values object Object.assign(accum.values, newRules); } @@ -443,7 +457,26 @@ function validateRules(floorsData, numFields, delimiter) { return Object.keys(floorsData.values).length > 0; } +export function normalizeDefault(model) { + if (isNumber(model.default)) { + let defaultRule = '*'; + const numFields = (model.schema?.fields || []).length; + if (!numFields) { + deepSetValue(model, 'schema.fields', [SYN_FIELD]); + } else { + defaultRule = Array(numFields).fill('*').join(model.schema?.delimiter || '|'); + } + model.values = model.values || {}; + if (model.values[defaultRule] == null) { + model.values[defaultRule] = model.default; + model.meta = {defaultRule}; + } + } + return model; +} + function modelIsValid(model) { + model = normalizeDefault(model); // schema.fields has only allowed attributes if (!validateSchemaFields(deepAccess(model, 'schema.fields'))) { return false; diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index e2b8ca38792..950e039491d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -12,7 +12,7 @@ import { isFloorsDataValid, addBidResponseHook, fieldMatchingFunctions, - allowedFields + allowedFields, parseFloorData, normalizeDefault, getFloorDataFromAdUnits } from 'modules/priceFloors.js'; import * as events from 'src/events.js'; import * as mockGpt from '../integration/faker/googletag.js'; @@ -123,7 +123,7 @@ describe('the price floors module', function () { return { code, mediaTypes: {banner: { sizes: [[300, 200], [300, 600]] }, native: {}}, - bids: [{bidder: 'someBidder'}, {bidder: 'someOtherBidder'}] + bids: [{bidder: 'someBidder', adUnitCode: code}, {bidder: 'someOtherBidder', adUnitCode: code}] }; } beforeEach(function() { @@ -143,6 +143,76 @@ describe('the price floors module', function () { getGlobal().bidderSettings = {}; }); + describe('parseFloorData', () => { + it('should accept just a default floor', () => { + const fd = parseFloorData({ + default: 1.23 + }); + expect(getFirstMatchingFloor(fd, {}, {}).matchingFloor).to.eql(1.23); + }); + }); + + describe('getFloorDataFromAdUnits', () => { + let adUnits; + + function setFloorValues(rule) { + adUnits.forEach((au, i) => { + au.floors = { + values: { + [rule]: i + 1 + } + } + }) + } + + beforeEach(() => { + adUnits = ['au1', 'au2', 'au3'].map(getAdUnitMock); + }) + + it('should use one schema for all adUnits', () => { + setFloorValues('*;*') + adUnits[1].floors.schema = { + fields: ['mediaType', 'gptSlot'], + delimiter: ';' + } + sinon.assert.match(getFloorDataFromAdUnits(adUnits), { + schema: { + fields: ['adUnitCode', 'mediaType', 'gptSlot'], + delimiter: ';' + }, + values: { + 'au1;*;*': 1, + 'au2;*;*': 2, + 'au3;*;*': 3 + } + }) + }); + it('should ignore adUnits that declare different schema', () => { + setFloorValues('*|*'); + adUnits[0].floors.schema = { + fields: ['mediaType', 'gptSlot'] + }; + adUnits[2].floors.schema = { + fields: ['gptSlot', 'mediaType'] + }; + expect(getFloorDataFromAdUnits(adUnits).values).to.eql({ + 'au1|*|*': 1, + 'au2|*|*': 2 + }) + }); + it('should ignore adUnits that declare no values', () => { + setFloorValues('*'); + adUnits[0].floors.schema = { + fields: ['mediaType'] + }; + delete adUnits[2].floors.values; + expect(getFloorDataFromAdUnits(adUnits).values).to.eql({ + 'au1|*': 1, + 'au2|*': 2, + }) + }) + }) + describe('getFloorsDataForAuction', function () { it('converts basic input floor data into a floorData map for the auction correctly', function () { // basic input where nothing needs to be updated @@ -233,8 +303,8 @@ describe('the price floors module', function () { }); describe('getFirstMatchingFloor', function () { - it('uses a 0 floor as overrite', function () { - let inputFloorData = { + it('uses a 0 floor as override', function () { + let inputFloorData = normalizeDefault({ currency: 'USD', schema: { delimiter: '|', @@ -245,7 +315,7 @@ describe('the price floors module', function () { 'test_div_2': 2 }, default: 0.5 - }; + }); expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ floorMin: 0, @@ -434,7 +504,7 @@ describe('the price floors module', function () { }); }); it('selects the right floor for more complex rules', function () { - let inputFloorData = { + let inputFloorData = normalizeDefault({ currency: 'USD', schema: { delimiter: '^', @@ -448,7 +518,7 @@ describe('the price floors module', function () { 'weird_div^*^300x250': 5.5 }, default: 0.5 - }; + }); // banner with 300x250 size expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: [300, 250]})).to.deep.equal({ floorMin: 0, @@ -490,10 +560,8 @@ describe('the price floors module', function () { matchingFloor: undefined }); // if default is there use it - inputFloorData = { default: 5.0 }; - expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'})).to.deep.equal({ - matchingFloor: 5.0 - }); + inputFloorData = normalizeDefault({ default: 5.0 }); + expect(getFirstMatchingFloor(inputFloorData, basicBidRequest, {mediaType: 'banner', size: '*'}).matchingFloor).to.equal(5.0); }); describe('with gpt enabled', function () { let gptFloorData; @@ -693,6 +761,95 @@ describe('the price floors module', function () { floorProvider: undefined }); }); + describe('default floor', () => { + let adUnits; + beforeEach(() => { + adUnits = ['au1', 'au2'].map(getAdUnitMock); + }) + function expectFloors(floors) { + runStandardAuction(adUnits); + adUnits.forEach((au, i) => { + au.bids.forEach(bid => { + expect(bid.getFloor().floor).to.eql(floors[i]); + }) + }) + } + describe('should be sufficient by itself', () => { + it('globally', () => { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: { + default: 1.23 + } + }); + expectFloors([1.23, 1.23]) + }); + it('on adUnits', () => { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + adUnits[0].floors = {default: 1}; + adUnits[1].floors = {default: 2}; + expectFloors([1, 2]) + }); + it('on an adUnit with hidden schema', () => { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + adUnits[0].floors = { + schema: { + fields: ['mediaType', 'gptSlot'], + }, + default: 1 + } + adUnits[1].floors = { + default: 2 + } + expectFloors([1, 2]); + }) + }); + describe('should NOT be used when a star rule exists', () => { + it('globally', () => { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: { + schema: { + fields: ['mediaType', 'gptSlot'], + }, + values: { + '*|*': 2 + }, + default: 3, + } + }); + expectFloors([2, 2]); + }); + it('on adUnits', () => { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: undefined + }); + adUnits[0].floors = { + schema: { + fields: ['mediaType', 'gptSlot'], + }, + values: { + '*|*': 1 + }, + default: 3 + }; + adUnits[1].floors = { + values: { + '*|*': 2 + }, + default: 4 + } + expectFloors([1, 2]); + }) + }); + }) it('bidRequests should have getFloor function and flooring meta data when setConfig occurs', function () { handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider'}); runStandardAuction(); @@ -1382,7 +1539,7 @@ describe('the price floors module', function () { it('picks the right rule with more complex rules', function () { _floorDataForAuction[bidRequest.auctionId] = { ...basicFloorConfig, - data: { + data: normalizeDefault({ currency: 'USD', schema: { fields: ['mediaType', 'size'], delimiter: '|' }, values: { @@ -1394,7 +1551,7 @@ describe('the price floors module', function () { 'video|*': 5.5 }, default: 10.0 - } + }) }; // assumes banner * From 7df304fdfef03b41ed995f327dd9b635e5b1f3ea Mon Sep 17 00:00:00 2001 From: ChangsikChoi <49671733+ChangsikChoi@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:13:06 +0900 Subject: [PATCH 028/760] Add A1Media Bid Adapter (#10424) Co-authored-by: ChangsikChoi <> --- modules/a1MediaBidAdapter.js | 89 ++++++++ modules/a1MediaBidAdapter.md | 93 +++++++++ test/spec/modules/a1MediaBidAdapter_spec.js | 220 ++++++++++++++++++++ 3 files changed, 402 insertions(+) create mode 100644 modules/a1MediaBidAdapter.js create mode 100644 modules/a1MediaBidAdapter.md create mode 100644 test/spec/modules/a1MediaBidAdapter_spec.js diff --git a/modules/a1MediaBidAdapter.js b/modules/a1MediaBidAdapter.js new file mode 100644 index 00000000000..6a137e621c5 --- /dev/null +++ b/modules/a1MediaBidAdapter.js @@ -0,0 +1,89 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'a1media'; +const END_POINT = 'https://d11.contentsfeed.com/dsp/breq/a1'; +const DEFAULT_CURRENCY = 'JPY'; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + if (!imp.bidfloor) { + imp.bidfloor = bidRequest.params.bidfloor || 0; + imp.bidfloorcur = bidRequest.params.currency || DEFAULT_CURRENCY; + } + if (bidRequest.params.battr) { + Object.keys(bidRequest.mediaTypes).forEach(mType => { + imp[mType].battr = bidRequest.params.battr; + }) + } + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + if (!request.cur) { + request.cur = [bid.params.currency || DEFAULT_CURRENCY]; + } + if (bid.params.bcat) { + request.bcat = bid.params.bcat; + } + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + + let resMediaType; + const reqMediaTypes = Object.keys(bidRequest.mediaTypes); + if (reqMediaTypes.length === 1) { + resMediaType = reqMediaTypes[0]; + } else { + if (bid.adm.search(/^(<\?xml| { + return { + bidderCode: 'a1media', + auctionId: 'ba87bfdf-493e-4a88-8e26-17b4cbc9adbd', + bidderRequestId: '104e8d2392bd6f', + bids: [ + { + bidder: 'a1media', + params: {}, + auctionId: 'ba87bfdf-493e-4a88-8e26-17b4cbc9adbd', + mediaTypes: { + banner: { + sizes: [ + [ 320, 100 ], + ] + }, + ...(isMulti && { + video: { + mimes: ['video/mp4'] + }, + native: { + title: { + required: true, + }} + }) + }, + ...(isMulti && { + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { + id: 0, + required: 1, + title: { + len: 140 + } + } + ] + } + }), + adUnitCode: 'test-div', + transactionId: 'cab00498-028b-4061-8f9d-a8d66c8cb91d', + bidId: '2e9f38ea93bb9e', + bidderRequestId: '104e8d2392bd6f', + } + ], + } +}; +const getConvertedBidReq = () => { + return { + cur: [ + 'JPY' + ], + imp: [ + { + banner: { + format: [ + { + h: 100, + w: 320 + }, + ], + topframe: 0 + }, + bidfloor: 0, + bidfloorcur: 'JPY', + id: '2e9f38ea93bb9e' + } + ], + test: 0, + } +}; + +const getBidderResponse = () => { + return { + body: { + id: 'bid-response', + cur: 'JPY', + seatbid: [ + { + bid: [{ + impid: '2e9f38ea93bb9e', + crid: 'creative-id', + cur: 'JPY', + price: 9, + }] + } + ] + } + } +} +const bannerAdm = '
'; +const videoAdm = 'testvast1'; +const nativeAdm = '{"ver":"1.2","link":{"url":"test_url"},"assets":[{"id":1,"required":1,"title":{"text":"native_title"}}]}'; + +describe('a1MediaBidAdapter', function() { + describe('isValidRequest', function() { + const bid = { + bidder: 'a1media', + }; + + it('should return true always', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function() { + let bidderRequest, convertedRequest; + beforeEach(function() { + bidderRequest = getBidderRequest(); + convertedRequest = getConvertedBidReq(); + }); + + it('should return expected request object', function() { + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + convertedRequest.id = bidRequest.data.id; + + expect(bidRequest.method).equal('POST'); + expect(bidRequest.url).equal('https://d11.contentsfeed.com/dsp/breq/a1'); + expect(bidRequest.data).deep.equal(convertedRequest); + }); + it('should set ortb blocking using params', function() { + bidderRequest.bids[0].params = ortbBlockParams; + + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + convertedRequest.id = bidRequest.data.id; + convertedRequest.bcat = ortbBlockParams.bcat; + convertedRequest.imp[0].banner.battr = ortbBlockParams.battr; + + expect(bidRequest.data).deep.equal(convertedRequest); + }); + + it('should set bidfloor when getFloor is available', function() { + bidderRequest.bids[0].getFloor = () => ({ currency: 'USD', floor: 999 }); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + + expect(bidRequest.data.imp[0].bidfloor).equal(999); + expect(bidRequest.data.imp[0].bidfloorcur).equal('USD'); + }); + + it('should set cur when currency config is configured', function() { + config.setConfig({ + currency: { + adServerCurrency: 'USD', + } + }); + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + + expect(bidRequest.data.cur[0]).equal('USD'); + }); + + it('should set bidfloor and currency using params when modules not available', function() { + bidderRequest.bids[0].params.currency = 'USD'; + bidderRequest.bids[0].params.bidfloor = 0.99; + + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + convertedRequest.id = bidRequest.data.id; + convertedRequest.imp[0].bidfloor = 0.99; + convertedRequest.imp[0].bidfloorcur = 'USD'; + convertedRequest.cur[0] = 'USD'; + + expect(bidRequest.data).deep.equal(convertedRequest); + }); + }); + + describe('interpretResponse', function() { + describe('when request mediaType is single', function() { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getBidderRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + it('should set cpm using price attribute', function() { + const bidResPrice = 9; + bidderResponse.body.seatbid[0].bid[0].price = bidResPrice; + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + expect(interpretedRes[0].cpm).equal(bidResPrice); + }); + it('should set mediaType using request mediaTypes', function() { + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + expect(interpretedRes[0].mediaType).equal(BANNER); + }); + }); + + describe('when request mediaType is multi', function() { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getBidderRequest(true); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + it('should set mediaType to video', function() { + bidderResponse.body.seatbid[0].bid[0].adm = videoAdm; + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + expect(interpretedRes[0].mediaType).equal(VIDEO); + }); + it('should set mediaType to native', function() { + bidderResponse.body.seatbid[0].bid[0].adm = nativeAdm; + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + expect(interpretedRes[0].mediaType).equal(NATIVE); + }); + it('should set mediaType to banner when adm is neither native or video', function() { + bidderResponse.body.seatbid[0].bid[0].adm = bannerAdm; + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + expect(interpretedRes[0].mediaType).equal(BANNER); + }); + }); + }); +}) From 2588459cfa2e1a143b1e26eff3a505353911bcc3 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:00:27 +0530 Subject: [PATCH 029/760] PubMatic Analytics Adapter : log partner latency value using timeToRespond property (#10497) * Log time to respond in l1 as latency * Addressed code review changes * Added comments for future reference --- modules/pubmaticAnalyticsAdapter.js | 7 +++- .../modules/pubmaticAnalyticsAdapter_spec.js | 39 ++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 3391773a3d2..f53b4094ae8 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -273,7 +273,8 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'en': bid.bidResponse ? bid.bidResponse.bidPriceUSD : 0, 'di': bid.bidResponse ? (bid.bidResponse.dealId || OPEN_AUCTION_DEAL_ID) : OPEN_AUCTION_DEAL_ID, 'dc': bid.bidResponse ? (bid.bidResponse.dealChannel || EMPTY_STRING) : EMPTY_STRING, - 'l1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, + 'l1': bid.bidResponse ? bid.partnerTimeToRespond : 0, + 'ol1': bid.bidResponse ? bid.clientLatencyTimeMs : 0, 'l2': 0, 'adv': bid.bidResponse ? getAdDomain(bid.bidResponse) || undefined : undefined, 'ss': isS2SBidder(bid.bidder), @@ -507,6 +508,10 @@ function bidResponseHandler(args) { bid.adId = args.adId; bid.source = formatSource(bid.source || args.source); setBidStatus(bid, args); + const latency = args?.timeToRespond || Date.now() - cache.auctions[args.auctionId].timestamp; + const auctionTime = cache.auctions[args.auctionId].timeout; + // Check if latency is greater than auctiontime+150, then log auctiontime+150 to avoid large numbers + bid.partnerTimeToRespond = latency > (auctionTime + 150) ? (auctionTime + 150) : latency; bid.clientLatencyTimeMs = Date.now() - cache.auctions[args.auctionId].timestamp; bid.bidResponse = parseBidResponse(args); } diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index aba9750d6a6..ad471252f30 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -388,7 +388,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(1); expect(data.s[0].ps[0].t).to.equal(0); @@ -417,7 +418,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -794,7 +796,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(0); + expect(data.s[0].ps[0].ol1).to.equal(0); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(1); @@ -853,7 +856,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -901,7 +905,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -958,7 +963,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1012,7 +1018,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1067,7 +1074,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1126,7 +1134,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1198,7 +1207,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1228,7 +1238,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1317,7 +1328,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1346,7 +1358,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[1].ps[0].l1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); From ecc0f48c617d7fc497880af671c4e24b3669ebef Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Mon, 18 Sep 2023 18:10:56 +0200 Subject: [PATCH 030/760] alias update (#10491) --- modules/nexx360BidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index 3d8e9c348c8..c65544936fa 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -190,7 +190,7 @@ function interpretResponse(serverResponse) { demandSource: bid.ext.ssp, }, }; - if (allowAlternateBidderCodes) response.bidderCode = `n360-${bid.ext.ssp}`; + if (allowAlternateBidderCodes) response.bidderCode = `n360_${bid.ext.ssp}`; if (bid.ext.mediaType === BANNER) { if (bid.adm) { From e18ab5161a4edb312faf307336eba815106b4e55 Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Tue, 19 Sep 2023 04:58:08 +1000 Subject: [PATCH 031/760] Jixie Bid Adapter : pass bid floor to backend and change 1st party cookie (#10496) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * WIP * cleaned up * correcting formatting errors from circleci * sending floor to our backend for each bid, when available, changed one of the 1st party cookies that we want to send to backend * fixed spacing issues in code --- modules/jixieBidAdapter.js | 30 ++++++++++++++++++++--- test/spec/modules/jixieBidAdapter_spec.js | 25 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 824bc3828b4..103c925a2f9 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getDNT, isArray, logWarn} from '../src/utils.js'; +import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -14,6 +14,27 @@ const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.media/jxhbrenderer.1.1. const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost'; const sidTTLMins_ = 30; +/** + * Get bid floor from Price Floors Module + * + * @param {Object} bid + * @returns {float||null} + */ +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return null; + } + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + /** * Own miscellaneous support functions: */ @@ -56,7 +77,7 @@ function fetchIds_() { if (tmp) ret.client_id_ls = tmp; tmp = storage.getDataFromLocalStorage('_jxxs'); if (tmp) ret.session_id_ls = tmp; - ['_jxtoko', '_jxifo', '_jxtdid', '__uid2_advertising_token'].forEach(function(n) { + ['_jxtoko', '_jxifo', '_jxtdid', '_jxcomp'].forEach(function(n) { tmp = storage.getCookie(n); if (tmp) ret.jxeids[n] = tmp; }); @@ -171,9 +192,12 @@ export const spec = { params: one.params, gpid: gpid }; + let bidFloor = getBidFloor(one); + if (bidFloor) { + tmp.bidFloor = bidFloor; + } bids.push(tmp); }); - let jixieCfgBlob = config.getConfig('jixie'); if (!jixieCfgBlob) { jixieCfgBlob = {}; diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 4a0fa3b4d57..fd0d7e8a033 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec, internal as jixieaux, storage } from 'modules/jixieBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { deepClone } from 'src/utils.js'; describe('jixie Adapter', function () { const pageurl_ = 'https://testdomain.com/testpage.html'; @@ -74,13 +75,13 @@ describe('jixie Adapter', function () { const jxtokoTest1_ = 'eyJJRCI6ImFiYyJ9'; const jxifoTest1_ = 'fffffbbbbbcccccaaaaae890606aaaaa'; const jxtdidTest1_ = '222223d1-1111-2222-3333-b9f129299999'; - const __uid2_advertising_token_Test1 = 'AAAAABBBBBCCCCCDDDDDEEEEEUkkZPQfifpkPnnlJhtsa4o+gf4nfqgN5qHiTVX73ymTSbLT9jz1nf+Q7QdxNh9nTad9UaN5pzfHMt/rs1woQw72c1ip+8heZXPfKGZtZP7ldJesYhlo3/0FVcL/wl9ZlAo1jYOEfHo7Y9zFzNXABbbbbb=='; + const jxcompTest1_ = 'AAAAABBBBBCCCCCDDDDDEEEEEUkkZPQfifpkPnnlJhtsa4o+gf4nfqgN5qHiTVX73ymTSbLT9jz1nf+Q7QdxNh9nTad9UaN5pzfHMt/rs1woQw72c1ip+8heZXPfKGZtZP7ldJesYhlo3/0FVcL/wl9ZlAo1jYOEfHo7Y9zFzNXABbbbbb=='; const refJxEids_ = { '_jxtoko': jxtokoTest1_, '_jxifo': jxifoTest1_, '_jxtdid': jxtdidTest1_, - '__uid2_advertising_token': __uid2_advertising_token_Test1 + '_jxcomp': jxcompTest1_ }; // to serve as the object that prebid will call jixie buildRequest with: (param2) @@ -237,8 +238,8 @@ describe('jixie Adapter', function () { .withArgs('_jxtdid') .returns(jxtdidTest1_); getCookieStub - .withArgs('__uid2_advertising_token') - .returns(__uid2_advertising_token_Test1); + .withArgs('_jxcomp') + .returns(jxcompTest1_); getCookieStub .withArgs('_jxx') .returns(clientIdTest1_); @@ -345,6 +346,22 @@ describe('jixie Adapter', function () { expect(payload.schain).to.deep.include(schain); }); + it('it should populate the floor info when available', function () { + let oneSpecialBidReq = deepClone(bidRequests_[0]); + let request, payload = null; + // 1 floor is not set + request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + payload = JSON.parse(request.data); + expect(payload.bids[0].bidFloor).to.not.exist; + + // 2 floor is set + let getFloorResponse = { currency: 'USD', floor: 2.1 }; + oneSpecialBidReq.getFloor = () => getFloorResponse; + request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + payload = JSON.parse(request.data); + expect(payload.bids[0].bidFloor).to.exist.and.to.equal(2.1); + }); + it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { userIdAsEids: [ From 0ea5caa09eeed0d9a40c65c8320bc11eeb1b2345 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 18 Sep 2023 14:54:32 -0700 Subject: [PATCH 032/760] Core: fix bug where the PBS adapter always times out (#10501) --- src/adapterManager.js | 2 +- test/spec/unit/core/adapterManager_spec.js | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/adapterManager.js b/src/adapterManager.js index 554fde21734..93cbb8a071b 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -422,7 +422,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request bidRequest.start = timestamp(); return function () { onTimelyResponse(bidRequest.bidderRequestId); - doneCbs.apply(bidRequest, arguments); + doneCb.apply(bidRequest, arguments); } }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index cff26df2e4d..98d841d9c7c 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -965,13 +965,23 @@ describe('adapterManager tests', function () { }]; it('invokes callBids on the S2S adapter', function () { + const done = sinon.stub(); + const onTimelyResponse = sinon.stub(); + prebidServerAdapterMock.callBids.callsFake((_1, _2, _3, done) => { + done(); + }); adapterManager.callBids( getAdUnits(), bidRequests, () => {}, - () => () => {} + done, + undefined, + undefined, + onTimelyResponse ); sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + sinon.assert.calledTwice(done); + bidRequests.forEach(br => sinon.assert.calledWith(onTimelyResponse, br.bidderRequestId)); }); // Enable this test when prebidServer adapter is made 1.0 compliant From a0b53e6e2ca6c53e89e9f550d786bf6ae8038e2a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 19 Sep 2023 09:46:41 +0000 Subject: [PATCH 033/760] Prebid 8.15.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dffd7c0b837..3e1440e0340 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.15.0-pre", + "version": "8.15.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index e2cc34a23ce..5452eec45b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.15.0-pre", + "version": "8.15.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 6d39d8b2292d031a1d3e599749a7ceda74d379bc Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 19 Sep 2023 09:46:41 +0000 Subject: [PATCH 034/760] Increment version to 8.16.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e1440e0340..43935bca1ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.15.0", + "version": "8.16.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 5452eec45b8..4b53b8e2c29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.15.0", + "version": "8.16.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 07f8b4e9abc438bb900529aa1ab05ed85f504e08 Mon Sep 17 00:00:00 2001 From: AdFusionPrebid <144114711+AdFusionPrebid@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:34:15 +0200 Subject: [PATCH 035/760] AdFusion Bid Adapter : initial release (#10455) * adfusion bid adapter test * Add adapter and docs --- modules/adfusionBidAdapter.js | 90 ++++++++++++++++++ modules/adfusionBidAdapter.md | 61 ++++++++++++ test/spec/modules/adfusionBidAdapter_spec.js | 97 ++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 modules/adfusionBidAdapter.js create mode 100644 modules/adfusionBidAdapter.md create mode 100644 test/spec/modules/adfusionBidAdapter_spec.js diff --git a/modules/adfusionBidAdapter.js b/modules/adfusionBidAdapter.js new file mode 100644 index 00000000000..b3638159c2a --- /dev/null +++ b/modules/adfusionBidAdapter.js @@ -0,0 +1,90 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import * as utils from '../src/utils.js'; + +const adpterVersion = '1.0'; +export const REQUEST_URL = 'https://spicyrtb.com/auction/prebid'; + +export const spec = { + code: 'adfusion', + gvlid: 844, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + isBannerBid, + isVideoBid, +}; + +registerBidder(spec); + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + utils.mergeDeep(req, { + at: 1, + ext: { + prebid: { + accountid: bid.params.accountId, + adapterVersion: `${adpterVersion}`, + }, + }, + }); + return req; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context); + return response.bids; + }, +}); + +function isBidRequestValid(bidRequest) { + const isValid = bidRequest.params.accountId; + if (!isValid) { + utils.logError('AdFusion adapter bidRequest has no accountId'); + return false; + } + return true; +} + +function buildRequests(bids, bidderRequest) { + let videoBids = bids.filter((bid) => isVideoBid(bid)); + let bannerBids = bids.filter((bid) => isBannerBid(bid)); + let requests = bannerBids.length + ? [createRequest(bannerBids, bidderRequest, BANNER)] + : []; + videoBids.forEach((bid) => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + return requests; +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + return { + method: 'POST', + url: REQUEST_URL, + data: converter.toORTB({ + bidRequests, + bidderRequest, + context: { mediaType }, + }), + }; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner'); +} + +function interpretResponse(resp, req) { + return converter.fromORTB({ request: req.data, response: resp.body }); +} diff --git a/modules/adfusionBidAdapter.md b/modules/adfusionBidAdapter.md new file mode 100644 index 00000000000..803a03ba1a1 --- /dev/null +++ b/modules/adfusionBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: AdFusion Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid@adfusion.pl +``` + +# Description + +Module that connects to AdFusion demand sources + +# Banner Test Parameters + +```js +var adUnits = [ + { + code: "test-banner", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + [320, 480], + ], + }, + }, + bids: [ + { + bidder: "adfusion", + params: { + accountId: 1234, // required + }, + }, + ], + }, +]; +``` + +# Video Test Parameters + +```js +var videoAdUnit = { + code: "video1", + mediaTypes: { + video: { + context: "instream", + playerSize: [640, 480], + mimes: ["video/mp4"], + }, + }, + bids: [ + { + bidder: "adfusion", + params: { + accountId: 1234, // required + }, + }, + ], +}; +``` diff --git a/test/spec/modules/adfusionBidAdapter_spec.js b/test/spec/modules/adfusionBidAdapter_spec.js new file mode 100644 index 00000000000..638831c33f3 --- /dev/null +++ b/test/spec/modules/adfusionBidAdapter_spec.js @@ -0,0 +1,97 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adfusionBidAdapter'; +import 'modules/priceFloors.js'; +import { newBidder } from 'src/adapters/bidderFactory'; + +describe('adfusionBidAdapter', 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: 'adfusion', + params: { + accountId: 1234, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when params.accountID is missing', function () { + let localbid = Object.assign({}, bid); + delete localbid.params.accountId; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [ + { + bidder: 'adfusion', + params: { + accountId: 1234, + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + }, + { + bidder: 'adfusion', + params: { + accountId: 1234, + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480], + }, + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2', + }, + ]; + bidderRequest = { refererInfo: {} }; + }); + + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], bidderRequest); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); + }); + + it('should return a valid bid request object', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.an('array'); + expect(request[0].data).to.be.an('object'); + expect(request[0].method).to.equal('POST'); + expect(request[0].url).to.not.equal(''); + expect(request[0].url).to.not.equal(undefined); + expect(request[0].url).to.not.equal(null); + }); + }); +}); From 584e7c0a1677c8d61e2e249a197e6ea28c820933 Mon Sep 17 00:00:00 2001 From: Thuy Hoang <61451682+thuyhq@users.noreply.github.com> Date: Wed, 20 Sep 2023 00:40:41 +0700 Subject: [PATCH 036/760] Update apacdexBidAdapter.js (#10502) Fix confusion about screen width and height --- modules/apacdexBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index e1557d9c6d3..834df134c2e 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -96,8 +96,8 @@ export const spec = { payload.device = {}; payload.device.ua = navigator.userAgent; - payload.device.height = window.screen.width; - payload.device.width = window.screen.height; + payload.device.height = window.screen.height; + payload.device.width = window.screen.width; payload.device.dnt = _getDoNotTrack(); payload.device.language = navigator.language; From e8b9aeca01e9de8d3f05eab7c631f705d19a5520 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 20 Sep 2023 15:32:11 +0200 Subject: [PATCH 037/760] Adyoulike: support getUserSyncs (#10319) --- modules/adyoulikeBidAdapter.js | 45 +++++++ test/spec/modules/adyoulikeBidAdapter_spec.js | 112 ++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 6eb897d334e..8952c3ae2b9 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -1,5 +1,6 @@ import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; @@ -166,6 +167,50 @@ export const spec = { } }); return bidResponses; + }, + + /** + * List user sync endpoints. + * Legal information have to be added to the request. + * Only iframe syncs are supported. + * + * @param {*} syncOptions Publisher prebid configuration. + * @param {*} serverResponses A successful response from the server. + * @return {syncs[]} An array of syncs that should be executed. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled) { + return []; + } + + let params = ''; + + // GDPR + if (gdprConsent) { + params += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + params += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + + // coppa compliance + if (config.getConfig('coppa') === true) { + params += '&coppa=1'; + } + + // CCPA + if (uspConsent) { + params += '&us_privacy=' + encodeURIComponent(uspConsent); + } + + // GPP + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppConsent.gppString); + params += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + + return [{ + type: 'iframe', + url: `https://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4${params}` + }]; } } diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index de77c741364..ffd6729397a 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/adyoulikeBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; describe('Adyoulike Adapter', function () { const canonicalUrl = 'https://canonical.url/?t=%26'; @@ -887,4 +888,115 @@ describe('Adyoulike Adapter', function () { expect(spec.gvlid).to.equal(259) }) }); + + describe('getUserSyncs', function () { + const syncurl_iframe = 'https://visitor.omnitagjs.com/visitor/isync?uid=19340f4f097d16f41f34fc0274981ca4'; + + const emptySync = []; + + describe('with iframe enabled', function() { + const userSyncConfig = { iframeEnabled: true }; + + it('should not add parameters if not provided', function() { + expect(spec.getUserSyncs(userSyncConfig, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should add GDPR parameters if provided', function() { + expect(spec.getUserSyncs(userSyncConfig, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=` + }]); + + expect(spec.getUserSyncs(userSyncConfig, {}, {gdprApplies: true, consentString: 'foo?'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=1&gdpr_consent=foo%3F` + }]); + expect(spec.getUserSyncs(userSyncConfig, {}, {gdprApplies: false, consentString: 'bar'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gdpr=0&gdpr_consent=bar` + }]); + }); + + it('should add CCPA parameters if provided', function() { + expect(spec.getUserSyncs(userSyncConfig, {}, undefined, 'foo?')).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&us_privacy=foo%3F` + }]); + }); + + describe('COPPA', function() { + let sandbox; + + this.beforeEach(function() { + sandbox = sinon.sandbox.create(); + }); + + this.afterEach(function() { + sandbox.restore(); + }); + + it('should add coppa parameters if provided', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + + expect(spec.getUserSyncs(userSyncConfig, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&coppa=1` + }]); + }); + }); + + describe('GPP', function() { + it('should not apply if not gppConsent.gppString', function() { + const gppConsent = { gppString: '', applicableSections: [123] }; + const result = spec.getUserSyncs(userSyncConfig, {}, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should not apply if not gppConsent.applicableSections', function() { + const gppConsent = { gppString: '', applicableSections: undefined }; + const result = spec.getUserSyncs(userSyncConfig, {}, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should not apply if empty gppConsent.applicableSections', function() { + const gppConsent = { gppString: '', applicableSections: [] }; + const result = spec.getUserSyncs(userSyncConfig, {}, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}` + }]); + }); + + it('should apply if all above are available', function() { + const gppConsent = { gppString: 'foo?', applicableSections: [123] }; + const result = spec.getUserSyncs(userSyncConfig, {}, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gpp=foo%3F&gpp_sid=123` + }]); + }); + + it('should support multiple sections', function() { + const gppConsent = { gppString: 'foo', applicableSections: [123, 456] }; + const result = spec.getUserSyncs(userSyncConfig, {}, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${syncurl_iframe}&gpp=foo&gpp_sid=123%2C456` + }]); + }); + }); + }); + + describe('with iframe disabled', function() { + const userSyncConfig = { iframeEnabled: false }; + + it('should return empty list of syncs', function() { + expect(spec.getUserSyncs(userSyncConfig, {}, undefined, undefined)).to.deep.equal(emptySync); + expect(spec.getUserSyncs(userSyncConfig, {}, {gdprApplies: true, consentString: 'foo'}, 'bar')).to.deep.equal(emptySync); + }); + }); + }); }); From 221336505a2867495af6d8eeccc4f7c2ca0f5445 Mon Sep 17 00:00:00 2001 From: Nitin Nimbalkar <96475150+pm-nitin-nimbalkar@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:23:57 +0530 Subject: [PATCH 038/760] Pubmatic Bid Adapter : consuming fledge auction config in bid adapter (#10506) Changes in PubMatic bid adapter to consume auction config from server response and submit the same to Prebid core --- modules/pubmaticBidAdapter.js | 15 ++++ test/spec/modules/pubmaticBidAdapter_spec.js | 83 ++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a3833551fff..4c5602f959a 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1359,6 +1359,21 @@ export const spec = { }); }); } + let fledgeAuctionConfigs = deepAccess(response.body, 'ext.fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids: bidResponses, + fledgeAuctionConfigs, + } + } } catch (error) { logError(error); } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 0ed764c8f7e..066004bd954 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -3601,6 +3601,89 @@ describe('PubMatic adapter', function () { } }); + describe('Fledge Auction config Response', function () { + let response; + let bidRequestConfigs = [ + { + bidder: 'pubmatic', + mediaTypes: { + banner: { + sizes: [[728, 90], [160, 600]] + } + }, + params: { + publisherId: '5670', + adSlot: '/15671365/DMDemo@300x250:0', + kadfloor: '1.2', + pmzoneid: 'aabc, ddef', + kadpageurl: 'www.publisher.com', + yob: '1986', + gender: 'M', + lat: '12.3', + lon: '23.7', + wiid: '1234567890', + profId: '100', + verId: '200', + currency: 'AUD', + dctr: 'key1:val1,val2|key2:val1' + }, + placementCode: '/19968336/header-bid-tag-1', + sizes: [[300, 250], [300, 600]], + bidId: 'test_bid_id', + requestId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + ortb2Imp: { + ext: { + tid: '92489f71-1bf2-49a0-adf9-000cea934729', + ae: 1 + } + }, + } + ]; + + let bidRequest = spec.buildRequests(bidRequestConfigs, {}); + let bidResponse = { + seatbid: [{ + bid: [{ + impid: 'test_bid_id', + price: 2, + w: 728, + h: 250, + crid: 'test-creative-id', + dealid: 'test-deal-id', + adm: 'test-ad-markup' + }] + }], + cur: 'AUS', + ext: { + fledge_auction_configs: { + 'test_bid_id': { + seller: 'ads.pubmatic.com', + interestGroupBuyers: ['dsp1.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'dsp1.com': { + bid_macros: 0.1, + disallowed_adv_ids: [ + '5678', + '5890' + ], + } + } + } + } + } + }; + + response = spec.interpretResponse({ body: bidResponse }, bidRequest); + it('should return FLEDGE auction_configs alongside bids', function () { + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs.length).to.equal(1); + expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id'); + }); + }); + describe('Preapare metadata', function () { it('Should copy all fields from ext to meta', function () { const bid = { From ab35867545188a29f42531c96ca9c76436ed74ef Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:00:03 +0200 Subject: [PATCH 039/760] ZetaGlobalSsp Analytics Adapter: update deepCopy of analytics object (#10507) * ZetaGlobalSspAnalytics adapter: bugfix in deepClone * checkstyle fix --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 37 ++++--------------- .../zeta_global_sspAnalyticsAdapter_spec.js | 5 --- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 9609a047656..3d5466dd906 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {logInfo, logError, deepClone} from '../src/utils.js'; +import {logInfo, logError} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; @@ -47,46 +47,25 @@ function getZetaParams(event) { /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// -function adRenderSucceededHandler(originalArgs) { - const args = deepClone(originalArgs); +function adRenderSucceededHandler(args) { let eventType = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - if (args.bid) { - // cleanup object - delete args.bid.metrics; - delete args.bid.ad; - - // set zetaParams from cache - if (args.bid.auctionId) { - const zetaParams = cache.auctions[args.bid.auctionId]; - if (zetaParams) { - args.bid.params = [ zetaParams ]; - } + // set zetaParams from cache + if (args.bid && args.bid.auctionId) { + const zetaParams = cache.auctions[args.bid.auctionId]; + if (zetaParams) { + args.bid.params = [ zetaParams ]; } } sendEvent(eventType, args); } -function auctionEndHandler(originalArgs) { - const args = deepClone(originalArgs); +function auctionEndHandler(args) { let eventType = CONSTANTS.EVENTS.AUCTION_END; logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - // cleanup object - delete args.metrics; - if (args.bidderRequests) { - args.bidderRequests.forEach(requests => { - delete requests.metrics; - if (requests.bids) { - requests.bids.forEach(bid => { - delete bid.metrics; - }) - } - }) - } - // save zetaParams to cache const zetaParams = getZetaParams(args); if (zetaParams && args.auctionId) { diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 962d135cd6d..0796736a162 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -395,11 +395,6 @@ describe('Zeta Global SSP Analytics Adapter', function() { const auctionEnd = JSON.parse(requests[0].requestBody); const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd.metrics).to.be.undefined; - - expect(auctionSucceeded.bid.ad).to.be.undefined; - expect(auctionSucceeded.bid.metrics).to.be.undefined; - expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(EVENTS.AUCTION_END.adUnits[0].bids[0].params); expect(EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); }); From 549a85c5a99913d0d1c1c5547d2e559dd38645b4 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 21 Sep 2023 06:04:12 -0700 Subject: [PATCH 040/760] gdprEnforcement: transmitEids and transmitPreciseGeo activity controls (#10435) * transmitEids GDPR rule * transmitPreciseGeo as gdpr special feature 1 * fix capitalization eidsRequireP4consent -> eidsRequireP4Consent --- modules/gdprEnforcement.js | 216 ++++++++++------- src/activities/redactor.js | 12 +- test/spec/modules/gdprEnforcement_spec.js | 277 +++++++++++++++++++++- 3 files changed, 401 insertions(+), 104 deletions(-) diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index e7f1e7b8b45..4e600f71b90 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -5,7 +5,6 @@ import {deepAccess, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; -import {find} from '../src/polyfill.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; @@ -27,44 +26,62 @@ import { ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD, ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS, - ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_UFPD + ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_EIDS, ACTIVITY_TRANSMIT_PRECISE_GEO, ACTIVITY_TRANSMIT_UFPD } from '../src/activities/activities.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; -const TCF2 = { - purpose1: {id: 1, name: 'storage'}, - purpose2: {id: 2, name: 'basicAds'}, - purpose4: {id: 4, name: 'personalizedAds'}, - purpose7: {id: 7, name: 'measurement'}, +export const ACTIVE_RULES = { + purpose: {}, + feature: {} }; -/* - These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher. -*/ -const DEFAULT_RULES = [{ - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: [] -}, { - purpose: 'basicAds', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: [] -}]; - -export let purpose1Rule; -export let purpose2Rule; -export let purpose4Rule; -export let purpose7Rule; - -export let enforcementRules; +const CONSENT_PATHS = { + purpose: 'purpose.consents', + feature: 'specialFeatureOptins' +}; + +const CONFIGURABLE_RULES = { + storage: { + type: 'purpose', + default: { + purpose: 'storage', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + }, + id: 1, + }, + basicAds: { + type: 'purpose', + id: 2, + default: { + purpose: 'basicAds', + enforcePurpose: true, + enforceVendor: true, + vendorExceptions: [] + } + }, + personalizedAds: { + type: 'purpose', + id: 4, + }, + measurement: { + type: 'purpose', + id: 7, + }, + transmitPreciseGeo: { + type: 'feature', + id: 1, + }, +}; const storageBlocked = new Set(); const biddersBlocked = new Set(); const analyticsBlocked = new Set(); const ufpdBlocked = new Set(); +const eidsBlocked = new Set(); +const geoBlocked = new Set(); let hooksAdded = false; let strictStorageEnforcement = false; @@ -79,6 +96,9 @@ const GVLID_LOOKUP_PRIORITY = [ const RULE_NAME = 'TCF2'; const RULE_HANDLES = []; +// in JS we do not have access to the GVL; assume that everyone declares legitimate interest for basic ads +const LI_PURPOSES = [2]; + /** * Retrieve a module's GVL ID. */ @@ -143,6 +163,16 @@ export function shouldEnforce(consentData, purpose, name) { return consentData && consentData.gdprApplies; } +function getConsent(consentData, type, id, gvlId) { + let purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${id}`); + let vendor = !!deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + if (type === 'purpose' && LI_PURPOSES.includes(id)) { + purpose ||= !!deepAccess(consentData, `vendorData.purpose.legitimateInterests.${id}`); + vendor ||= !!deepAccess(consentData, `vendorData.vendor.legitimateInterests.${gvlId}`); + } + return {purpose, vendor}; +} + /** * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns, * the caller may decide to suppress a TCF-sensitive activity. @@ -153,42 +183,24 @@ export function shouldEnforce(consentData, purpose, name) { * @returns {boolean} */ export function validateRules(rule, consentData, currentModule, gvlId) { - const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id; + const ruleOptions = CONFIGURABLE_RULES[rule.purpose]; // return 'true' if vendor present in 'vendorExceptions' if ((rule.vendorExceptions || []).includes(currentModule)) { return true; } const vendorConsentRequred = rule.enforceVendor && !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); - - let purposeAllowed = !rule.enforcePurpose || !!deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`); - let vendorAllowed = !vendorConsentRequred || !!deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); - - if (purposeId === 2) { - purposeAllowed ||= !!deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`); - vendorAllowed ||= !!deepAccess(consentData, `vendorData.vendor.legitimateInterests.${gvlId}`); - } - - return purposeAllowed && vendorAllowed; + const {purpose, vendor} = getConsent(consentData, ruleOptions.type, ruleOptions.id, gvlId); + return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); } -/** - * all activity rules follow the same structure: - * if GDPR is in scope, check configuration for a particular purpose, and if that enables enforcement, - * check against consent data for that purpose and vendor - * - * @param purposeNo TCF purpose number to check for this activity - * @param getEnforcementRule getter for gdprEnforcement rule definition to use - * @param blocked optional set to use for collecting denied vendors - * @param gvlidFallback optional factory function for a gvlid falllback function - */ -function gdprRule(purposeNo, getEnforcementRule, blocked = null, gvlidFallback = () => null) { +function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback = () => null) { return function (params) { const consentData = gdprDataHandler.getConsentData(); const modName = params[ACTIVITY_PARAM_COMPONENT_NAME]; if (shouldEnforce(consentData, purposeNo, modName)) { const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); - let allow = !!validateRules(getEnforcementRule(), consentData, modName, gvlid); + let allow = !!checkConsent(consentData, modName, gvlid); if (!allow) { blocked && blocked.add(modName); return {allow}; @@ -197,32 +209,62 @@ function gdprRule(purposeNo, getEnforcementRule, blocked = null, gvlidFallback = }; } -export const accessDeviceRule = ((rule) => { - return function (params) { - // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set - if (params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_PREBID && !strictStorageEnforcement) return; - return rule(params); - }; -})(gdprRule(1, () => purpose1Rule, storageBlocked)); - -export const syncUserRule = gdprRule(1, () => purpose1Rule, storageBlocked); -export const enrichEidsRule = gdprRule(1, () => purpose1Rule, storageBlocked); +function singlePurposeGdprRule(purposeNo, blocked = null, gvlidFallback = () => null) { + return gdprRule(purposeNo, (cd, modName, gvlid) => !!validateRules(ACTIVE_RULES.purpose[purposeNo], cd, modName, gvlid), blocked, gvlidFallback); +} -export const fetchBidsRule = ((rule) => { +function exceptPrebidModules(ruleFn) { return function (params) { - if (params[ACTIVITY_PARAM_COMPONENT_TYPE] !== MODULE_TYPE_BIDDER) { + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_PREBID) { // TODO: this special case is for the PBS adapter (componentType is 'prebid') // we should check for generic purpose 2 consent & vendor consent based on the PBS vendor's GVL ID; // that is, however, a breaking change and skipped for now return; } + return ruleFn(params); + }; +} + +export const accessDeviceRule = ((rule) => { + return function (params) { + // for vendorless (core) storage, do not enforce rules unless strictStorageEnforcement is set + if (params[ACTIVITY_PARAM_COMPONENT_TYPE] === MODULE_TYPE_PREBID && !strictStorageEnforcement) return; return rule(params); }; -})(gdprRule(2, () => purpose2Rule, biddersBlocked)); +})(singlePurposeGdprRule(1, storageBlocked)); + +export const syncUserRule = singlePurposeGdprRule(1, storageBlocked); +export const enrichEidsRule = singlePurposeGdprRule(1, storageBlocked); +export const fetchBidsRule = exceptPrebidModules(singlePurposeGdprRule(2, biddersBlocked)); +export const reportAnalyticsRule = singlePurposeGdprRule(7, analyticsBlocked, (params) => getGvlidFromAnalyticsAdapter(params[ACTIVITY_PARAM_COMPONENT_NAME], params[ACTIVITY_PARAM_ANL_CONFIG])); +export const ufpdRule = singlePurposeGdprRule(4, ufpdBlocked); + +export const transmitEidsRule = exceptPrebidModules((() => { + // Transmit EID special case: + // by default, legal basis or vendor exceptions for any purpose between 2 and 10 + // (but disregarding enforcePurpose and enforceVendor config) is enough to allow EIDs through + function check2to10Consent(consentData, modName, gvlId) { + for (let pno = 2; pno <= 10; pno++) { + if (ACTIVE_RULES.purpose[pno]?.vendorExceptions?.includes(modName)) { + return true; + } + const {purpose, vendor} = getConsent(consentData, 'purpose', pno, gvlId); + if (purpose && (vendor || ACTIVE_RULES.purpose[pno]?.softVendorExceptions?.includes(modName))) { + return true; + } + } + return false; + } -export const reportAnalyticsRule = gdprRule(7, () => purpose7Rule, analyticsBlocked, (params) => getGvlidFromAnalyticsAdapter(params[ACTIVITY_PARAM_COMPONENT_NAME], params[ACTIVITY_PARAM_ANL_CONFIG])); + const defaultBehavior = gdprRule('2-10', check2to10Consent, eidsBlocked); + const p4Behavior = singlePurposeGdprRule(4, eidsBlocked); + return function () { + const fn = ACTIVE_RULES.purpose[4]?.eidsRequireP4Consent ? p4Behavior : defaultBehavior; + return fn.apply(this, arguments); + }; +})()); -export const ufpdRule = gdprRule(4, () => purpose4Rule, ufpdBlocked); +export const transmitPreciseGeoRule = gdprRule('Special Feature 1', (cd, modName, gvlId) => validateRules(ACTIVE_RULES.feature[1], cd, modName, gvlId), geoBlocked); /** * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event. @@ -237,65 +279,55 @@ function emitTCF2FinalResults() { biddersBlocked: formatSet(biddersBlocked), analyticsBlocked: formatSet(analyticsBlocked), ufpdBlocked: formatSet(ufpdBlocked), + eidsBlocked: formatSet(eidsBlocked), + geoBlocked: formatSet(geoBlocked) }; events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); - [storageBlocked, biddersBlocked, analyticsBlocked, ufpdBlocked].forEach(el => el.clear()); + [storageBlocked, biddersBlocked, analyticsBlocked, ufpdBlocked, eidsBlocked, geoBlocked].forEach(el => el.clear()); } events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); -function hasPurpose(purposeNo) { - const pname = TCF2[`purpose${purposeNo}`].name; - return (rule) => rule.purpose === pname; -} - /** * A configuration function that initializes some module variables, as well as adds hooks * @param {Object} config - GDPR enforcement config object */ export function setEnforcementConfig(config) { - const rules = deepAccess(config, 'gdpr.rules'); + let rules = deepAccess(config, 'gdpr.rules'); if (!rules) { logWarn('TCF2: enforcing P1 and P2 by default'); - enforcementRules = DEFAULT_RULES; - } else { - enforcementRules = rules; } + rules = Object.fromEntries((rules || []).map(r => [r.purpose, r])); strictStorageEnforcement = !!deepAccess(config, STRICT_STORAGE_ENFORCEMENT); - purpose1Rule = find(enforcementRules, hasPurpose(1)); - purpose2Rule = find(enforcementRules, hasPurpose(2)); - purpose4Rule = find(enforcementRules, hasPurpose(4)) - purpose7Rule = find(enforcementRules, hasPurpose(7)); - - if (!purpose1Rule) { - purpose1Rule = DEFAULT_RULES[0]; - } - - if (!purpose2Rule) { - purpose2Rule = DEFAULT_RULES[1]; - } + Object.entries(CONFIGURABLE_RULES).forEach(([name, opts]) => { + ACTIVE_RULES[opts.type][opts.id] = rules[name] ?? opts.default; + }); if (!hooksAdded) { - if (purpose1Rule) { + if (ACTIVE_RULES.purpose[1] != null) { hooksAdded = true; RULE_HANDLES.push(registerActivityControl(ACTIVITY_ACCESS_DEVICE, RULE_NAME, accessDeviceRule)); RULE_HANDLES.push(registerActivityControl(ACTIVITY_SYNC_USER, RULE_NAME, syncUserRule)); RULE_HANDLES.push(registerActivityControl(ACTIVITY_ENRICH_EIDS, RULE_NAME, enrichEidsRule)); } - if (purpose2Rule) { + if (ACTIVE_RULES.purpose[2] != null) { RULE_HANDLES.push(registerActivityControl(ACTIVITY_FETCH_BIDS, RULE_NAME, fetchBidsRule)); } - if (purpose4Rule) { + if (ACTIVE_RULES.purpose[4] != null) { RULE_HANDLES.push( registerActivityControl(ACTIVITY_TRANSMIT_UFPD, RULE_NAME, ufpdRule), registerActivityControl(ACTIVITY_ENRICH_UFPD, RULE_NAME, ufpdRule) ); } - if (purpose7Rule) { + if (ACTIVE_RULES.purpose[7] != null) { RULE_HANDLES.push(registerActivityControl(ACTIVITY_REPORT_ANALYTICS, RULE_NAME, reportAnalyticsRule)); } + if (ACTIVE_RULES.feature[1] != null) { + RULE_HANDLES.push(registerActivityControl(ACTIVITY_TRANSMIT_PRECISE_GEO, RULE_NAME, transmitPreciseGeoRule)); + } + RULE_HANDLES.push(registerActivityControl(ACTIVITY_TRANSMIT_EIDS, RULE_NAME, transmitEidsRule)); } } diff --git a/src/activities/redactor.js b/src/activities/redactor.js index 5942ee17152..d052c029c13 100644 --- a/src/activities/redactor.js +++ b/src/activities/redactor.js @@ -8,7 +8,17 @@ import { ACTIVITY_TRANSMIT_UFPD } from './activities.js'; -export const ORTB_UFPD_PATHS = ['user.data', 'user.ext.data', 'user.yob', 'user.gender', 'user.keywords', 'user.kwarray']; +export const ORTB_UFPD_PATHS = [ + 'data', + 'ext.data', + 'yob', + 'gender', + 'keywords', + 'kwarray', + 'id', + 'buyeruid', + 'customdata' +].map(f => `user.${f}`); export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids']; export const ORTB_GEO_PATHS = ['user.geo.lat', 'user.geo.lon', 'device.geo.lat', 'device.geo.lon']; diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 1d0eee923b9..2880b2fac5d 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -1,13 +1,12 @@ import { accessDeviceRule, - deviceAccessHook, - enforcementRules, enrichEidsRule, fetchBidsRule, + transmitEidsRule, + transmitPreciseGeoRule, getGvlid, getGvlidFromAnalyticsAdapter, - purpose1Rule, - purpose2Rule, + ACTIVE_RULES, reportAnalyticsRule, setEnforcementConfig, STRICT_STORAGE_ENFORCEMENT, @@ -474,6 +473,261 @@ describe('gdpr enforcement', function () { }); }); + describe('transmitEidsRule', () => { + const GVL_ID = 123; + const BIDDER = 'mockBidder'; + let cd; + const RULES_2_10 = { + basicAds: 2, + personalizedAds: 4, + measurement: 7, + } + + beforeEach(() => { + cd = setupConsentData(); + cd.vendorData = { + vendor: { + consents: {}, + legitimateInterests: {}, + }, + purpose: { + consents: {}, + legitimateInterests: {} + } + }; + Object.assign(gvlids, { + [BIDDER]: GVL_ID + }); + }); + + function setVendorConsent(type = 'consents') { + cd.vendorData.vendor[type][GVL_ID] = true; + } + + function runRule() { + return transmitEidsRule(activityParams(MODULE_TYPE_BIDDER, BIDDER)); + } + + describe('default behavior', () => { + const CS_PURPOSES = [3, 4, 5, 6, 7, 8, 9, 10]; + const LI_PURPOSES = [2]; + const CONSENT_TYPES = ['consents', 'legitimateInterests']; + + describe('should deny if', () => { + describe('config is default', () => { + beforeEach(() => { + setEnforcementConfig({}); + }); + + it('no consent is given, of any type or for any vendor', () => { + expectAllow(false, runRule()); + }); + + CONSENT_TYPES.forEach(type => { + it(`vendor ${type} is given, but no purpose has consent`, () => { + setVendorConsent(type); + expectAllow(false, runRule()); + }); + + it(`${type} is given for purpose other than 2-10`, () => { + setVendorConsent(type); + cd.vendorData.purpose[type][1] = true; + expectAllow(false, runRule()); + }); + + LI_PURPOSES.forEach(purpose => { + it(`purpose ${purpose} has ${type}, but vendor does not`, () => { + cd.vendorData.purpose[type][purpose] = true; + expectAllow(false, runRule()); + }); + }); + }); + }); + + describe(`no consent is given`, () => { + [ + { + enforcePurpose: false, + }, + { + enforceVendor: false, + }, + { + enforcePurpose: false, + enforceVendor: false, + } + ].forEach(t => { + it(`config has ${JSON.stringify(t)} for each of ${Object.keys(RULES_2_10).join(', ')}`, () => { + setEnforcementConfig({ + gdpr: { + rules: Object.keys(RULES_2_10).map(rule => Object.assign({ + purpose: rule, + vendorExceptions: [], + enforcePurpose: true, + enforceVendor: true + }, t)) + } + }); + expectAllow(false, runRule()); + }); + }); + }); + }); + + describe('should allow if', () => { + describe('config is default', () => { + beforeEach(() => { + setEnforcementConfig({}); + }); + LI_PURPOSES.forEach(purpose => { + it(`purpose ${purpose} has LI, vendor has LI`, () => { + setVendorConsent('legitimateInterests'); + cd.vendorData.purpose.legitimateInterests[purpose] = true; + expectAllow(true, runRule()); + }); + }); + + LI_PURPOSES.concat(CS_PURPOSES).forEach(purpose => { + it(`purpose ${purpose} has consent, vendor has consent`, () => { + setVendorConsent(); + cd.vendorData.purpose.consents[purpose] = true; + expectAllow(true, runRule()); + }); + }); + }); + + Object.keys(RULES_2_10).forEach(rule => { + it(`no consent given, but '${rule}' config has a vendor exception`, () => { + setEnforcementConfig({ + gdpr: { + rules: [ + { + purpose: rule, + enforceVendor: false, + enforcePurpose: false, + vendorExceptions: [BIDDER] + } + ] + } + }); + expectAllow(true, runRule()); + }); + + it(`vendor consent is missing, but '${rule}' config has a softVendorException`, () => { + setEnforcementConfig({ + gdpr: { + rules: [ + { + purpose: rule, + enforceVendor: false, + enforcePurpose: false, + softVendorExceptions: [BIDDER] + } + ] + } + }); + cd.vendorData.purpose.consents[RULES_2_10[rule]] = true; + expectAllow(true, runRule()); + }) + }); + }); + }); + + describe('with eidsRequireP4consent', () => { + function setupPAdsRule(cfg = {}) { + setEnforcementConfig({ + gdpr: { + rules: [ + Object.assign({ + purpose: 'personalizedAds', + eidsRequireP4Consent: true, + enforcePurpose: true, + enforceVendor: true, + }, cfg) + ] + } + }) + } + describe('allows when', () => { + Object.entries({ + 'purpose 4 consent is given'() { + setupPAdsRule(); + setVendorConsent(); + cd.vendorData.purpose.consents[4] = true + }, + 'enforcePurpose is false, with vendor consent given'() { + setupPAdsRule({enforcePurpose: false}); + setVendorConsent(); + }, + 'enforceVendor is false, with purpose consent given'() { + setupPAdsRule({enforceVendor: false}); + cd.vendorData.purpose.consents[4] = true; + }, + 'vendor is excepted'() { + setupPAdsRule({vendorExceptions: [BIDDER]}); + }, + 'vendor is softly excepted, with purpose consent given'() { + setupPAdsRule({softVendorExceptions: [BIDDER]}); + cd.vendorData.purpose.consents[4] = true; + } + }).forEach(([t, setup]) => { + it(t, () => { + setup(); + expectAllow(true, runRule()); + }); + }); + }); + describe('denies when', () => { + Object.entries({ + 'purpose 4 consent is not given'() { + setupPAdsRule(); + setVendorConsent(); + }, + 'vendor consent is not given'() { + setupPAdsRule(); + cd.vendorData.purpose.consents[4] = true + }, + }).forEach(([t, setup]) => { + it(t, () => { + setup(); + expectAllow(false, runRule()); + }) + }) + }) + }) + }); + + describe('transmitPreciseGeoRule', () => { + const BIDDER = 'mockBidder'; + let cd; + + function runRule() { + return transmitPreciseGeoRule(activityParams(MODULE_TYPE_BIDDER, BIDDER)) + } + + beforeEach(() => { + cd = setupConsentData(); + setEnforcementConfig({ + gdpr: { + rules: [{ + purpose: 'transmitPreciseGeo', + enforcePurpose: true, + enforceVendor: false + }] + } + }) + }); + + it('should allow when special feature 1 consent is given', () => { + cd.vendorData.specialFeatureOptins[1] = true; + expectAllow(true, runRule()); + }) + it('should deny when configured, but consent is missing', () => { + cd.vendorData.specialFeatureOptins[1] = false; + expectAllow(false, runRule()); + }); + }); + describe('validateRules', function () { const createGdprRule = (purposeName = 'storage', enforcePurpose = true, enforceVendor = true, vendorExceptions = [], softVendorExceptions = []) => ({ purpose: purposeName, @@ -631,7 +885,8 @@ describe('gdpr enforcement', function () { }); expect(logWarnSpy.calledOnce).to.equal(true); - expect(enforcementRules).to.deep.equal(DEFAULT_RULES); + expect(ACTIVE_RULES.purpose[1]).to.deep.equal(DEFAULT_RULES[0]); + expect(ACTIVE_RULES.purpose[2]).to.deep.equal(DEFAULT_RULES[1]); }); it('should enforce TCF2 Purpose 2 also if only Purpose 1 is defined in "rules"', function () { @@ -647,8 +902,8 @@ describe('gdpr enforcement', function () { } }); - expect(purpose1Rule).to.deep.equal(purpose1RuleDefinedInConfig); - expect(purpose2Rule).to.deep.equal(DEFAULT_RULES[1]); + expect(ACTIVE_RULES.purpose[1]).to.deep.equal(purpose1RuleDefinedInConfig); + expect(ACTIVE_RULES.purpose[2]).to.deep.equal(DEFAULT_RULES[1]); }); it('should enforce TCF2 Purpose 1 also if only Purpose 2 is defined in "rules"', function () { @@ -664,8 +919,8 @@ describe('gdpr enforcement', function () { } }); - expect(purpose1Rule).to.deep.equal(DEFAULT_RULES[0]); - expect(purpose2Rule).to.deep.equal(purpose2RuleDefinedInConfig); + expect(ACTIVE_RULES.purpose[1]).to.deep.equal(DEFAULT_RULES[0]); + expect(ACTIVE_RULES.purpose[2]).to.deep.equal(purpose2RuleDefinedInConfig); }); it('should use the "rules" defined in config if a definition found', function() { @@ -679,8 +934,8 @@ describe('gdpr enforcement', function () { enforceVendor: false }] setEnforcementConfig({gdpr: { rules }}); - - expect(enforcementRules).to.deep.equal(rules); + expect(ACTIVE_RULES.purpose[1]).to.deep.equal(rules[0]); + expect(ACTIVE_RULES.purpose[2]).to.deep.equal(rules[1]); }); }); From 308b48fd5fe47cdda4dcae3bf5a884d674bc401c Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:14:37 +0100 Subject: [PATCH 041/760] Alkimi Bid Adapter : insert keywords into bid-request param (#10511) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support * Support new OpenRTB parameters * Support new oRTB2 parameters * remove pos parameter * Add gvl_id into Alkimi adapter * Insert keywords into bid-request param --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: mik --- modules/alkimiBidAdapter.js | 3 +++ test/spec/modules/alkimiBidAdapter_spec.js | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 6274ad2bf1c..81d993e9ac8 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -59,6 +59,9 @@ export const spec = { h: screen.height }, ortb2: { + site: { + keywords: bidderRequest.ortb2?.site?.keywords + }, at: bidderRequest.ortb2?.at, bcat: bidderRequest.ortb2?.bcat, wseat: bidderRequest.ortb2?.wseat diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 08f00186358..3101fac7500 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -112,7 +112,15 @@ describe('alkimiBidAdapter', function () { vendorData: {}, gdprApplies: true }, - uspConsent: 'uspConsent' + uspConsent: 'uspConsent', + ortb2: { + site: { + keywords: 'test1, test2' + }, + at: 2, + bcat: ['BSW1', 'BSW2'], + wseat: ['16', '165'] + } } const bidderRequest = spec.buildRequests(bidRequests, requestData) @@ -138,6 +146,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) + expect(bidderRequest.data.ortb2).to.deep.contains({ at: 2, wseat: ['16', '165'], bcat: ['BSW1', 'BSW2'], site: { keywords: 'test1, test2' }, }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) From 99d58ddf1e2b6e6a3610dae86f89d82f334f8745 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 21 Sep 2023 07:18:23 -0700 Subject: [PATCH 042/760] PBjs Core : clean up `utils.js` (#10441) * remove bind * bidderUtils/getBidIdParameter * urlUtils/tryAppendQuerySTring * sizeUtils/getAdUnitSizes * getWindowFromDocument * htmlEscape/escapeUnsafeChars * compareOn to adpod * remove Math.min/max copies * undo getBidIdParameter * _each * map/contains * hasOwn * insertHtmlIntoIframe * getValueString/getKeys * getBidRequest * getKeyByValue * adUnitsFilter * gptUtils * isInteger * convertCamelCaseToUnderscore * convertTypes * chunk * fix lint --- .../anKeywords.js | 15 +- libraries/appnexusUtils/anUtils.js | 25 + libraries/chunk/chunk.js | 19 + libraries/gptUtils/gptUtils.js | 37 ++ libraries/htmlEscape/htmlEscape.js | 26 ++ libraries/sizeUtils/sizeUtils.js | 29 ++ .../transformParamsUtils/convertTypes.js | 36 ++ libraries/urlUtils/urlUtils.js | 7 + modules/33acrossBidAdapter.js | 2 +- modules/adWMGBidAdapter.js | 2 +- modules/adagioBidAdapter.js | 2 +- modules/adgenerationBidAdapter.js | 4 +- modules/adkernelBidAdapter.js | 2 +- modules/adlooxAnalyticsAdapter.js | 2 +- modules/adlooxRtdProvider.js | 2 +- modules/admaticBidAdapter.js | 2 +- modules/adpod.js | 18 +- modules/adrelevantisBidAdapter.js | 8 +- modules/adriverBidAdapter.js | 2 +- modules/adtargetBidAdapter.js | 3 +- modules/adtelligentBidAdapter.js | 4 +- modules/aduptechBidAdapter.js | 3 +- modules/ajaBidAdapter.js | 3 +- modules/appnexusBidAdapter.js | 18 +- modules/asoBidAdapter.js | 4 +- modules/audiencerunBidAdapter.js | 3 +- modules/beopBidAdapter.js | 3 +- modules/betweenBidAdapter.js | 3 +- modules/bidglassBidAdapter.js | 2 +- modules/brightcomBidAdapter.js | 15 +- modules/brightcomSSPBidAdapter.js | 3 +- modules/cadentApertureMXBidAdapter.js | 3 +- modules/connectadBidAdapter.js | 4 +- modules/conversantBidAdapter.js | 5 +- modules/cpmstarBidAdapter.js | 12 +- modules/craftBidAdapter.js | 6 +- modules/datablocksBidAdapter.js | 3 +- modules/datawrkzBidAdapter.js | 10 +- modules/dchain.js | 4 +- modules/displayioBidAdapter.js | 4 +- modules/docereeBidAdapter.js | 2 +- modules/ebdrBidAdapter.js | 2 +- modules/eplanningBidAdapter.js | 3 +- modules/eskimiBidAdapter.js | 9 +- modules/fledgeForGpt.js | 3 +- modules/getintentBidAdapter.js | 2 +- modules/gmosspBidAdapter.js | 7 +- modules/goldbachBidAdapter.js | 19 +- modules/growadvertisingBidAdapter.js | 2 +- modules/growthCodeIdSystem.js | 3 +- modules/growthCodeRtdProvider.js | 3 +- modules/holidBidAdapter.js | 3 +- modules/iasRtdProvider.js | 16 +- modules/imdsBidAdapter.js | 3 +- modules/ivsBidAdapter.js | 2 +- modules/ixBidAdapter.js | 4 +- modules/kueezBidAdapter.js | 14 +- modules/lockerdomeBidAdapter.js | 2 +- modules/mediafuseBidAdapter.js | 15 +- modules/medianetBidAdapter.js | 2 +- modules/mediasniperBidAdapter.js | 3 +- modules/mgidBidAdapter.js | 3 +- modules/minutemediaBidAdapter.js | 14 +- modules/nextMillenniumBidAdapter.js | 3 +- modules/nextrollBidAdapter.js | 3 +- modules/oguryBidAdapter.js | 3 +- modules/onomagicBidAdapter.js | 3 +- modules/open8BidAdapter.js | 3 +- modules/openwebBidAdapter.js | 3 +- modules/openxBidAdapter.js | 3 +- modules/optidigitalBidAdapter.js | 7 +- modules/orbitsoftBidAdapter.js | 13 +- modules/otmBidAdapter.js | 3 +- modules/pixfutureBidAdapter.js | 5 +- modules/prebidServerBidAdapter/index.js | 8 +- modules/priceFloors.js | 2 +- modules/prismaBidAdapter.js | 2 +- modules/pubmaticAnalyticsAdapter.js | 9 +- modules/pubmaticBidAdapter.js | 3 +- modules/pubxaiAnalyticsAdapter.js | 3 +- modules/pulsepointBidAdapter.js | 3 +- modules/rasBidAdapter.js | 3 +- modules/relaidoBidAdapter.js | 12 +- modules/revcontentBidAdapter.js | 3 +- modules/riseBidAdapter.js | 14 +- modules/rubiconBidAdapter.js | 2 +- modules/schain.js | 27 +- modules/sharethroughAnalyticsAdapter.js | 2 +- modules/shinezBidAdapter.js | 14 +- modules/showheroes-bsBidAdapter.js | 3 +- modules/slimcutBidAdapter.js | 2 +- modules/smaatoBidAdapter.js | 22 +- modules/smartxBidAdapter.js | 15 +- modules/sonobiBidAdapter.js | 3 +- modules/sovrnBidAdapter.js | 3 +- modules/spotxBidAdapter.js | 18 +- modules/teadsBidAdapter.js | 2 +- modules/trafficgateBidAdapter.js | 3 +- modules/trionBidAdapter.js | 3 +- modules/tripleliftBidAdapter.js | 3 +- modules/underdogmediaBidAdapter.js | 2 +- modules/vdoaiBidAdapter.js | 2 +- modules/ventesBidAdapter.js | 3 +- modules/vidazooBidAdapter.js | 3 +- modules/videoreachBidAdapter.js | 2 +- modules/visxBidAdapter.js | 12 +- modules/weboramaRtdProvider.js | 4 +- modules/winrBidAdapter.js | 6 +- modules/xeBidAdapter.js | 3 +- src/adapterManager.js | 14 +- src/auction.js | 8 +- src/config.js | 19 +- src/events.js | 14 +- src/native.js | 5 +- src/prebid.js | 7 +- src/utils.js | 428 ++---------------- test/spec/appnexusKeywords_spec.js | 2 +- test/spec/libraries/sizeUtils_spec.js | 30 ++ test/spec/libraries/urlUtils_spec.js | 24 + test/spec/modules/fledgeForGpt_spec.js | 7 +- .../modules/pubxaiAnalyticsAdapter_spec.js | 16 +- test/spec/modules/sonobiBidAdapter_spec.js | 16 +- test/spec/utils_spec.js | 55 +-- 123 files changed, 680 insertions(+), 726 deletions(-) rename libraries/{appnexusKeywords => appnexusUtils}/anKeywords.js (91%) create mode 100644 libraries/appnexusUtils/anUtils.js create mode 100644 libraries/chunk/chunk.js create mode 100644 libraries/gptUtils/gptUtils.js create mode 100644 libraries/htmlEscape/htmlEscape.js create mode 100644 libraries/sizeUtils/sizeUtils.js create mode 100644 libraries/transformParamsUtils/convertTypes.js create mode 100644 libraries/urlUtils/urlUtils.js create mode 100644 test/spec/libraries/sizeUtils_spec.js create mode 100644 test/spec/libraries/urlUtils_spec.js diff --git a/libraries/appnexusKeywords/anKeywords.js b/libraries/appnexusUtils/anKeywords.js similarity index 91% rename from libraries/appnexusKeywords/anKeywords.js rename to libraries/appnexusUtils/anKeywords.js index 5dc0b453253..d6714dacc21 100644 --- a/libraries/appnexusKeywords/anKeywords.js +++ b/libraries/appnexusUtils/anKeywords.js @@ -1,4 +1,4 @@ -import {_each, deepAccess, getValueString, isArray, isStr, mergeDeep, isNumber} from '../../src/utils.js'; +import {_each, deepAccess, isArray, isNumber, isStr, mergeDeep, logWarn} from '../../src/utils.js'; import {getAllOrtbKeywords} from '../keywords/keywords.js'; import {CLIENT_SECTIONS} from '../../src/fpd/oneClient.js'; @@ -12,6 +12,19 @@ const ORTB_SEG_PATHS = ['user.data'].concat( CLIENT_SECTIONS.map((prefix) => `${prefix}.content.data`) ); +function getValueString(param, val, defaultValue) { + if (val === undefined || val === null) { + return defaultValue; + } + if (isStr(val)) { + return val; + } + if (isNumber(val)) { + return val.toString(); + } + logWarn('Unsuported type for param: ' + param + ' required type: String'); +} + /** * Converts an object of arrays (either strings or numbers) into an array of objects containing key and value properties * normally read from bidder params diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js new file mode 100644 index 00000000000..9b55cd5c2a4 --- /dev/null +++ b/libraries/appnexusUtils/anUtils.js @@ -0,0 +1,25 @@ +/** + * Converts a string value in camel-case to underscore eg 'placementId' becomes 'placement_id' + * @param {string} value string value to convert + */ +import {deepClone, isPlainObject} from '../../src/utils.js'; + +export function convertCamelToUnderscore(value) { + return value.replace(/(?:^|\.?)([A-Z])/g, function (x, y) { + return '_' + y.toLowerCase(); + }).replace(/^_/, ''); +} + +/** + * Creates an array of n length and fills each item with the given value + */ +export function fill(value, length) { + let newArray = []; + + for (let i = 0; i < length; i++) { + let valueToPush = isPlainObject(value) ? deepClone(value) : value; + newArray.push(valueToPush); + } + + return newArray; +} diff --git a/libraries/chunk/chunk.js b/libraries/chunk/chunk.js new file mode 100644 index 00000000000..57be7bd5016 --- /dev/null +++ b/libraries/chunk/chunk.js @@ -0,0 +1,19 @@ +/** + * http://npm.im/chunk + * Returns an array with *size* chunks from given array + * + * Example: + * ['a', 'b', 'c', 'd', 'e'] chunked by 2 => + * [['a', 'b'], ['c', 'd'], ['e']] + */ +export function chunk(array, size) { + let newArray = []; + + for (let i = 0; i < Math.ceil(array.length / size); i++) { + let start = i * size; + let end = start + size; + newArray.push(array.slice(start, end)); + } + + return newArray; +} diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js new file mode 100644 index 00000000000..950f28c618f --- /dev/null +++ b/libraries/gptUtils/gptUtils.js @@ -0,0 +1,37 @@ +import {find} from '../../src/polyfill.js'; +import {compareCodeAndSlot, isGptPubadsDefined} from '../../src/utils.js'; + +/** + * Returns filter function to match adUnitCode in slot + * @param {string} adUnitCode AdUnit code + * @return {function} filter function + */ +export function isSlotMatchingAdUnitCode(adUnitCode) { + return (slot) => compareCodeAndSlot(slot, adUnitCode); +} + +/** + * @summary Uses the adUnit's code in order to find a matching gpt slot object on the page + */ +export function getGptSlotForAdUnitCode(adUnitCode) { + let matchingSlot; + if (isGptPubadsDefined()) { + // find the first matching gpt slot on the page + matchingSlot = find(window.googletag.pubads().getSlots(), isSlotMatchingAdUnitCode(adUnitCode)); + } + return matchingSlot; +} + +/** + * @summary Uses the adUnit's code in order to find a matching gptSlot on the page + */ +export function getGptSlotInfoForAdUnitCode(adUnitCode) { + const matchingSlot = getGptSlotForAdUnitCode(adUnitCode); + if (matchingSlot) { + return { + gptSlot: matchingSlot.getAdUnitPath(), + divId: matchingSlot.getSlotElementId() + }; + } + return {}; +} diff --git a/libraries/htmlEscape/htmlEscape.js b/libraries/htmlEscape/htmlEscape.js new file mode 100644 index 00000000000..f0952c02e3c --- /dev/null +++ b/libraries/htmlEscape/htmlEscape.js @@ -0,0 +1,26 @@ +/** + * Encode a string for inclusion in HTML. + * See https://pragmaticwebsecurity.com/articles/spasecurity/json-stringify-xss.html and + * https://codeql.github.com/codeql-query-help/javascript/js-bad-code-sanitization/ + * @return {string} + */ +export const escapeUnsafeChars = (() => { + const escapes = { + '<': '\\u003C', + '>': '\\u003E', + '/': '\\u002F', + '\\': '\\\\', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t', + '\0': '\\0', + '\u2028': '\\u2028', + '\u2029': '\\u2029' + }; + + return function (str) { + return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029\\]/g, x => escapes[x]); + }; +})(); diff --git a/libraries/sizeUtils/sizeUtils.js b/libraries/sizeUtils/sizeUtils.js new file mode 100644 index 00000000000..41cdd71df89 --- /dev/null +++ b/libraries/sizeUtils/sizeUtils.js @@ -0,0 +1,29 @@ +/** + * Read an adUnit object and return the sizes used in an [[728, 90]] format (even if they had [728, 90] defined) + * Preference is given to the `adUnit.mediaTypes.banner.sizes` object over the `adUnit.sizes` + * @param {object} adUnit one adUnit object from the normal list of adUnits + * @returns {Array.} array of arrays containing numeric sizes + */ +export function getAdUnitSizes(adUnit) { + if (!adUnit) { + return; + } + + let sizes = []; + if (adUnit.mediaTypes && adUnit.mediaTypes.banner && Array.isArray(adUnit.mediaTypes.banner.sizes)) { + let bannerSizes = adUnit.mediaTypes.banner.sizes; + if (Array.isArray(bannerSizes[0])) { + sizes = bannerSizes; + } else { + sizes.push(bannerSizes); + } + // TODO - remove this else block when we're ready to deprecate adUnit.sizes for bidders + } else if (Array.isArray(adUnit.sizes)) { + if (Array.isArray(adUnit.sizes[0])) { + sizes = adUnit.sizes; + } else { + sizes.push(adUnit.sizes); + } + } + return sizes; +} diff --git a/libraries/transformParamsUtils/convertTypes.js b/libraries/transformParamsUtils/convertTypes.js new file mode 100644 index 00000000000..813d8e6e693 --- /dev/null +++ b/libraries/transformParamsUtils/convertTypes.js @@ -0,0 +1,36 @@ +import {isFn} from '../../src/utils.js'; + +/** + * Try to convert a value to a type. + * If it can't be done, the value will be returned. + * + * @param {string} typeToConvert The target type. e.g. "string", "number", etc. + * @param {*} value The value to be converted into typeToConvert. + */ +function tryConvertType(typeToConvert, value) { + if (typeToConvert === 'string') { + return value && value.toString(); + } else if (typeToConvert === 'number') { + return Number(value); + } else { + return value; + } +} + +export function convertTypes(types, params) { + Object.keys(types).forEach(key => { + if (params[key]) { + if (isFn(types[key])) { + params[key] = types[key](params[key]); + } else { + params[key] = tryConvertType(types[key], params[key]); + } + + // don't send invalid values + if (isNaN(params[key])) { + delete params.key; + } + } + }); + return params; +} diff --git a/libraries/urlUtils/urlUtils.js b/libraries/urlUtils/urlUtils.js new file mode 100644 index 00000000000..f0c5823aab1 --- /dev/null +++ b/libraries/urlUtils/urlUtils.js @@ -0,0 +1,7 @@ +export function tryAppendQueryString(existingUrl, key, value) { + if (value) { + return existingUrl + key + '=' + encodeURIComponent(value) + '&'; + } + + return existingUrl; +} diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index b965183de19..0e9beb22013 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -6,7 +6,6 @@ import { getWindowTop, isArray, isGptPubadsDefined, - isSlotMatchingAdUnitCode, logInfo, logWarn, mergeDeep, @@ -14,6 +13,7 @@ import { uniques } from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; // **************************** UTILS *************************** // const BIDDER_CODE = '33across'; diff --git a/modules/adWMGBidAdapter.js b/modules/adWMGBidAdapter.js index 36935e80d3b..d268c4cafa8 100644 --- a/modules/adWMGBidAdapter.js +++ b/modules/adWMGBidAdapter.js @@ -1,9 +1,9 @@ 'use strict'; -import { tryAppendQueryString } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'adWMG'; const ENDPOINT = 'https://hb.adwmg.com/hb'; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index c642dff5a8f..3de584a1195 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -6,7 +6,6 @@ import { deepClone, generateUUID, getDNT, - getGptSlotInfoForAdUnitCode, getUniqueIdentifierStr, getWindowSelf, getWindowTop, @@ -34,6 +33,7 @@ import {OUTSTREAM} from '../src/video.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { userSync } from '../src/userSync.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index bf75756174d..b40378c8e35 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -1,8 +1,10 @@ -import {tryAppendQueryString, getBidIdParameter, escapeUnsafeChars, deepAccess} from '../src/utils.js'; +import {deepAccess, getBidIdParameter} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +import {escapeUnsafeChars} from '../libraries/htmlEscape/htmlEscape.js'; const ADG_BIDDER_CODE = 'adgeneration'; diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 71d5c809e71..9d9da8cb0ab 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -5,7 +5,6 @@ import { createTrackPixelHtml, deepAccess, deepSetValue, - getAdUnitSizes, getDefinedParams, getDNT, isArray, @@ -22,6 +21,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; /* * In case you're AdKernel whitelable platform's client who needs branded adapter to diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 5db75c656bb..9284d543298 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -14,7 +14,6 @@ import {find} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; import { deepAccess, - getGptSlotInfoForAdUnitCode, getUniqueIdentifierStr, insertElement, isFn, @@ -28,6 +27,7 @@ import { mergeDeep, parseUrl } from '../src/utils.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const MODULE = 'adlooxAnalyticsAdapter'; diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index c2037429185..727dc84e399 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -25,7 +25,6 @@ import { deepAccess, deepClone, deepSetValue, - getGptSlotInfoForAdUnitCode, isArray, isBoolean, isInteger, @@ -37,6 +36,7 @@ import { parseUrl, safeJSONParse } from '../src/utils.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const MODULE_NAME = 'adloox'; const MODULE = `${MODULE_NAME}RtdProvider`; diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 436f918a0f6..52c06318ec0 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, isEmpty, deepAccess, getBidIdParameter, isArray } from '../src/utils.js'; +import {getValue, logError, isEmpty, deepAccess, isArray, getBidIdParameter} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; diff --git a/modules/adpod.js b/modules/adpod.js index 0318785d55e..d2fd817ee62 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -13,7 +13,6 @@ */ import { - compareOn, deepAccess, generateUUID, groupBy, @@ -591,6 +590,23 @@ function getAdPodAdUnits(codes) { .filter((adUnit) => (codes.length > 0) ? codes.indexOf(adUnit.code) != -1 : true); } +/** + * This function will create compare function to sort on object property + * @param {string} property + * @returns {function} compare function to be used in sorting + */ +function compareOn(property) { + return function compare(a, b) { + if (a[property] < b[property]) { + return 1; + } + if (a[property] > b[property]) { + return -1; + } + return 0; + } +} + /** * This function removes bids of same category. It will be used when competitive exclusion is enabled. * @param {Array[Object]} bidsReceived diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index f1f92e5dd5e..3c9c661b09c 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -1,8 +1,5 @@ import {Renderer} from '../src/Renderer.js'; import { - chunk, - convertCamelToUnderscore, - convertTypes, createTrackPixelHtml, deepAccess, deepClone, @@ -21,7 +18,10 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'adrelevantis'; const URL = 'https://ssp.adrelevantis.com/prebid'; diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index 1af0cffa700..5bce315f572 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -1,5 +1,5 @@ // ADRIVER BID ADAPTER for Prebid 1.13 -import { logInfo, getWindowLocation, getBidIdParameter, _each } from '../src/utils.js'; +import {logInfo, getWindowLocation, _each, getBidIdParameter} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index 89ba4878acf..a1dec5a420f 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -1,8 +1,9 @@ -import {_map, chunk, deepAccess, flatten, isArray, logError, parseSizesInput} from '../src/utils.js'; +import {_map, deepAccess, flatten, isArray, logError, parseSizesInput} 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 {find} from '../src/polyfill.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const ENDPOINT = 'https://ghb.console.adtarget.com.tr/v2/auction/'; const BIDDER_CODE = 'adtarget'; diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a315c9a696e..04bca21c60f 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -1,9 +1,11 @@ -import {_map, chunk, convertTypes, deepAccess, flatten, isArray, parseSizesInput} from '../src/utils.js'; +import {_map, deepAccess, flatten, isArray, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const subdomainSuffixes = ['', 1, 2]; const AUCTION_PATH = '/v2/auction/'; diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index 1ea5f1a0096..49187da2fe2 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -1,7 +1,8 @@ -import {deepClone, getAdUnitSizes, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; +import {deepClone, isArray, isBoolean, isEmpty, isFn, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; export const BIDDER_CODE = 'aduptech'; export const GVLID = 647; diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index ffab41611ef..9049197e565 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,8 @@ -import { getBidIdParameter, tryAppendQueryString, createTrackPixelHtml, logError, logWarn, deepAccess } from '../src/utils.js'; +import {createTrackPixelHtml, logError, logWarn, deepAccess, getBidIdParameter} from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BidderCode = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index ae4b1a0d489..e6b3441b988 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1,17 +1,10 @@ import { - chunk, - convertCamelToUnderscore, - convertTypes, createTrackPixelHtml, deepAccess, deepClone, - fill, getBidRequest, - getMaxValueFromArray, - getMinValueFromArray, getParameterByName, getUniqueIdentifierStr, - getWindowFromDocument, isArray, isArrayOfNums, isEmpty, @@ -40,7 +33,10 @@ import { getANKewyordParamFromMaps, getANKeywordParam, transformBidderParamKeywords -} from '../libraries/appnexusKeywords/anKeywords.js'; +} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -1031,7 +1027,7 @@ function createAdPodRequest(tags, adPodBid) { const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = getMaxValueFromArray(durationRangeSec); + const maxDuration = Math.max(...durationRangeSec); const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); let request = fill(...tagToDuplicate, numberOfPlacements); @@ -1057,7 +1053,7 @@ function createAdPodRequest(tags, adPodBid) { function getAdPodPlacementNumber(videoParams) { const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = getMinValueFromArray(durationRangeSec); + const minAllowedDuration = Math.min(...durationRangeSec); const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); return requireExactDuration @@ -1142,7 +1138,7 @@ function outstreamRender(bid, doc) { hideSASIframe(bid.adUnitCode); // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { - const win = getWindowFromDocument(doc) || window; + const win = doc?.defaultView || window; win.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, sizes: [bid.getSize().split('x')], diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index e569f04a2a8..704cffefb39 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -7,14 +7,14 @@ import { isArray, isFn, logWarn, - parseSizesInput, - tryAppendQueryString + 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 {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'aso'; const DEFAULT_SERVER_URL = 'https://srv.aso1.net'; diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 9beb20d4f77..e716fe94c8b 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -1,8 +1,7 @@ import { _each, deepAccess, - formatQS, - getBidIdParameter, + formatQS, getBidIdParameter, getValue, isArray, isFn, diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index c5282c28cfc..b6b6107ddd0 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,7 +1,6 @@ import { buildUrl, - deepAccess, - getBidIdParameter, + deepAccess, getBidIdParameter, getValue, isArray, logInfo, diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index d615e433cc0..6883b7cce2c 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -1,6 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getAdUnitSizes, parseSizesInput} from '../src/utils.js'; +import {parseSizesInput} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'between'; let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index 3184372881b..a29976cfcb7 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, isArray, getBidIdParameter, deepClone, getUniqueIdentifierStr } from '../src/utils.js'; +import {_each, isArray, deepClone, getUniqueIdentifierStr, getBidIdParameter} from '../src/utils.js'; // import {config} from 'src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index c4cc5394a03..1fa1dac4e95 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -1,4 +1,17 @@ -import { getBidIdParameter, _each, isArray, getWindowTop, getUniqueIdentifierStr, deepSetValue, logError, logWarn, createTrackPixelHtml, getWindowSelf, isFn, isPlainObject } from '../src/utils.js'; +import { + _each, + isArray, + getWindowTop, + getUniqueIdentifierStr, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, + getBidIdParameter +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js index b85a01c8fc7..4750881da40 100644 --- a/modules/brightcomSSPBidAdapter.js +++ b/modules/brightcomSSPBidAdapter.js @@ -1,5 +1,4 @@ import { - getBidIdParameter, isArray, getWindowTop, getUniqueIdentifierStr, @@ -9,7 +8,7 @@ import { createTrackPixelHtml, getWindowSelf, isFn, - isPlainObject, + isPlainObject, getBidIdParameter, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index 079ca592160..e73564dacdb 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -1,7 +1,6 @@ import { _each, - deepAccess, - getBidIdParameter, + deepAccess, getBidIdParameter, isArray, isFn, isPlainObject, diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index d5665b318be..b40ef30f6bc 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -1,7 +1,9 @@ -import { deepSetValue, convertTypes, tryAppendQueryString, logWarn } from '../src/utils.js'; +import { deepSetValue, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index fd436e51461..bef65a43616 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -3,22 +3,21 @@ import { isStr, deepAccess, isArray, - getBidIdParameter, deepSetValue, isEmpty, _each, - convertTypes, parseUrl, mergeDeep, buildUrl, _map, logError, isFn, - isPlainObject, + isPlainObject, getBidIdParameter, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; // Maintainer: mediapsr@epsilon.com diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js index 9e237ef2558..e076fb4b0bb 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,8 +1,8 @@ - import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {config} from '../src/config.js'; +import {getBidIdParameter} from '../src/utils.js'; const BIDDER_CODE = 'cpmstar'; @@ -49,13 +49,13 @@ export const spec = { var bidRequest = validBidRequests[i]; var referer = bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : bidderRequest.refererInfo.domain; referer = encodeURIComponent(referer); - var e = utils.getBidIdParameter('endpoint', bidRequest.params); + var e = getBidIdParameter('endpoint', bidRequest.params); var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; var mediaType = spec.getMediaType(bidRequest); var playerSize = spec.getPlayerSize(bidRequest); var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + - '&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) + + '&json=c_b&mv=1&poolid=' + getBidIdParameter('placementId', bidRequest.params) + '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + '&requestid=' + bidRequest.bidId + '&referer=' + encodeURIComponent(referer); diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 8f7821173c1..a2a054d7659 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,4 +1,4 @@ -import {convertCamelToUnderscore, convertTypes, getBidRequest, logError} from '../src/utils.js'; +import {getBidRequest, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {auctionManager} from '../src/auctionManager.js'; @@ -7,7 +7,9 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 11d3ebb1589..395706994fe 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -1,10 +1,11 @@ -import {deepAccess, getAdUnitSizes, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; +import {deepAccess, getWindowTop, isEmpty, isGptPubadsDefined} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; export const storage = getStorageManager({bidderCode: 'datablocks'}); diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 2cf28c36330..127e7893ec5 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -1,4 +1,12 @@ -import { deepAccess, getBidIdParameter, isArray, getUniqueIdentifierStr, contains, isFn, isPlainObject } from '../src/utils.js'; +import { + deepAccess, + isArray, + getUniqueIdentifierStr, + contains, + isFn, + isPlainObject, + getBidIdParameter +} from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; diff --git a/modules/dchain.js b/modules/dchain.js index daf97a7551f..7f84282b81e 100644 --- a/modules/dchain.js +++ b/modules/dchain.js @@ -1,7 +1,7 @@ import {includes} from '../src/polyfill.js'; import {config} from '../src/config.js'; import {getHook} from '../src/hook.js'; -import {_each, deepAccess, deepClone, hasOwn, isArray, isPlainObject, isStr, logError, logWarn} from '../src/utils.js'; +import {_each, deepAccess, deepClone, isArray, isPlainObject, isStr, logError, logWarn} from '../src/utils.js'; import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; const shouldBeAString = ' should be a string'; @@ -49,7 +49,7 @@ export function checkDchainSyntax(bid, mode) { appendFailMsg(`dchain.ver` + shouldBeAString); } - if (hasOwn(dchainObj, 'ext')) { + if (dchainObj.hasOwnProperty('ext')) { if (!isPlainObject(dchainObj.ext)) { appendFailMsg(`dchain.ext` + shouldBeAnObject); } diff --git a/modules/displayioBidAdapter.js b/modules/displayioBidAdapter.js index 3d34f2c8b26..3cdfd3a77cd 100644 --- a/modules/displayioBidAdapter.js +++ b/modules/displayioBidAdapter.js @@ -1,7 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; -import {getWindowFromDocument, logWarn} from '../src/utils.js'; +import {logWarn} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; @@ -156,7 +156,7 @@ function newRenderer(bid) { function webisRender(bid, doc) { bid.renderer.push(() => { - const win = getWindowFromDocument(doc) || window; + const win = doc?.defaultView || window; win.webis.init(bid.adData, bid.adUnitCode, bid.params); }) } diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js index 524f464cee3..fa4446ede47 100644 --- a/modules/docereeBidAdapter.js +++ b/modules/docereeBidAdapter.js @@ -1,7 +1,7 @@ -import { tryAppendQueryString } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'doceree'; const END_POINT = 'https://bidder.doceree.com' diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js index a03a1ec12ca..e830f8a94f7 100644 --- a/modules/ebdrBidAdapter.js +++ b/modules/ebdrBidAdapter.js @@ -1,4 +1,4 @@ -import { logInfo, getBidIdParameter } from '../src/utils.js'; +import {getBidIdParameter, logInfo} from '../src/utils.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'ebdr'; diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 1ebbc78730c..d57804c04e6 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -1,8 +1,9 @@ -import {getWindowSelf, isEmpty, parseSizesInput, isGptPubadsDefined, isSlotMatchingAdUnitCode} from '../src/utils.js'; +import {getWindowSelf, isEmpty, parseSizesInput, isGptPubadsDefined} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'eplanning'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 88d8f95b859..81b8c5d8058 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -1,7 +1,8 @@ -import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; +import {getBidIdParameter} from '../src/utils.js'; const BIDDER_CODE = 'eskimi'; // const ENDPOINT = 'https://hb.eskimi.com/bids' @@ -65,7 +66,7 @@ const CONVERTER = ortbConverter({ imp.secure = Number(window.location.protocol === 'https:'); if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; - imp.bidfloorcur = utils.getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' } if (bidRequest.mediaTypes[VIDEO]) { diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index eddb7424f92..fd29c41210c 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -4,12 +4,13 @@ */ import { config } from '../src/config.js'; import { getHook } from '../src/hook.js'; -import {deepSetValue, getGptSlotForAdUnitCode, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js' import CONSTANTS from '../src/constants.json'; import {currencyCompare} from '../libraries/currencyUtils/currency.js'; import {maximum, minimum} from '../src/utils/reducers.js'; +import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const MODULE = 'fledgeForGpt' const PENDING = {}; diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 25322d81f9b..2b6ea1c2c2a 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,4 +1,4 @@ -import { getBidIdParameter, isFn, isInteger } from '../src/utils.js'; +import {getBidIdParameter, isFn, isInteger} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'getintent'; diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index 8c90d0cccfe..559f9f77aaf 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -1,17 +1,16 @@ import { createTrackPixelHtml, deepAccess, - deepSetValue, - getBidIdParameter, + deepSetValue, getBidIdParameter, getDNT, getWindowTop, isEmpty, - logError, - tryAppendQueryString + logError } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER} from '../src/mediaTypes.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'gmossp'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 4768931950c..8892df130df 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -1,15 +1,9 @@ import {Renderer} from '../src/Renderer.js'; import { - chunk, - convertCamelToUnderscore, - convertTypes, createTrackPixelHtml, deepAccess, deepClone, - fill, getBidRequest, - getMaxValueFromArray, - getMinValueFromArray, getParameterByName, isArray, isArrayOfNums, @@ -29,9 +23,12 @@ import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { APPNEXUS_CATEGORY_MAPPING } from '../libraries/categoryTranslationMapping/index.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; +import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'goldbach'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -971,7 +968,7 @@ function createAdPodRequest(tags, adPodBid) { const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = getMaxValueFromArray(durationRangeSec); + const maxDuration = Math.max(...durationRangeSec); const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); let request = fill(...tagToDuplicate, numberOfPlacements); @@ -997,7 +994,7 @@ function createAdPodRequest(tags, adPodBid) { function getAdPodPlacementNumber(videoParams) { const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = getMinValueFromArray(durationRangeSec); + const minAllowedDuration = Math.min(...durationRangeSec); const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); return requireExactDuration diff --git a/modules/growadvertisingBidAdapter.js b/modules/growadvertisingBidAdapter.js index b9b256dbaff..f6f7867f0fe 100644 --- a/modules/growadvertisingBidAdapter.js +++ b/modules/growadvertisingBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { getBidIdParameter, deepAccess, _each, triggerPixel } from '../src/utils.js'; +import {deepAccess, _each, triggerPixel, getBidIdParameter} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index aec49c64fa3..e50a4e73019 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -5,11 +5,12 @@ * @requires module:modules/userId */ -import {logError, logInfo, pick, tryAppendQueryString} from '../src/utils.js'; +import {logError, logInfo, pick} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const MODULE_NAME = 'growthCodeId'; const GC_DATA_KEY = '_gc_data'; diff --git a/modules/growthCodeRtdProvider.js b/modules/growthCodeRtdProvider.js index 370ace9a203..ef5c7906ad7 100644 --- a/modules/growthCodeRtdProvider.js +++ b/modules/growthCodeRtdProvider.js @@ -5,10 +5,11 @@ import { submodule } from '../src/hook.js' import { getStorageManager } from '../src/storageManager.js'; import { - logMessage, logError, tryAppendQueryString, mergeDeep + logMessage, logError, mergeDeep } from '../src/utils.js'; import * as ajax from '../src/ajax.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const MODULE_NAME = 'growthCodeRtd'; const LOG_PREFIX = 'GrowthCodeRtd: '; diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index 2073063168d..fbcbb9492c7 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -1,7 +1,6 @@ import { deepAccess, - deepSetValue, - getBidIdParameter, + deepSetValue, getBidIdParameter, isStr, logMessage, triggerPixel, diff --git a/modules/iasRtdProvider.js b/modules/iasRtdProvider.js index 994be7c0804..b9de7ef4e46 100644 --- a/modules/iasRtdProvider.js +++ b/modules/iasRtdProvider.js @@ -1,7 +1,9 @@ -import { submodule } from '../src/hook.js'; +import {submodule} from '../src/hook.js'; import * as utils from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import {ajax} from '../src/ajax.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -76,7 +78,7 @@ function getAdUnitPath(adSlot, bidRequest, adUnitPath) { if (!utils.isEmpty(adSlot)) { p = adSlot.gptSlot; } else { - if (!utils.isEmpty(adUnitPath) && utils.hasOwn(adUnitPath, bidRequest.code)) { + if (!utils.isEmpty(adUnitPath) && adUnitPath.hasOwnProperty(bidRequest.code)) { if (utils.isStr(adUnitPath[bidRequest.code]) && !utils.isEmpty(adUnitPath[bidRequest.code])) { p = adUnitPath[bidRequest.code]; } @@ -86,13 +88,13 @@ function getAdUnitPath(adSlot, bidRequest, adUnitPath) { } function stringifySlot(bidRequest, adUnitPath) { - const sizes = utils.getAdUnitSizes(bidRequest); + const sizes = getAdUnitSizes(bidRequest); const id = bidRequest.code; const ss = stringifySlotSizes(sizes); - const adSlot = utils.getGptSlotInfoForAdUnitCode(bidRequest.code); + const adSlot = getGptSlotInfoForAdUnitCode(bidRequest.code); const p = getAdUnitPath(adSlot, bidRequest, adUnitPath); const slot = { id, ss, p }; - const keyValues = utils.getKeys(slot).map(function (key) { + const keyValues = Object.keys(slot).map(function (key) { return [key, slot[key]].join(':'); }); return '{' + keyValues.join(',') + '}'; diff --git a/modules/imdsBidAdapter.js b/modules/imdsBidAdapter.js index d6f3df94409..122662feb8a 100644 --- a/modules/imdsBidAdapter.js +++ b/modules/imdsBidAdapter.js @@ -1,10 +1,11 @@ 'use strict'; -import {deepAccess, deepSetValue, getAdUnitSizes, isFn, isPlainObject, logWarn, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepSetValue, isFn, isPlainObject, logWarn, mergeDeep} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; import {config} from '../src/config.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BID_SCHEME = 'https://'; const BID_DOMAIN = 'technoratimedia.com'; diff --git a/modules/ivsBidAdapter.js b/modules/ivsBidAdapter.js index 47685fbbe46..6f4c024f09f 100644 --- a/modules/ivsBidAdapter.js +++ b/modules/ivsBidAdapter.js @@ -1,5 +1,5 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import { deepAccess, deepSetValue, getBidIdParameter, logError } from '../src/utils.js'; +import {deepAccess, deepSetValue, getBidIdParameter, logError} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { INSTREAM } from '../src/video.js'; diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 50595152b23..6c5f90b7a2a 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1,10 +1,8 @@ import { contains, - convertTypes, deepAccess, deepClone, deepSetValue, - getGptSlotInfoForAdUnitCode, inIframe, isArray, isEmpty, @@ -24,6 +22,8 @@ import { find } from '../src/polyfill.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 0a868661310..5a5536e0c1a 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -1,4 +1,16 @@ -import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + isInteger, + 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'; diff --git a/modules/lockerdomeBidAdapter.js b/modules/lockerdomeBidAdapter.js index 5c38753c1e2..5038eadce30 100644 --- a/modules/lockerdomeBidAdapter.js +++ b/modules/lockerdomeBidAdapter.js @@ -1,6 +1,6 @@ -import { getBidIdParameter } from '../src/utils.js'; import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getBidIdParameter} from '../src/utils.js'; export const spec = { code: 'lockerdome', diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 98179c49e0d..1fdd3530fae 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -1,14 +1,8 @@ import { - chunk, - convertCamelToUnderscore, - convertTypes, createTrackPixelHtml, deepAccess, deepClone, - fill, getBidRequest, - getMaxValueFromArray, - getMinValueFromArray, getParameterByName, isArray, isArrayOfNums, @@ -38,7 +32,10 @@ import { getANKewyordParamFromMaps, getANKeywordParam, transformBidderParamKeywords -} from '../libraries/appnexusKeywords/anKeywords.js'; +} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'mediafuse'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -959,7 +956,7 @@ function createAdPodRequest(tags, adPodBid) { const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = getMaxValueFromArray(durationRangeSec); + const maxDuration = Math.max(...durationRangeSec); const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); let request = fill(...tagToDuplicate, numberOfPlacements); @@ -985,7 +982,7 @@ function createAdPodRequest(tags, adPodBid) { function getAdPodPlacementNumber(videoParams) { const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = getMinValueFromArray(durationRangeSec); + const minAllowedDuration = Math.min(...durationRangeSec); const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); return requireExactDuration diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 659da0c16fb..041db71cd34 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,7 +1,6 @@ import { buildUrl, deepAccess, - getGptSlotInfoForAdUnitCode, getWindowTop, isArray, isEmpty, @@ -18,6 +17,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'medianet'; const TRUSTEDSTACK_CODE = 'trustedstack'; diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js index 378a804487a..aee5f6230b2 100644 --- a/modules/mediasniperBidAdapter.js +++ b/modules/mediasniperBidAdapter.js @@ -1,8 +1,7 @@ import { deepAccess, deepClone, - deepSetValue, - getBidIdParameter, + deepSetValue, getBidIdParameter, inIframe, isArray, isEmpty, diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 8e889261e52..1e158236deb 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -9,11 +9,10 @@ import { isEmpty, triggerPixel, logWarn, - getBidIdParameter, isFn, isNumber, isBoolean, - isInteger, deepSetValue, + isInteger, deepSetValue, getBidIdParameter, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index bb0bb76bdbc..e67534d74fe 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -1,4 +1,16 @@ -import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + isInteger, + 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'; diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index cb660ad9fd6..0cbe954175c 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,8 +1,7 @@ import { _each, createTrackPixelHtml, - deepAccess, - getBidIdParameter, + deepAccess, getBidIdParameter, getDefinedParams, getWindowTop, isArray, diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index 0dd4b334f6e..eab174d22dd 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -1,6 +1,5 @@ import { - deepAccess, - getBidIdParameter, + deepAccess, getBidIdParameter, isArray, isFn, isNumber, diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 4fd9b711b42..c1c8376de87 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -1,9 +1,10 @@ 'use strict'; import {BANNER} from '../src/mediaTypes.js'; -import {getAdUnitSizes, getWindowSelf, getWindowTop, isFn, logWarn} from '../src/utils.js'; +import {getWindowSelf, getWindowTop, isFn, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ajax} from '../src/ajax.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'ogury'; const GVLID = 31; diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index edab625e541..78f00153a8b 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,7 +1,6 @@ import { _each, - createTrackPixelHtml, - getBidIdParameter, + createTrackPixelHtml, getBidIdParameter, getUniqueIdentifierStr, getWindowSelf, getWindowTop, diff --git a/modules/open8BidAdapter.js b/modules/open8BidAdapter.js index 5fa1dd0a143..49523926c0e 100644 --- a/modules/open8BidAdapter.js +++ b/modules/open8BidAdapter.js @@ -1,8 +1,9 @@ import { Renderer } from '../src/Renderer.js'; import {ajax} from '../src/ajax.js'; -import { createTrackPixelHtml, getBidIdParameter, logError, logWarn, tryAppendQueryString } from '../src/utils.js'; +import {createTrackPixelHtml, getBidIdParameter, logError, logWarn} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'open8'; const URL = 'https://as.vt.open8.com/v1/control/prebid'; diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 296bfc682f1..547447039da 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,8 +1,9 @@ -import {convertTypes, deepAccess, flatten, isArray, isNumber, parseSizesInput} from '../src/utils.js'; +import {deepAccess, flatten, isArray, isNumber, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const ENDPOINT = 'https://ghb.spotim.market/v2/auction'; const BIDDER_CODE = 'openweb'; diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index d206e70aac4..181a0c70c7e 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -4,6 +4,7 @@ import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '2.0'; @@ -150,7 +151,7 @@ const converter = ortbConverter({ }); function transformBidParams(params, isOpenRtb) { - return utils.convertTypes({ + return convertTypes({ 'unit': 'string', 'customFloor': 'number' }, params); diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 489f2c8264c..9f27ae49d1e 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -1,6 +1,7 @@ -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { deepAccess, parseSizesInput, getAdUnitSizes } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {deepAccess, parseSizesInput} from '../src/utils.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'optidigital'; const GVL_ID = 915; diff --git a/modules/orbitsoftBidAdapter.js b/modules/orbitsoftBidAdapter.js index 4c3f2e38c58..f55c7ff9917 100644 --- a/modules/orbitsoftBidAdapter.js +++ b/modules/orbitsoftBidAdapter.js @@ -1,5 +1,6 @@ import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getBidIdParameter} from '../src/utils.js'; const BIDDER_CODE = 'orbitsoft'; let styleParamsMap = { @@ -45,10 +46,10 @@ export const spec = { for (let i = 0; i < validBidRequests.length; i++) { bidRequest = validBidRequests[i]; let bidRequestParams = bidRequest.params; - let placementId = utils.getBidIdParameter('placementId', bidRequestParams); - let requestUrl = utils.getBidIdParameter('requestUrl', bidRequestParams); - let referrer = utils.getBidIdParameter('ref', bidRequestParams); - let location = utils.getBidIdParameter('loc', bidRequestParams); + let placementId = getBidIdParameter('placementId', bidRequestParams); + let requestUrl = getBidIdParameter('requestUrl', bidRequestParams); + let referrer = getBidIdParameter('ref', bidRequestParams); + let location = getBidIdParameter('loc', bidRequestParams); // Append location & referrer if (location === '') { location = utils.getWindowLocation(); @@ -58,7 +59,7 @@ export const spec = { } // Styles params - let stylesParams = utils.getBidIdParameter('style', bidRequestParams); + let stylesParams = getBidIdParameter('style', bidRequestParams); let stylesParamsArray = {}; for (let currentValue in stylesParams) { if (stylesParams.hasOwnProperty(currentValue)) { @@ -74,7 +75,7 @@ export const spec = { } } // Custom params - let customParams = utils.getBidIdParameter('customParams', bidRequestParams); + let customParams = getBidIdParameter('customParams', bidRequestParams); let customParamsArray = {}; for (let customField in customParams) { if (customParams.hasOwnProperty(customField)) { diff --git a/modules/otmBidAdapter.js b/modules/otmBidAdapter.js index 6125cee6593..7d4049e3054 100644 --- a/modules/otmBidAdapter.js +++ b/modules/otmBidAdapter.js @@ -2,14 +2,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { logInfo, logError, - getBidIdParameter, _each, getValue, isFn, isPlainObject, isArray, isStr, - isNumber, + isNumber, getBidIdParameter, } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 608ba20aa5f..1c3f9b8da1a 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -3,10 +3,11 @@ import {getStorageManager} from '../src/storageManager.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {find, includes} from '../src/polyfill.js'; -import {convertCamelToUnderscore, deepAccess, isArray, isFn, isNumber, isPlainObject} from '../src/utils.js'; +import {deepAccess, isArray, isFn, isNumber, isPlainObject} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import {getANKeywordParam} from '../libraries/appnexusKeywords/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; const SOURCE = 'pbjs'; const storageManager = getStorageManager({bidderCode: 'pixfuture'}); diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index e49dfec2f1c..0fff93cdcd1 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1,6 +1,6 @@ import Adapter from '../../src/adapter.js'; import { - bind, + deepAccess, deepClone, flatten, generateUUID, @@ -15,7 +15,6 @@ import { logWarn, triggerPixel, uniques, - deepAccess, } from '../../src/utils.js'; import CONSTANTS from '../../src/constants.json'; import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js'; @@ -297,7 +296,7 @@ function doAllSyncs(bidders, s2sConfig) { // if PBS reports this bidder doesn't have an ID, then call the sync and recurse to the next sync entry if (thisSync.no_cookie) { - doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, bind.call(doAllSyncs, null, bidders, s2sConfig), s2sConfig); + doPreBidderSync(thisSync.usersync.type, thisSync.usersync.url, thisSync.bidder, doAllSyncs.bind(null, bidders, s2sConfig), s2sConfig); } else { // bidder already has an ID, so just recurse to the next sync entry doAllSyncs(bidders, s2sConfig); @@ -356,8 +355,7 @@ function doClientSideSyncs(bidders, gdprConsent, uspConsent, gppConsent) { if (clientAdapter && clientAdapter.registerSyncs) { config.runWithBidder( bidder, - bind.call( - clientAdapter.registerSyncs, + clientAdapter.registerSyncs.bind( clientAdapter, [], gdprConsent, diff --git a/modules/priceFloors.js b/modules/priceFloors.js index e62e615ea86..07f8fbed45d 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -4,7 +4,6 @@ import { deepClone, deepSetValue, generateUUID, - getGptSlotInfoForAdUnitCode, getParameterByName, isNumber, logError, @@ -29,6 +28,7 @@ import {auctionManager} from '../src/auctionManager.js'; import {IMP, PBS, registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {adjustCpm} from '../src/utils/cpm.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; /** diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index 7c9108f60b1..c13e6e1c330 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -2,7 +2,7 @@ import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {getANKeywordParam} from '../libraries/appnexusKeywords/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; const BIDDER_CODE = 'prisma'; const BIDDER_URL = 'https://prisma.nexx360.io/prebid'; diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index f53b4094ae8..0651b373f12 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,10 +1,11 @@ -import { _each, pick, logWarn, isStr, isArray, logError, getGptSlotInfoForAdUnitCode } from '../src/utils.js'; +import {_each, isArray, isStr, logError, logWarn, pick} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import {ajax} from '../src/ajax.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 4c5602f959a..16d909c2fea 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,10 +1,11 @@ -import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, convertTypes, uniques, isPlainObject, isInteger } from '../src/utils.js'; +import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, uniques, isPlainObject, isInteger } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; import CONSTANTS from '../src/constants.json'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index 19a3c236942..e97e5505768 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,9 +1,10 @@ -import { deepAccess, getGptSlotInfoForAdUnitCode, parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; +import { deepAccess, parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; import {getGlobal} from '../src/prebidGlobal.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 7297c931326..516254b358b 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,6 +1,7 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import {convertTypes, isArray} from '../src/utils.js'; +import {isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const DEFAULT_CURRENCY = 'USD'; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'battr', 'deals']; diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js index a7aceb107b9..801457aa552 100644 --- a/modules/rasBidAdapter.js +++ b/modules/rasBidAdapter.js @@ -1,7 +1,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { isEmpty, getAdUnitSizes, parseSizesInput, deepAccess } from '../src/utils.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'; diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index b2961b09eb5..1e702d812f0 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -1,4 +1,14 @@ -import { deepAccess, logWarn, getBidIdParameter, parseQueryStringParameters, triggerPixel, generateUUID, isArray, isNumber, parseSizesInput } from '../src/utils.js'; +import { + deepAccess, + logWarn, + parseQueryStringParameters, + triggerPixel, + generateUUID, + isArray, + isNumber, + parseSizesInput, + getBidIdParameter +} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js index 5bf7dd691e7..f1d5521f780 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -3,9 +3,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import {_map, deepAccess, getAdUnitSizes, isFn, parseGPTSingleSizeArrayToRtbSize, triggerPixel} from '../src/utils.js'; +import {_map, deepAccess, isFn, parseGPTSingleSizeArrayToRtbSize, triggerPixel} from '../src/utils.js'; import {parseDomain} from '../src/refererDetection.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'revcontent'; const NATIVE_PARAMS = { diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index d5c6469db12..78740f7f87d 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -1,4 +1,16 @@ -import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + isInteger, + 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'; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 8e083a43505..4cfd40fb682 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -7,7 +7,6 @@ import { find } from '../src/polyfill.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { Renderer } from '../src/Renderer.js'; import { - convertTypes, deepAccess, deepSetValue, formatQS, @@ -21,6 +20,7 @@ import { parseSizesInput, _each } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; diff --git a/modules/schain.js b/modules/schain.js index 2991bb5b3d5..726679b133f 100644 --- a/modules/schain.js +++ b/modules/schain.js @@ -1,16 +1,17 @@ -import { config } from '../src/config.js'; +import {config} from '../src/config.js'; import adapterManager from '../src/adapterManager.js'; import { - isNumber, - isStr, + _each, + deepAccess, + deepClone, + deepSetValue, isArray, + isInteger, + isNumber, isPlainObject, - hasOwn, + isStr, logError, - isInteger, - _each, - logWarn, - deepAccess, deepSetValue, deepClone + logWarn } from '../src/utils.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; @@ -63,7 +64,7 @@ export function isSchainObjectValid(schainObject, returnOnError) { } // ext: Object [optional] - if (hasOwn(schainObject, 'ext')) { + if (schainObject.hasOwnProperty('ext')) { if (!isPlainObject(schainObject.ext)) { appendFailMsg(`schain.config.ext` + shouldBeAnObject); } @@ -92,28 +93,28 @@ export function isSchainObjectValid(schainObject, returnOnError) { } // rid: String [Optional] - if (hasOwn(node, 'rid')) { + if (node.hasOwnProperty('rid')) { if (!isStr(node.rid)) { appendFailMsg(`schain.config.nodes[${index}].rid` + shouldBeAString); } } // name: String [Optional] - if (hasOwn(node, 'name')) { + if (node.hasOwnProperty('name')) { if (!isStr(node.name)) { appendFailMsg(`schain.config.nodes[${index}].name` + shouldBeAString); } } // domain: String [Optional] - if (hasOwn(node, 'domain')) { + if (node.hasOwnProperty('domain')) { if (!isStr(node.domain)) { appendFailMsg(`schain.config.nodes[${index}].domain` + shouldBeAString); } } // ext: Object [Optional] - if (hasOwn(node, 'ext')) { + if (node.hasOwnProperty('ext')) { if (!isPlainObject(node.ext)) { appendFailMsg(`schain.config.nodes[${index}].ext` + shouldBeAnObject); } diff --git a/modules/sharethroughAnalyticsAdapter.js b/modules/sharethroughAnalyticsAdapter.js index 6502c7e3a53..dc621e8da92 100644 --- a/modules/sharethroughAnalyticsAdapter.js +++ b/modules/sharethroughAnalyticsAdapter.js @@ -1,6 +1,6 @@ -import { tryAppendQueryString } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 96b6d281fdc..47fca317de2 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -1,4 +1,16 @@ -import { logWarn, logInfo, isArray, isFn, deepAccess, isEmpty, contains, timestamp, getBidIdParameter, triggerPixel, isInteger } from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + isInteger, + 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'; diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index a241cb71a5d..a1e7df49d18 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,10 +1,9 @@ import { deepAccess, - getBidIdParameter, getWindowTop, triggerPixel, logInfo, - logError + logError, getBidIdParameter } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index 447e314958f..c3f06556652 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, parseSizesInput, getBidIdParameter } from '../src/utils.js'; +import {getBidIdParameter, getValue, parseSizesInput} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 1b50e033074..b735953d099 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -1,22 +1,12 @@ -import { - chunk, - deepAccess, - deepSetValue, - fill, - getAdUnitSizes, - getDNT, - getMaxValueFromArray, - getMinValueFromArray, - isEmpty, - isNumber, - logError, - logInfo -} from '../src/utils.js'; +import {deepAccess, deepSetValue, getDNT, isEmpty, isNumber, logError, logInfo} from '../src/utils.js'; import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import CONSTANTS from '../src/constants.json'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {fill} from '../libraries/appnexusUtils/anUtils.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; @@ -466,7 +456,7 @@ function createAdPodImp(bidRequest, videoMediaType) { }); } else { // all maxdurations should be the same - const maxDuration = getMaxValueFromArray(durationRangeSec); + const maxDuration = Math.max(...durationRangeSec); imps.map((imp, index) => { const sequence = index + 1; imp.video.maxduration = maxDuration @@ -481,7 +471,7 @@ function createAdPodImp(bidRequest, videoMediaType) { function getAdPodNumberOfPlacements(videoMediaType) { const {adPodDurationSec, durationRangeSec, requireExactDuration} = videoMediaType - const minAllowedDuration = getMinValueFromArray(durationRangeSec) + const minAllowedDuration = Math.min(...durationRangeSec) const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration) return requireExactDuration diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index d91b62729bc..45cc45192ef 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -1,4 +1,17 @@ -import { logError, deepAccess, isArray, getBidIdParameter, getDNT, generateUUID, isEmpty, _each, logMessage, logWarn, isFn, isPlainObject } from '../src/utils.js'; +import { + logError, + deepAccess, + isArray, + getDNT, + generateUUID, + isEmpty, + _each, + logMessage, + logWarn, + isFn, + isPlainObject, + getBidIdParameter +} from '../src/utils.js'; import { Renderer } from '../src/Renderer.js'; diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index b40ff9a65c9..a2d1f385623 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -1,11 +1,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, getGptSlotInfoForAdUnitCode, isFn, isPlainObject } from '../src/utils.js'; +import { parseSizesInput, logError, generateUUID, isEmpty, deepAccess, logWarn, logMessage, isFn, isPlainObject } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; import { bidderSettings } from '../src/bidderSettings.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 0d077ad2ae3..79481b81936 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,13 +1,12 @@ import { _each, - getBidIdParameter, isArray, getUniqueIdentifierStr, deepSetValue, logError, deepAccess, isInteger, - logWarn + logWarn, getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 874207adcf8..017544cc596 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -1,4 +1,20 @@ -import { logError, deepAccess, isArray, getBidIdParameter, getDNT, deepSetValue, isEmpty, _each, logMessage, logWarn, isBoolean, isNumber, isPlainObject, isFn, setScriptAttributes } from '../src/utils.js'; +import { + logError, + deepAccess, + isArray, + getDNT, + deepSetValue, + isEmpty, + _each, + logMessage, + logWarn, + isBoolean, + isNumber, + isPlainObject, + isFn, + setScriptAttributes, + getBidIdParameter +} from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 6107d1a6e66..5e92af640b8 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, logError, deepAccess, getBidIdParameter, parseSizesInput, isArray } from '../src/utils.js'; +import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; diff --git a/modules/trafficgateBidAdapter.js b/modules/trafficgateBidAdapter.js index 25289a19801..fcd84306099 100644 --- a/modules/trafficgateBidAdapter.js +++ b/modules/trafficgateBidAdapter.js @@ -1,7 +1,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {deepAccess, mergeDeep, convertTypes} from '../src/utils.js'; +import {deepAccess, mergeDeep} from '../src/utils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'trafficgate'; const URL = 'https://[HOST].bc-plugin.com/prebidjs' diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index b6375243a5a..e976396c71c 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -1,6 +1,7 @@ -import { getBidIdParameter, parseSizesInput, tryAppendQueryString } from '../src/utils.js'; +import {getBidIdParameter, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BID_REQUEST_BASE_URL = 'https://in-appadvertising.com/api/bidRequest'; const USER_SYNC_URL = 'https://in-appadvertising.com/api/userSync.html'; diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 22936ba750a..bfbf1409c1b 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,8 +1,9 @@ -import { tryAppendQueryString, logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; +import { logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const GVLID = 28; const BIDDER_CODE = 'triplelift'; diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 7561cdb60de..4c2bdfe175f 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -4,7 +4,6 @@ import { getWindowSelf, getWindowTop, isGptPubadsDefined, - isSlotMatchingAdUnitCode, logInfo, logMessage, logWarn, @@ -12,6 +11,7 @@ import { } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'underdogmedia'; const UDM_ADAPTER_VERSION = '7.30V'; diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index fa1e2898a4a..05960378d23 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,6 +1,6 @@ -import {getAdUnitSizes} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const BIDDER_CODE = 'vdoai'; const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; diff --git a/modules/ventesBidAdapter.js b/modules/ventesBidAdapter.js index 73ae0a7e5f1..78c580c4116 100644 --- a/modules/ventesBidAdapter.js +++ b/modules/ventesBidAdapter.js @@ -1,8 +1,9 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {convertCamelToUnderscore, isArray, isNumber, isPlainObject, isStr, replaceAuctionPrice} from '../src/utils.js'; +import {isArray, isNumber, isPlainObject, isStr, replaceAuctionPrice} from '../src/utils.js'; import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://ad.ventesavenues.in/va/ad'; diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index ba21a9c82d2..b5323181c6c 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,9 +1,10 @@ -import {_each, chunk, deepAccess, isFn, parseSizesInput, parseUrl, uniques, isArray} from '../src/utils.js'; +import {_each, deepAccess, isFn, parseSizesInput, parseUrl, uniques, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; import {config} from '../src/config.js'; +import {chunk} from '../libraries/chunk/chunk.js'; const GVLID = 744; const DEFAULT_SUB_DOMAIN = 'prebid'; diff --git a/modules/videoreachBidAdapter.js b/modules/videoreachBidAdapter.js index 9fd5853c75e..8835398d7cc 100644 --- a/modules/videoreachBidAdapter.js +++ b/modules/videoreachBidAdapter.js @@ -1,4 +1,4 @@ -import { getValue, getBidIdParameter } from '../src/utils.js'; +import {getBidIdParameter, getValue} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'videoreach'; const ENDPOINT_URL = 'https://a.videoreach.com/hb/'; diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index c1bb626a39c..a45a1db9ece 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,9 +1,11 @@ -import { triggerPixel, parseSizesInput, deepAccess, logError, getGptSlotInfoForAdUnitCode } 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 { INSTREAM as VIDEO_INSTREAM } from '../src/video.js'; +import {deepAccess, logError, parseSizesInput, triggerPixel} 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 {INSTREAM as VIDEO_INSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; +import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; + const BIDDER_CODE = 'visx'; const GVLID = 154; const BASE_URL = 'https://t.visx.net'; diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 6ba502d2c8b..14abba95323 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -109,8 +109,7 @@ import { isBoolean, isPlainObject, logWarn, - mergeDeep, - tryAppendQueryString + mergeDeep } from '../src/utils.js'; import { submodule @@ -123,6 +122,7 @@ import { } from '../src/storageManager.js'; import adapterManager from '../src/adapterManager.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 5cc063d16b4..41efc432e11 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -1,6 +1,4 @@ import { - convertCamelToUnderscore, - convertTypes, deepAccess, getBidRequest, getParameterByName, @@ -16,7 +14,9 @@ import {BANNER} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusKeywords/anKeywords.js'; +import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'winr'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js index 94a66e9980e..6f527d905d6 100644 --- a/modules/xeBidAdapter.js +++ b/modules/xeBidAdapter.js @@ -1,7 +1,8 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { getAdUnitSizes, parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray } from '../src/utils.js'; +import {parseSizesInput, isFn, deepAccess, logError, isArray, getBidIdParameter} from '../src/utils.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const CUR = 'USD'; const BIDDER_CODE = 'xe'; diff --git a/src/adapterManager.js b/src/adapterManager.js index 93cbb8a071b..575d28b35fa 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -1,8 +1,6 @@ /** @module adaptermanger */ import { - _each, - bind, deepAccess, deepClone, flatten, @@ -31,12 +29,7 @@ import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; -import { - GDPR_GVLIDS, - gdprDataHandler, - uspDataHandler, - gppDataHandler, -} from './consentHandler.js'; +import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; import CONSTANTS from './constants.json'; import {useMetrics} from './utils/perfMetrics.js'; @@ -468,8 +461,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request try { config.runWithBidder( bidderRequest.bidderCode, - bind.call( - adapter.callBids, + adapter.callBids.bind( adapter, bidderRequest, addBidResponse, @@ -597,7 +589,7 @@ adapterManager.enableAnalytics = function (config) { config = [config]; } - _each(config, adapterConfig => { + config.forEach(adapterConfig => { const entry = _analyticsRegistry[adapterConfig.provider]; if (entry && entry.adapter) { if (dep.isAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(MODULE_TYPE_ANALYTICS, adapterConfig.provider, {[ACTIVITY_PARAM_ANL_CONFIG]: adapterConfig}))) { diff --git a/src/auction.js b/src/auction.js index c3712c0a4df..4bdd590f7ea 100644 --- a/src/auction.js +++ b/src/auction.js @@ -58,9 +58,6 @@ */ import { - _each, - adUnitsFilter, - bind, deepAccess, generateUUID, getValue, @@ -210,9 +207,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a bidsBackCallback(_adUnits, function () { try { if (_callback != null) { - const adUnitCodes = _adUnitCodes; const bids = _bidsReceived - .filter(bind.call(adUnitsFilter, this, adUnitCodes)) + .filter(bid => _adUnitCodes.includes(bid.adUnitCode)) .reduce(groupByPlacement, {}); _callback.apply(pbjsInstance, [bids, timedOut, _auctionId]); _callback = null; @@ -945,7 +941,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; custBidObj.size = custBidObj.getSize(); - _each(targeting, function (kvPair) { + (targeting || []).forEach(function (kvPair) { var key = kvPair.key; var value = kvPair.val; diff --git a/src/config.js b/src/config.js index 48909d677e9..d4dc07989af 100644 --- a/src/config.js +++ b/src/config.js @@ -12,11 +12,20 @@ * @property {(string|Object)} [video-outstream] */ -import { isValidPriceConfig } from './cpmBucketManager.js'; -import {find, includes, arrayFrom as from} from './polyfill.js'; +import {isValidPriceConfig} from './cpmBucketManager.js'; +import {arrayFrom as from, find, includes} from './polyfill.js'; import { - mergeDeep, deepClone, getParameterByName, isPlainObject, logMessage, logWarn, logError, - isArray, isStr, isBoolean, deepAccess, bind + deepAccess, + deepClone, + getParameterByName, + isArray, + isBoolean, + isPlainObject, + isStr, + logError, + logMessage, + logWarn, + mergeDeep } from './utils.js'; import CONSTANTS from './constants.json'; @@ -505,7 +514,7 @@ export function newConfig() { return function(cb) { return function(...args) { if (typeof cb === 'function') { - return runWithBidder(bidder, bind.call(cb, this, ...args)) + return runWithBidder(bidder, cb.bind(this, ...args)) } else { logWarn('config.callbackWithBidder callback is not a function'); } diff --git a/src/events.js b/src/events.js index bea5d4ee4d9..7a1e25e0e49 100644 --- a/src/events.js +++ b/src/events.js @@ -49,9 +49,7 @@ const _public = (function () { let idPath = idPaths[eventString]; let key = eventPayload[idPath]; let event = _handlers[eventString] || { que: [] }; - let eventKeys = utils._map(event, function (v, k) { - return k; - }); + var eventKeys = Object.keys(event); let callbacks = []; @@ -69,7 +67,7 @@ const _public = (function () { * each function in the `que` array as an argument to push to the * `callbacks` array * */ - if (key && utils.contains(eventKeys, key)) { + if (key && eventKeys.includes(key)) { push.apply(callbacks, event[key].que); } @@ -77,7 +75,7 @@ const _public = (function () { push.apply(callbacks, event.que); /** call each of the callbacks */ - utils._each(callbacks, function (fn) { + (callbacks || []).forEach(function (fn) { if (!fn) return; try { fn.apply(null, args); @@ -88,7 +86,7 @@ const _public = (function () { } function _checkAvailableEvent(event) { - return utils.contains(allEvents, event); + return allEvents.includes(event) } _public.on = function (eventString, handler, id) { @@ -126,14 +124,14 @@ const _public = (function () { } if (id) { - utils._each(event[id].que, function (_handler) { + (event[id].que || []).forEach(function (_handler) { let que = event[id].que; if (_handler === handler) { que.splice(que.indexOf(_handler), 1); } }); } else { - utils._each(event.que, function (_handler) { + (event.que || []).forEach(function (_handler) { let que = event.que; if (_handler === handler) { que.splice(que.indexOf(_handler), 1); diff --git a/src/native.js b/src/native.js index 1414c7a2ee7..c4709dd77e2 100644 --- a/src/native.js +++ b/src/native.js @@ -1,7 +1,6 @@ import { deepAccess, deepClone, - getKeyByValue, insertHtmlIntoIframe, isArray, isBoolean, @@ -422,12 +421,14 @@ function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {} return message; } +const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(CONSTANTS.NATIVE_KEYS).map(([k, v]) => [v, k])); + /** * Constructs a message object containing asset values for each of the * requested data keys. */ export function getAssetMessage(data, adObject) { - const keys = data.assets.map((k) => getKeyByValue(CONSTANTS.NATIVE_KEYS, k)); + const keys = data.assets.map((k) => NATIVE_KEYS_INVERTED[k]); return assetsMessage(data, adObject, keys); } diff --git a/src/prebid.js b/src/prebid.js index decc2a7cef6..6ad5120ce82 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -2,10 +2,7 @@ import {getGlobal} from './prebidGlobal.js'; import { - adUnitsFilter, - bind, callBurl, - contains, createInvisibleIframe, deepAccess, deepClone, @@ -91,7 +88,7 @@ function checkDefinedPlacement(id) { .reduce(flatten) .filter(uniques); - if (!contains(adUnitCodes, id)) { + if (!adUnitCodes.includes(id)) { logError('The "' + id + '" placement is not defined.'); return; } @@ -345,7 +342,7 @@ pbjsInstance.getConsentMetadata = function () { function getBids(type) { const responses = auctionManager[type]() - .filter(bind.call(adUnitsFilter, this, auctionManager.getAdUnitCodes())); + .filter(bid => auctionManager.getAdUnitCodes().includes(bid.adUnitCode)) // find the last auction id to get responses for most recent auction only const currentAuctionId = auctionManager.getLastAuctionId(); diff --git a/src/utils.js b/src/utils.js index c436b6385e7..256dfb15174 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ import {config} from './config.js'; import clone from 'just-clone'; -import {find, includes} from './polyfill.js'; +import {includes} from './polyfill.js'; import CONSTANTS from './constants.json'; import {GreedyPromise} from './utils/promise.js'; import {getGlobal} from './prebidGlobal.js'; @@ -8,7 +8,6 @@ import {getGlobal} from './prebidGlobal.js'; export { default as deepAccess } from 'dlv/index.js'; export { dset as deepSetValue } from 'dset'; -var tArr = 'Array'; var tStr = 'String'; var tFn = 'Function'; var tNumb = 'Number'; @@ -64,17 +63,6 @@ export function getPrebidInternal() { return prebidInternal; } -var uniqueRef = {}; -export let bind = function(a, b) { return b; }.bind(null, 1, uniqueRef)() === uniqueRef - ? Function.prototype.bind - : function(bind) { - var self = this; - var args = Array.prototype.slice.call(arguments, 1); - return function() { - return self.apply(bind, args.concat(Array.prototype.slice.call(arguments))); - }; - }; - /* utility method to get incremental integer starting from 1 */ var getIncrementalInteger = (function () { var count = 0; @@ -114,19 +102,7 @@ function _getRandomData() { } export function getBidIdParameter(key, paramsObj) { - if (paramsObj && paramsObj[key]) { - return paramsObj[key]; - } - - return ''; -} - -export function tryAppendQueryString(existingUrl, key, value) { - if (value) { - return existingUrl + key + '=' + encodeURIComponent(value) + '&'; - } - - return existingUrl; + return paramsObj?.[key] || ''; } // parse a query string object passed in bid params @@ -145,84 +121,30 @@ export function parseQueryStringParameters(queryObj) { export function transformAdServerTargetingObj(targeting) { // we expect to receive targeting for a single slot at a time if (targeting && Object.getOwnPropertyNames(targeting).length > 0) { - return getKeys(targeting) - .map(key => `${key}=${encodeURIComponent(getValue(targeting, key))}`).join('&'); + return Object.keys(targeting) + .map(key => `${key}=${encodeURIComponent(targeting[key])}`).join('&'); } else { return ''; } } -/** - * Read an adUnit object and return the sizes used in an [[728, 90]] format (even if they had [728, 90] defined) - * Preference is given to the `adUnit.mediaTypes.banner.sizes` object over the `adUnit.sizes` - * @param {object} adUnit one adUnit object from the normal list of adUnits - * @returns {Array.} array of arrays containing numeric sizes - */ -export function getAdUnitSizes(adUnit) { - if (!adUnit) { - return; - } - - let sizes = []; - if (adUnit.mediaTypes && adUnit.mediaTypes.banner && Array.isArray(adUnit.mediaTypes.banner.sizes)) { - let bannerSizes = adUnit.mediaTypes.banner.sizes; - if (Array.isArray(bannerSizes[0])) { - sizes = bannerSizes; - } else { - sizes.push(bannerSizes); - } - // TODO - remove this else block when we're ready to deprecate adUnit.sizes for bidders - } else if (Array.isArray(adUnit.sizes)) { - if (Array.isArray(adUnit.sizes[0])) { - sizes = adUnit.sizes; - } else { - sizes.push(adUnit.sizes); - } - } - return sizes; -} - /** * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` */ export function parseSizesInput(sizeObj) { - var parsedSizes = []; - - // if a string for now we can assume it is a single size, like "300x250" if (typeof sizeObj === 'string') { // multiple sizes will be comma-separated - var sizes = sizeObj.split(','); - - // regular expression to match strigns like 300x250 - // start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line - var sizeRegex = /^(\d)+x(\d)+$/i; - if (sizes) { - for (var curSizePos in sizes) { - if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { - parsedSizes.push(sizes[curSizePos]); - } - } - } + return sizeObj.split(',').filter(sz => sz.match(/^(\d)+x(\d)+$/i)) } else if (typeof sizeObj === 'object') { - var sizeArrayLength = sizeObj.length; - - // don't process empty array - if (sizeArrayLength > 0) { - // if we are a 2 item array of 2 numbers, we must be a SingleSize array - if (sizeArrayLength === 2 && typeof sizeObj[0] === 'number' && typeof sizeObj[1] === 'number') { - parsedSizes.push(parseGPTSingleSizeArray(sizeObj)); - } else { - // otherwise, we must be a MultiSize array - for (var i = 0; i < sizeArrayLength; i++) { - parsedSizes.push(parseGPTSingleSizeArray(sizeObj[i])); - } - } + if (sizeObj.length === 2 && typeof sizeObj[0] === 'number' && typeof sizeObj[1] === 'number') { + return [parseGPTSingleSizeArray(sizeObj)]; + } else { + return sizeObj.map(parseGPTSingleSizeArray) } } - - return parsedSizes; + return []; } // Parse a GPT style single size array, (i.e [300, 250]) @@ -345,6 +267,9 @@ export function createInvisibleIframe() { f.frameBorder = '0'; f.src = 'about:blank'; f.style.display = 'none'; + f.style.height = '0px'; + f.style.width = '0px'; + f.allowtransparency = 'true'; return f; } @@ -375,9 +300,7 @@ export function isStr(object) { return isA(object, tStr); } -export function isArray(object) { - return isA(object, tArr); -} +export const isArray = Array.isArray.bind(Array); export function isNumber(object) { return isA(object, tNumb); @@ -402,12 +325,7 @@ export function isEmpty(object) { if (isArray(object) || isStr(object)) { return !(object.length > 0); } - - for (var k in object) { - if (hasOwnProperty.call(object, k)) return false; - } - - return true; + return Object.keys(object).length <= 0; } /** @@ -426,38 +344,12 @@ export function isEmptyStr(str) { * @param {Function(value, key, object)} fn */ export function _each(object, fn) { - if (isEmpty(object)) return; - if (isFn(object.forEach)) return object.forEach(fn, this); - - var k = 0; - var l = object.length; - - if (l > 0) { - for (; k < l; k++) fn(object[k], k, object); - } else { - for (k in object) { - if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); - } - } + if (isFn(object?.forEach)) return object.forEach(fn, this); + Object.entries(object || {}).forEach(([k, v]) => fn.call(this, v, k)); } export function contains(a, obj) { - if (isEmpty(a)) { - return false; - } - - if (isFn(a.indexOf)) { - return a.indexOf(obj) !== -1; - } - - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } - } - - return false; + return isFn(a?.includes) && a.includes(obj); } /** @@ -468,24 +360,10 @@ export function contains(a, obj) { * @return {Array} */ export function _map(object, callback) { - if (isEmpty(object)) return []; - if (isFn(object.map)) return object.map(callback); - var output = []; - _each(object, function (value, key) { - output.push(callback(value, key, object)); - }); - - return output; + if (isFn(object?.map)) return object.map(callback); + return Object.entries(object || {}).map(([k, v]) => callback(v, k, object)) } -export function hasOwn(objectToCheck, propertyToCheckFor) { - if (objectToCheck.hasOwnProperty) { - return objectToCheck.hasOwnProperty(propertyToCheckFor); - } else { - return (typeof objectToCheck[propertyToCheckFor] !== 'undefined') && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); - } -}; - /* * Inserts an element(elm) as targets child, by default as first child * @param {HTMLElement} elm @@ -568,27 +446,14 @@ export function insertHtmlIntoIframe(htmlCode) { if (!htmlCode) { return; } - - let iframe = document.createElement('iframe'); - iframe.id = getUniqueIdentifierStr(); - iframe.width = 0; - iframe.height = 0; - iframe.hspace = '0'; - iframe.vspace = '0'; - iframe.marginWidth = '0'; - iframe.marginHeight = '0'; - iframe.style.display = 'none'; - iframe.style.height = '0px'; - iframe.style.width = '0px'; - iframe.scrolling = 'no'; - iframe.frameBorder = '0'; - iframe.allowtransparency = 'true'; - + const iframe = createInvisibleIframe(); internal.insertElement(iframe, document, 'body'); - iframe.contentWindow.document.open(); - iframe.contentWindow.document.write(htmlCode); - iframe.contentWindow.document.close(); + ((doc) => { + doc.open(); + doc.write(htmlCode); + doc.close(); + })(iframe.contentWindow.document); } /** @@ -654,19 +519,6 @@ export function createTrackPixelIframeHtml(url, encodeUri = true, sandbox = '') `; } -export function getValueString(param, val, defaultValue) { - if (val === undefined || val === null) { - return defaultValue; - } - if (isStr(val)) { - return val; - } - if (isNumber(val)) { - return val.toString(); - } - internal.logWarn('Unsuported type for param: ' + param + ' required type: String'); -} - export function uniques(value, index, arry) { return arry.indexOf(value) === index; } @@ -679,38 +531,14 @@ export function getBidRequest(id, bidderRequests) { if (!id) { return; } - let bidRequest; - bidderRequests.some(bidderRequest => { - let result = find(bidderRequest.bids, bid => ['bidId', 'adId', 'bid_id'].some(type => bid[type] === id)); - if (result) { - bidRequest = result; - } - return result; - }); - return bidRequest; -} - -export function getKeys(obj) { - return Object.keys(obj); + return bidderRequests.flatMap(br => br.bids) + .find(bid => ['bidId', 'adId', 'bid_id'].some(prop => bid[prop] === id)) } export function getValue(obj, key) { return obj[key]; } -/** - * Get the key of an object for a given value - */ -export function getKeyByValue(obj, value) { - for (let prop in obj) { - if (obj.hasOwnProperty(prop)) { - if (obj[prop] === value) { - return prop; - } - } - } -} - export function getBidderCodes(adUnits = pbjsInstance.adUnits) { // this could memoize adUnits return adUnits.map(unit => unit.bids.map(bid => bid.bidder) @@ -755,10 +583,6 @@ export function shuffle(array) { return array; } -export function adUnitsFilter(filter, bid) { - return includes(filter, bid && bid.adUnitCode); -} - export function deepClone(obj) { return clone(obj); } @@ -917,7 +741,7 @@ export function getDNT() { return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNotTrack === '1' || navigator.doNotTrack === 'yes'; } -const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode; +export const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === adUnitCode || slot.getSlotElementId() === adUnitCode; /** * Returns filter function to match adUnitCode in slot @@ -928,41 +752,6 @@ export function isAdUnitCodeMatchingSlot(slot) { return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode); } -/** - * Returns filter function to match adUnitCode in slot - * @param {string} adUnitCode AdUnit code - * @return {function} filter function - */ -export function isSlotMatchingAdUnitCode(adUnitCode) { - return (slot) => compareCodeAndSlot(slot, adUnitCode); -} - -/** - * @summary Uses the adUnit's code in order to find a matching gpt slot object on the page - */ -export function getGptSlotForAdUnitCode(adUnitCode) { - let matchingSlot; - if (isGptPubadsDefined()) { - // find the first matching gpt slot on the page - matchingSlot = find(window.googletag.pubads().getSlots(), isSlotMatchingAdUnitCode(adUnitCode)); - } - return matchingSlot; -}; - -/** - * @summary Uses the adUnit's code in order to find a matching gptSlot on the page - */ -export function getGptSlotInfoForAdUnitCode(adUnitCode) { - const matchingSlot = getGptSlotForAdUnitCode(adUnitCode); - if (matchingSlot) { - return { - gptSlot: matchingSlot.getAdUnitPath(), - divId: matchingSlot.getSlotElementId() - } - } - return {}; -}; - /** * Constructs warning message for when unsupported bidders are dropped from an adunit * @param {Object} adUnit ad unit from which the bidder is being dropped @@ -984,33 +773,14 @@ export function unsupportedBidderMessage(adUnit, bidder) { * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger * @param {*} value */ -export function isInteger(value) { - if (Number.isInteger) { - return Number.isInteger(value); - } else { - return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; - } -} - -/** - * Converts a string value in camel-case to underscore eg 'placementId' becomes 'placement_id' - * @param {string} value string value to convert - */ -export function convertCamelToUnderscore(value) { - return value.replace(/(?:^|\.?)([A-Z])/g, function (x, y) { return '_' + y.toLowerCase() }).replace(/^_/, ''); -} +export const isInteger = Number.isInteger.bind(Number); /** * Returns a new object with undefined properties removed from given object * @param obj the object to clean */ export function cleanObj(obj) { - return Object.keys(obj).reduce((newObj, key) => { - if (typeof obj[key] !== 'undefined') { - newObj[key] = obj[key]; - } - return newObj; - }, {}) + return Object.fromEntries(Object.entries(obj).filter(([_, v]) => typeof v !== 'undefined')) } /** @@ -1047,104 +817,10 @@ export function pick(obj, properties) { }, {}); } -/** - * Try to convert a value to a type. - * If it can't be done, the value will be returned. - * - * @param {string} typeToConvert The target type. e.g. "string", "number", etc. - * @param {*} value The value to be converted into typeToConvert. - */ -function tryConvertType(typeToConvert, value) { - if (typeToConvert === 'string') { - return value && value.toString(); - } else if (typeToConvert === 'number') { - return Number(value); - } else { - return value; - } -} - -export function convertTypes(types, params) { - Object.keys(types).forEach(key => { - if (params[key]) { - if (isFn(types[key])) { - params[key] = types[key](params[key]); - } else { - params[key] = tryConvertType(types[key], params[key]); - } - - // don't send invalid values - if (isNaN(params[key])) { - delete params.key; - } - } - }); - return params; -} - export function isArrayOfNums(val, size) { return (isArray(val)) && ((size) ? val.length === size : true) && (val.every(v => isInteger(v))); } -/** - * Creates an array of n length and fills each item with the given value - */ -export function fill(value, length) { - let newArray = []; - - for (let i = 0; i < length; i++) { - let valueToPush = isPlainObject(value) ? deepClone(value) : value; - newArray.push(valueToPush); - } - - return newArray; -} - -/** - * http://npm.im/chunk - * Returns an array with *size* chunks from given array - * - * Example: - * ['a', 'b', 'c', 'd', 'e'] chunked by 2 => - * [['a', 'b'], ['c', 'd'], ['e']] - */ -export function chunk(array, size) { - let newArray = []; - - for (let i = 0; i < Math.ceil(array.length / size); i++) { - let start = i * size; - let end = start + size; - newArray.push(array.slice(start, end)); - } - - return newArray; -} - -export function getMinValueFromArray(array) { - return Math.min(...array); -} - -export function getMaxValueFromArray(array) { - return Math.max(...array); -} - -/** - * This function will create compare function to sort on object property - * @param {string} property - * @returns {function} compare function to be used in sorting - */ -export function compareOn(property) { - return function compare(a, b) { - if (a[property] < b[property]) { - return 1; - } - if (a[property] > b[property]) { - return -1; - } - return 0; - } -} - export function parseQS(query) { return !query ? {} : query .replace(/^\?/, '') @@ -1307,15 +983,6 @@ export function cyrb53Hash(str, seed = 0) { return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(); } -/** - * returns a window object, which holds the provided document or null - * @param {Document} doc - * @returns {Window} - */ -export function getWindowFromDocument(doc) { - return (doc) ? doc.defaultView : null; -} - /** * returns the result of `JSON.parse(data)`, or undefined if that throws an error. * @param data @@ -1354,40 +1021,9 @@ export function memoize(fn, key = function (arg) { return arg; }) { * @param {object} attributes */ export function setScriptAttributes(script, attributes) { - for (let key in attributes) { - if (attributes.hasOwnProperty(key)) { - script.setAttribute(key, attributes[key]); - } - } + Object.entries(attributes).forEach(([k, v]) => script.setAttribute(k, v)) } -/** - * Encode a string for inclusion in HTML. - * See https://pragmaticwebsecurity.com/articles/spasecurity/json-stringify-xss.html and - * https://codeql.github.com/codeql-query-help/javascript/js-bad-code-sanitization/ - * @return {string} - */ -export const escapeUnsafeChars = (() => { - const escapes = { - '<': '\\u003C', - '>': '\\u003E', - '/': '\\u002F', - '\\': '\\\\', - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t', - '\0': '\\0', - '\u2028': '\\u2028', - '\u2029': '\\u2029' - }; - - return function(str) { - return str.replace(/[<>\b\f\n\r\t\0\u2028\u2029\\]/g, x => escapes[x]) - } -})(); - /** * Perform a binary search for `el` on an ordered array `arr`. * diff --git a/test/spec/appnexusKeywords_spec.js b/test/spec/appnexusKeywords_spec.js index 9bf567a27c5..68faeff0b82 100644 --- a/test/spec/appnexusKeywords_spec.js +++ b/test/spec/appnexusKeywords_spec.js @@ -1,4 +1,4 @@ -import {transformBidderParamKeywords} from '../../libraries/appnexusKeywords/anKeywords.js'; +import {transformBidderParamKeywords} from '../../libraries/appnexusUtils/anKeywords.js'; import {expect} from 'chai/index.js'; import * as utils from '../../src/utils.js'; diff --git a/test/spec/libraries/sizeUtils_spec.js b/test/spec/libraries/sizeUtils_spec.js new file mode 100644 index 00000000000..1c954c6accf --- /dev/null +++ b/test/spec/libraries/sizeUtils_spec.js @@ -0,0 +1,30 @@ +import {getAdUnitSizes} from '../../../libraries/sizeUtils/sizeUtils.js'; +import {expect} from 'chai/index.js'; + +describe('getAdUnitSizes', function () { + it('returns an empty response when adUnits is undefined', function () { + let sizes = getAdUnitSizes(); + expect(sizes).to.be.undefined; + }); + + it('returns an empty array when invalid data is present in adUnit object', function () { + let sizes = getAdUnitSizes({sizes: 300}); + expect(sizes).to.deep.equal([]); + }); + + it('retuns an array of arrays when reading from adUnit.sizes', function () { + let sizes = getAdUnitSizes({sizes: [300, 250]}); + expect(sizes).to.deep.equal([[300, 250]]); + + sizes = getAdUnitSizes({sizes: [[300, 250], [300, 600]]}); + expect(sizes).to.deep.equal([[300, 250], [300, 600]]); + }); + + it('returns an array of arrays when reading from adUnit.mediaTypes.banner.sizes', function () { + let sizes = getAdUnitSizes({mediaTypes: {banner: {sizes: [300, 250]}}}); + expect(sizes).to.deep.equal([[300, 250]]); + + sizes = getAdUnitSizes({mediaTypes: {banner: {sizes: [[300, 250], [300, 600]]}}}); + expect(sizes).to.deep.equal([[300, 250], [300, 600]]); + }); +}); diff --git a/test/spec/libraries/urlUtils_spec.js b/test/spec/libraries/urlUtils_spec.js new file mode 100644 index 00000000000..9dd66b05407 --- /dev/null +++ b/test/spec/libraries/urlUtils_spec.js @@ -0,0 +1,24 @@ +import {tryAppendQueryString} from '../../../libraries/urlUtils/urlUtils.js'; +import assert from 'assert'; + +describe('tryAppendQueryString', function () { + it('should append query string to existing url', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = 'c'; + + var output = tryAppendQueryString(url, key, value); + + var expectedResult = url + key + '=' + encodeURIComponent(value) + '&'; + assert.equal(output, expectedResult); + }); + + it('should return existing url, if the value is empty', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = ''; + + var output = tryAppendQueryString(url, key, value); + assert.equal(output, url); + }); +}); diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js index 0bcd7753e55..b4bff8e82f0 100644 --- a/test/spec/modules/fledgeForGpt_spec.js +++ b/test/spec/modules/fledgeForGpt_spec.js @@ -5,6 +5,7 @@ import * as fledge from 'modules/fledgeForGpt.js'; import {config} from '../../../src/config.js'; import adapterManager from '../../../src/adapterManager.js'; import * as utils from '../../../src/utils.js'; +import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; @@ -40,7 +41,7 @@ describe('fledgeForGpt module', () => { setConfig: sinon.stub(), getAdUnitPath: () => 'mock/gpt/au' }; - sandbox.stub(utils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); + sandbox.stub(gptUtils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); }); it('should call next()', function () { @@ -55,8 +56,8 @@ describe('fledgeForGpt module', () => { fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au1', cf1); fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au2', cf2); events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); - sinon.assert.calledWith(utils.getGptSlotForAdUnitCode, 'au1'); - sinon.assert.calledWith(utils.getGptSlotForAdUnitCode, 'au2'); + sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au1'); + sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au2'); sinon.assert.calledWith(mockGptSlot.setConfig, { componentAuction: [{ configKey: 'b1', diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 1dce87e4b8e..e0f4497a8c8 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,13 +1,9 @@ -import pubxaiAnalyticsAdapter from 'modules/pubxaiAnalyticsAdapter.js'; -import { getDeviceType, getBrowser, getOS } from 'modules/pubxaiAnalyticsAdapter.js'; -import { - expect -} from 'chai'; +import pubxaiAnalyticsAdapter, {getBrowser, getDeviceType, getOS} from 'modules/pubxaiAnalyticsAdapter.js'; +import {expect} from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; -import { - server -} from 'test/mocks/xhr.js'; +import {server} from 'test/mocks/xhr.js'; +import {getGptSlotInfoForAdUnitCode} from '../../../libraries/gptUtils/gptUtils.js'; let events = require('src/events'); let constants = require('src/constants.json'); @@ -527,7 +523,7 @@ describe('pubxai analytics adapter', function() { 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': utils.getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, + 'gptSlotCode': getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'sizes': '300x250', 'renderStatus': 2, @@ -596,7 +592,7 @@ describe('pubxai analytics adapter', function() { let expectedAfterBidWon = { 'winningBid': { 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': utils.getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, + 'gptSlotCode': getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || null, 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', 'bidderCode': 'appnexus', 'bidId': '248f9a4489835e', diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index b179f870e0d..164aa06d9b7 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -1,9 +1,9 @@ -import { expect } from 'chai' -import { spec, _getPlatform } from 'modules/sonobiBidAdapter.js' -import { newBidder } from 'src/adapters/bidderFactory.js' -import { userSync } from '../../../src/userSync.js'; -import { config } from 'src/config.js'; -import * as utils from '../../../src/utils.js'; +import {expect} from 'chai'; +import {_getPlatform, spec} from 'modules/sonobiBidAdapter.js'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {userSync} from '../../../src/userSync.js'; +import {config} from 'src/config.js'; +import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; describe('SonobiBidAdapter', function () { const adapter = newBidder(spec) @@ -248,13 +248,13 @@ describe('SonobiBidAdapter', function () { let sandbox; beforeEach(function () { sinon.stub(userSync, 'canBidderRegisterSync'); - sinon.stub(utils, 'getGptSlotInfoForAdUnitCode') + sinon.stub(gptUtils, 'getGptSlotInfoForAdUnitCode') .onFirstCall().returns({ gptSlot: '/123123/gpt_publisher/adunit-code-3', divId: 'adunit-code-3-div-id' }); sandbox = sinon.createSandbox(); }); afterEach(function () { userSync.canBidderRegisterSync.restore(); - utils.getGptSlotInfoForAdUnitCode.restore(); + gptUtils.getGptSlotInfoForAdUnitCode.restore(); sandbox.restore(); }); let bidRequest = [{ diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 40126f7f20c..098582c0af6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -4,6 +4,7 @@ import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; import {binarySearch, deepEqual, memoize, waitForElementToLoad} from 'src/utils.js'; +import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js'; var assert = require('assert'); @@ -40,28 +41,6 @@ describe('Utils', function () { }); }); - describe('tryAppendQueryString', function () { - it('should append query string to existing url', function () { - var url = 'www.a.com?'; - var key = 'b'; - var value = 'c'; - - var output = utils.tryAppendQueryString(url, key, value); - - var expectedResult = url + key + '=' + encodeURIComponent(value) + '&'; - assert.equal(output, expectedResult); - }); - - it('should return existing url, if the value is empty', function () { - var url = 'www.a.com?'; - var key = 'b'; - var value = ''; - - var output = utils.tryAppendQueryString(url, key, value); - assert.equal(output, url); - }); - }); - describe('parseQueryStringParameters', function () { it('should append query string to existing using the input obj', function () { var obj = { @@ -700,43 +679,15 @@ describe('Utils', function () { describe('convertCamelToUnderscore', function () { it('returns converted string value using underscore syntax instead of camelCase', function () { let var1 = 'placementIdTest'; - let test1 = utils.convertCamelToUnderscore(var1); + let test1 = convertCamelToUnderscore(var1); expect(test1).to.equal('placement_id_test'); let var2 = 'my_test_value'; - let test2 = utils.convertCamelToUnderscore(var2); + let test2 = convertCamelToUnderscore(var2); expect(test2).to.equal(var2); }); }); - describe('getAdUnitSizes', function () { - it('returns an empty response when adUnits is undefined', function () { - let sizes = utils.getAdUnitSizes(); - expect(sizes).to.be.undefined; - }); - - it('returns an empty array when invalid data is present in adUnit object', function () { - let sizes = utils.getAdUnitSizes({ sizes: 300 }); - expect(sizes).to.deep.equal([]); - }); - - it('retuns an array of arrays when reading from adUnit.sizes', function () { - let sizes = utils.getAdUnitSizes({ sizes: [300, 250] }); - expect(sizes).to.deep.equal([[300, 250]]); - - sizes = utils.getAdUnitSizes({ sizes: [[300, 250], [300, 600]] }); - expect(sizes).to.deep.equal([[300, 250], [300, 600]]); - }); - - it('returns an array of arrays when reading from adUnit.mediaTypes.banner.sizes', function () { - let sizes = utils.getAdUnitSizes({ mediaTypes: { banner: { sizes: [300, 250] } } }); - expect(sizes).to.deep.equal([[300, 250]]); - - sizes = utils.getAdUnitSizes({ mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } } }); - expect(sizes).to.deep.equal([[300, 250], [300, 600]]); - }); - }); - describe('URL helpers', function () { describe('parseUrl()', function () { let parsed; From 32c1bf651254ab6685a44aee9d9ff3ddf7c9d648 Mon Sep 17 00:00:00 2001 From: Hendrik Iseke <53309111+hendrikiseke1979@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:07:25 +0200 Subject: [PATCH 043/760] [orbidderBidAdapter] rename `profile` parameter to `keyValues` (#10498) Co-authored-by: Hendrik Iseke --- modules/orbidderBidAdapter.js | 8 ++--- test/spec/modules/orbidderBidAdapter_spec.js | 38 ++++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index f135ebb2bd1..53fff39047f 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -1,11 +1,11 @@ import { isFn, isPlainObject } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import { getGlobal } from '../src/prebidGlobal.js'; -const storageManager = getStorageManager({bidderCode: 'orbidder'}); +const storageManager = getStorageManager({ bidderCode: 'orbidder' }); /** * Determines whether or not the given bid response is valid. @@ -69,7 +69,7 @@ export const spec = { return !!(bid.sizes && bid.bidId && bid.params && (bid.params.accountId && (typeof bid.params.accountId === 'string')) && (bid.params.placementId && (typeof bid.params.placementId === 'string')) && - ((typeof bid.params.profile === 'undefined') || (typeof bid.params.profile === 'object'))); + ((typeof bid.params.keyValues === 'undefined') || (typeof bid.params.keyValues === 'object'))); }, /** diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index acb779b436d..5af5a4d710f 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -1,6 +1,6 @@ -import {expect} from 'chai'; -import {spec} from 'modules/orbidderBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; +import { expect } from 'chai'; +import { spec } from 'modules/orbidderBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; import * as _ from 'lodash'; import { BANNER, NATIVE } from '../../../src/mediaTypes.js'; @@ -59,7 +59,7 @@ describe('orbidderBidAdapter', () => { } }; - const deepClone = function (val) { + const deepClone = function(val) { return JSON.parse(JSON.stringify(val)); }; @@ -91,15 +91,15 @@ describe('orbidderBidAdapter', () => { expect(spec.isBidRequestValid(defaultBidRequestNative)).to.equal(true); }); - it('banner: accepts optional profile object', () => { + it('banner: accepts optional keyValues object', () => { const bidRequest = deepClone(defaultBidRequestBanner); - bidRequest.params.profile = {'key': 'value'}; + bidRequest.params.keyValues = { 'key': 'value' }; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); - it('native: accepts optional profile object', () => { + it('native: accepts optional keyValues object', () => { const bidRequest = deepClone(defaultBidRequestNative); - bidRequest.params.profile = {'key': 'value'}; + bidRequest.params.keyValues = { 'key': 'value' }; expect(spec.isBidRequestValid(bidRequest)).to.equal(true); }); @@ -115,15 +115,15 @@ describe('orbidderBidAdapter', () => { expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('banner: doesn\'t accept malformed profile', () => { + it('banner: doesn\'t accept malformed keyValues', () => { const bidRequest = deepClone(defaultBidRequestBanner); - bidRequest.params.profile = 'another not usable string'; + bidRequest.params.keyValues = 'another not usable string'; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); - it('native: doesn\'t accept malformed profile', () => { + it('native: doesn\'t accept malformed keyValues', () => { const bidRequest = deepClone(defaultBidRequestNative); - bidRequest.params.profile = 'another not usable string'; + bidRequest.params.keyValues = 'another not usable string'; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); }); @@ -347,7 +347,7 @@ describe('orbidderBidAdapter', () => { } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(expectedResponse.length); expect(_.isEqual(expectedResponse, serverResponse)).to.be.true; }); @@ -387,7 +387,7 @@ describe('orbidderBidAdapter', () => { } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(expectedResponse.length); Object.keys(expectedResponse[0]).forEach((key) => { @@ -454,7 +454,7 @@ describe('orbidderBidAdapter', () => { } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(expectedResponse.length); expect(_.isEqual(expectedResponse, serverResponse)).to.be.true; @@ -474,7 +474,7 @@ describe('orbidderBidAdapter', () => { 'netRevenue': true, } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(0); }); @@ -492,7 +492,7 @@ describe('orbidderBidAdapter', () => { 'creativeId': '29681110', } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(0); }); @@ -518,13 +518,13 @@ describe('orbidderBidAdapter', () => { } } ]; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(0); }); it('handles nobid responses', () => { const serverResponse = []; - const result = spec.interpretResponse({body: serverResponse}); + const result = spec.interpretResponse({ body: serverResponse }); expect(result.length).to.equal(0); }); }); From 79437d446d040892a6513e62ea0ca55e603792f4 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 22 Sep 2023 13:07:26 +0000 Subject: [PATCH 044/760] Prebid 8.16.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 43935bca1ab..edc8205a0a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.16.0-pre", + "version": "8.16.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4b53b8e2c29..ab48bf00eb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.16.0-pre", + "version": "8.16.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7c7c04025112e8036b3dc771f612cd7360affa18 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 22 Sep 2023 13:07:26 +0000 Subject: [PATCH 045/760] Increment version to 8.17.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index edc8205a0a4..e2f0f242d86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.16.0", + "version": "8.17.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ab48bf00eb9..0bffde226f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.16.0", + "version": "8.17.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ea179015b044270f5cdb11a4b94187908eeeb52d Mon Sep 17 00:00:00 2001 From: fkoch-sc Date: Mon, 25 Sep 2023 17:13:51 +0200 Subject: [PATCH 046/760] Update smartxBidAdapter.md (#10522) Updated maintainer email address --- modules/smartxBidAdapter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md index 853f06d6baf..50f78660458 100644 --- a/modules/smartxBidAdapter.md +++ b/modules/smartxBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: smartclip Bidder Adapter Module Type: Bidder Adapter -Maintainer: adtech@smartclip.tv +Maintainer: bidding@smartclip.tv ``` # Description @@ -170,4 +170,4 @@ This adapter requires setup and approval from the smartclip team. } }], }]; -``` \ No newline at end of file +``` From d5744050ad93d90b47c2bceb1366deeab38b3f70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Sep 2023 08:22:01 -0700 Subject: [PATCH 047/760] Bump tibdex/github-app-token from 2.0.0 to 2.1.0 (#10526) Bumps [tibdex/github-app-token](https://github.com/tibdex/github-app-token) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/tibdex/github-app-token/releases) - [Commits](https://github.com/tibdex/github-app-token/compare/0914d50df753bbc42180d982a6550f195390069f...3beb63f4bd073e61482598c45c71c1019b59b73a) --- updated-dependencies: - dependency-name: tibdex/github-app-token dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/issue_tracker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue_tracker.yml b/.github/workflows/issue_tracker.yml index a55e5f05cb8..b5c59c85160 100644 --- a/.github/workflows/issue_tracker.yml +++ b/.github/workflows/issue_tracker.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@0914d50df753bbc42180d982a6550f195390069f + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a with: app_id: ${{ secrets.ISSUE_APP_ID }} private_key: ${{ secrets.ISSUE_APP_PEM }} From ac37f9b46acd785cc04bd9c9c40f860ebff68994 Mon Sep 17 00:00:00 2001 From: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:04:44 +0300 Subject: [PATCH 048/760] Mobfox Bid Adapter : add gpp support (#10532) * initial * use util for win top * use util for win top * updates for prebid v5 * update import * gpp support --------- Co-authored-by: Aiholkin --- modules/mobfoxpbBidAdapter.js | 9 ++++++ test/spec/modules/mobfoxpbBidAdapter_spec.js | 33 +++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index 9ff50e2531f..35e9b03c031 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -71,6 +71,15 @@ export const spec = { if (bidderRequest.gdprConsent) { request.gdpr = bidderRequest.gdprConsent; } + + // Add GPP consent + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } } const len = validBidRequests.length; diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index 766f8d1a848..a4e58afbd1b 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -20,7 +20,8 @@ describe('MobfoxHBBidAdapter', function () { const bidderRequest = { refererInfo: { referer: 'test.com' - } + }, + ortb2: {} }; describe('isBidRequestValid', function () { @@ -143,6 +144,36 @@ describe('MobfoxHBBidAdapter', function () { expect(data.placements).to.be.an('array').that.is.empty; }); }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { From 0c7b14d6b334d6ae0fd9422f0b3cc82d654ceca2 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Tue, 26 Sep 2023 05:16:41 -0700 Subject: [PATCH 049/760] Fix bug Not Passing GPP Consent Object as a string (#10530) --- modules/gumgumBidAdapter.js | 12 ++++-------- test/spec/modules/gumgumBidAdapter_spec.js | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index d050af4ac8f..811af29c57b 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -383,15 +383,11 @@ function buildRequests(validBidRequests, bidderRequest) { data.uspConsent = uspConsent; } if (gppConsent) { - data.gppConsent = { - gppString: bidderRequest.gppConsent.gppString, - gpp_sid: bidderRequest.gppConsent.applicableSections - } + data.gppString = bidderRequest.gppConsent.gppString ? bidderRequest.gppConsent.gppString : '' + data.gppSid = Array.isArray(bidderRequest.gppConsent.applicableSections) ? bidderRequest.gppConsent.applicableSections.join(',') : '' } else if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) { - data.gppConsent = { - gppString: bidderRequest.ortb2.regs.gpp, - gpp_sid: bidderRequest.ortb2.regs.gpp_sid - }; + data.gppString = bidderRequest.ortb2.regs.gpp + data.gppSid = Array.isArray(bidderRequest.ortb2.regs.gpp_sid) ? bidderRequest.ortb2.regs.gpp_sid.join(',') : '' } if (coppa) { data.coppa = coppa; diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 0e64ec67b27..2b12547ca8f 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -102,6 +102,8 @@ describe('gumgumAdapter', function () { let sizesArray = [[300, 250], [300, 600]]; let bidRequests = [ { + gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + gppSid: [7], bidder: 'gumgum', params: { inSlot: 9 @@ -503,10 +505,9 @@ describe('gumgumAdapter', function () { const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [7] } const fakeBidRequest = { gppConsent: gppConsent }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; - expect(bidRequest.data.gppConsent).to.exist; - expect(bidRequest.data.gppConsent.gppString).to.equal(gppConsent.gppString); - expect(bidRequest.data.gppConsent.gpp_sid).to.equal(gppConsent.applicableSections); - expect(bidRequest.data.gppConsent.gppString).to.eq('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(bidRequest.data.gppString).to.equal(gppConsent.gppString); + expect(bidRequest.data.gppSid).to.equal(gppConsent.applicableSections.join(',')); + expect(bidRequest.data.gppString).to.eq('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); }); it('should handle ortb2 parameters', function () { const ortb2 = { @@ -517,15 +518,14 @@ describe('gumgumAdapter', function () { } const fakeBidRequest = { gppConsent: ortb2 }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; - expect(bidRequest.data.gppConsent.gppString).to.eq(fakeBidRequest[0]) + expect(bidRequest.data.gpp).to.eq(fakeBidRequest[0]) }); it('should handle gppConsent is present but values are undefined case', function () { const gppConsent = { gppString: undefined, applicableSections: undefined } const fakeBidRequest = { gppConsent: gppConsent }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; - expect(bidRequest.data.gppConsent).to.exist; - expect(bidRequest.data.gppConsent.gppString).to.equal(undefined); - expect(bidRequest.data.gppConsent.gpp_sid).to.equal(undefined); + expect(bidRequest.data.gppString).to.equal(''); + expect(bidRequest.data.gppSid).to.equal(''); }); it('should handle ortb2 undefined parameters', function () { const ortb2 = { @@ -536,8 +536,8 @@ describe('gumgumAdapter', function () { } const fakeBidRequest = { gppConsent: ortb2 }; const bidRequest = spec.buildRequests(bidRequests, fakeBidRequest)[0]; - expect(bidRequest.data.gppConsent.gppString).to.eq(undefined) - expect(bidRequest.data.gppConsent.gpp_sid).to.eq(undefined) + expect(bidRequest.data.gppString).to.eq('') + expect(bidRequest.data.gppSid).to.eq('') }); it('should not set coppa parameter if coppa config is set to false', function () { config.setConfig({ From 93bf6ef0d68e3b3ebc437cee1a5d8de1c7de0e80 Mon Sep 17 00:00:00 2001 From: Myhedin Zika <91465570+ZikaMyhedin@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:19:30 +0200 Subject: [PATCH 050/760] Integr8 Bid Adapter : updated endpoint URL (#10535) * Integr8 Bid Adapter changes * Change property/placement ids to correct ones --- modules/integr8BidAdapter.js | 16 ++++++++++++---- modules/integr8BidAdapter.md | 12 +++++++----- test/spec/modules/integr8BidAdapter_spec.js | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index a85e9b0a55c..cc19daf31a3 100644 --- a/modules/integr8BidAdapter.js +++ b/modules/integr8BidAdapter.js @@ -4,12 +4,12 @@ import { getStorageManager } from '../src/storageManager.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'integr8'; -const ENDPOINT_URL = 'https://integr8.central.gjirafa.tech/bid'; +const DEFAULT_ENDPOINT_URL = 'https://central.sea.integr8.digital/bid'; const DIMENSION_SEPARATOR = 'x'; const SIZE_SEPARATOR = ';'; -const BISKO_ID = 'biskoId'; +const BISKO_ID = 'integr8Id'; const STORAGE_ID = 'bisko-sid'; -const SEGMENTS = 'biskoSegments'; +const SEGMENTS = 'integr8Segments'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { @@ -31,6 +31,7 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + let deliveryUrl = ''; const storageId = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(STORAGE_ID) || '' : ''; const biskoId = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(BISKO_ID) || '' : ''; const segments = storage.localStorageIsEnabled() ? JSON.parse(storage.getDataFromLocalStorage(SEGMENTS)) || [] : []; @@ -55,6 +56,9 @@ export const spec = { if (!pageViewGuid) { pageViewGuid = bidRequest.params.pageViewGuid || ''; } if (!contents.length && bidRequest.params.contents && bidRequest.params.contents.length) { contents = bidRequest.params.contents; } if (!Object.keys(data).length && bidRequest.params.data && Object.keys(bidRequest.params.data).length) { data = bidRequest.params.data; } + if (!deliveryUrl && bidRequest.params && typeof bidRequest.params.deliveryUrl === 'string') { + deliveryUrl = bidRequest.params.deliveryUrl; + } return { sizes: generateSizeParam(bidRequest.sizes), @@ -67,6 +71,10 @@ export const spec = { }; }); + if (!deliveryUrl) { + deliveryUrl = DEFAULT_ENDPOINT_URL; + } + let body = { propertyId: propertyId, pageViewGuid: pageViewGuid, @@ -82,7 +90,7 @@ export const spec = { return [{ method: 'POST', - url: ENDPOINT_URL, + url: deliveryUrl, data: body }]; }, diff --git a/modules/integr8BidAdapter.md b/modules/integr8BidAdapter.md index eadab7acdb3..da52a2164c6 100644 --- a/modules/integr8BidAdapter.md +++ b/modules/integr8BidAdapter.md @@ -3,7 +3,7 @@ Module Name: Integr8 Bidder Adapter Module Type: Bidder Adapter -Maintainer: arditb@gjirafa.com +Maintainer: myhedin@gjirafa.com # Description Integr8 Bidder Adapter for Prebid.js. @@ -23,8 +23,9 @@ var adUnits = [ bids: [{ bidder: 'integr8', params: { - propertyId: '105109', //Required - placementId: '846835', //Required + propertyId: '105135', //Required + placementId: '846837', //Required, + deliveryUrl: 'https://central.sea.integr8.digital/bid', //Optional data: { //Optional catalogs: [{ catalogId: "699229", @@ -48,8 +49,9 @@ var adUnits = [ bids: [{ bidder: 'integr8', params: { - propertyId: '105109', //Required - placementId: '846830', //Required + propertyId: '105135', //Required + placementId: '846835', //Required, + deliveryUrl: 'https://central.sea.integr8.digital/bid', //Optional data: { //Optional catalogs: [{ catalogId: "699229", diff --git a/test/spec/modules/integr8BidAdapter_spec.js b/test/spec/modules/integr8BidAdapter_spec.js index 8c5a4b47903..01bb706df25 100644 --- a/test/spec/modules/integr8BidAdapter_spec.js +++ b/test/spec/modules/integr8BidAdapter_spec.js @@ -72,7 +72,7 @@ describe('integr8AdapterTest', () => { }); it('bidRequest url', () => { - const endpointUrl = 'https://integr8.central.gjirafa.tech/bid'; + const endpointUrl = 'https://central.sea.integr8.digital/bid'; const requests = spec.buildRequests(bidRequests); requests.forEach(function (requestItem) { expect(requestItem.url).to.match(new RegExp(`${endpointUrl}`)); @@ -113,7 +113,7 @@ describe('integr8AdapterTest', () => { describe('interpretResponse', () => { const bidRequest = { 'method': 'POST', - 'url': 'https://integr8.central.gjirafa.tech/bid', + 'url': 'https://central.sea.integr8.digital/bid', 'data': { 'sizes': '728x90', 'adUnitId': 'hb-leaderboard', From 2995c27b3fc64228390f8cc56d80641825c96ba9 Mon Sep 17 00:00:00 2001 From: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:34:07 -0500 Subject: [PATCH 051/760] Updated site.page source due to potential fault (#10508) Co-authored-by: Ubuntu --- modules/vrtcalBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index 21870e3218a..de6a9fc1ffe 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -67,7 +67,7 @@ export const spec = { name: 'VRTCAL_FILLED', cat: deepAccess(bid, 'ortb2.site.cat', []), domain: decodeURIComponent(window.location.href).replace('https://', '').replace('http://', '').split('/')[0], - page: bid.refererInfo.page + page: window.location.href }, device: { language: navigator.language, From 069d86bf825b7e63bfb3555d29fb95bbb92052c6 Mon Sep 17 00:00:00 2001 From: mickannese Date: Tue, 26 Sep 2023 12:39:23 -0700 Subject: [PATCH 052/760] Qortex RTD Provider: initial release (#10480) * branch from master * adloader * resolves testing issue --------- Co-authored-by: Mick --- modules/qortexRtdProvider.js | 165 ++++++++++ modules/qortexRtdProvider.md | 69 ++++ src/adloader.js | 1 + test/spec/modules/qortexRtdProvider_spec.js | 333 ++++++++++++++++++++ 4 files changed, 568 insertions(+) create mode 100644 modules/qortexRtdProvider.js create mode 100644 modules/qortexRtdProvider.md create mode 100644 test/spec/modules/qortexRtdProvider_spec.js diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js new file mode 100644 index 00000000000..a071436007a --- /dev/null +++ b/modules/qortexRtdProvider.js @@ -0,0 +1,165 @@ +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +let requestUrl; +let bidderArray; +let impressionIds; +let currentSiteContext; + +/** + * Init if module configuration is valid + * @param {Object} config Module configuration + * @returns {Boolean} + */ +function init (config) { + if (!config?.params?.groupId?.length > 0) { + logWarn('Qortex RTD module config does not contain valid groupId parameter. Config params: ' + JSON.stringify(config.params)) + return false; + } else { + initializeModuleData(config); + } + if (config?.params?.tagConfig) { + loadScriptTag(config) + } + return true; +} + +/** + * Processess prebid request and attempts to add context to ort2b fragments + * @param {Object} reqBidsConfig Bid request configuration object + * @param {Function} callback Called on completion + */ +function getBidRequestData (reqBidsConfig, callback) { + if (reqBidsConfig?.adUnits?.length > 0) { + getContext() + .then(contextData => { + setContextData(contextData) + addContextToRequests(reqBidsConfig) + callback(); + }) + .catch((e) => { + logWarn(e?.message); + callback(); + }); + } else { + logWarn('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfig)) + callback(); + } +} + +/** + * determines whether to send a request to context api and does so if necessary + * @returns {Promise} ortb Content object + */ +export function getContext () { + if (!currentSiteContext) { + logMessage('Requesting new context data'); + return new Promise((resolve, reject) => { + const callbacks = { + success(text, data) { + const result = data.status === 200 ? JSON.parse(data.response)?.content : null; + resolve(result); + }, + error(error) { + reject(new Error(error)); + } + } + ajax(requestUrl, callbacks) + }) + } else { + logMessage('Adding Content object from existing context data'); + return new Promise(resolve => resolve(currentSiteContext)); + } +} + +/** + * Updates bidder configs with the response from Qortex context services + * @param {Object} reqBidsConfig Bid request configuration object + * @param {string[]} bidders Bidders specified in module's configuration + */ +export function addContextToRequests (reqBidsConfig) { + if (currentSiteContext === null) { + logWarn('No context data recieved at this time'); + } else { + const fragment = { site: {content: currentSiteContext} } + if (bidderArray?.length > 0) { + bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})) + } else if (!bidderArray) { + mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment); + } else { + logWarn('Config contains an empty bidders array, unable to determine which bids to enrich'); + } + } +} + +/** + * Loads Qortex header tag using data passed from module config object + * @param {Object} config module config obtained during init + */ +export function loadScriptTag(config) { + const code = 'qortex'; + const groupId = config.params.groupId; + const src = 'https://tags.qortex.ai/bootstrapper' + const attr = {'data-group-id': groupId} + const tc = config.params.tagConfig + + Object.keys(tc).forEach(p => { + attr[`data-${p.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`)}`] = tc[p] + }) + + addEventListener('qortex-rtd', (e) => { + const billableEvent = { + vendor: code, + billingId: generateUUID(), + type: e?.detail?.type, + accountId: groupId + } + switch (e?.detail?.type) { + case 'qx-impression': + const {uid} = e.detail; + if (!uid || impressionIds.has(uid)) { + logWarn(`recieved invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`) + return; + } else { + logMessage('recieved billable event: qx-impression') + impressionIds.add(uid) + billableEvent.transactionId = e.detail.uid; + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, billableEvent); + break; + } + default: + logWarn(`recieved invalid billable event: ${e.detail?.type}`) + } + }) + + loadExternalScript(src, code, undefined, undefined, attr); +} + +/** + * Helper function to set initial values when they are obtained by init + * @param {Object} config module config obtained during init + */ +export function initializeModuleData(config) { + const DEFAULT_API_URL = 'https://demand.qortex.ai'; + const {apiUrl, groupId, bidders} = config.params; + requestUrl = `${apiUrl || DEFAULT_API_URL}/api/v1/analyze/${groupId}/prebid`; + bidderArray = bidders; + impressionIds = new Set(); + currentSiteContext = null; +} + +export function setContextData(value) { + currentSiteContext = value +} + +export const qortexSubmodule = { + name: 'qortex', + init, + getBidRequestData +} + +submodule('realTimeData', qortexSubmodule); diff --git a/modules/qortexRtdProvider.md b/modules/qortexRtdProvider.md new file mode 100644 index 00000000000..312696068cd --- /dev/null +++ b/modules/qortexRtdProvider.md @@ -0,0 +1,69 @@ +# Qortex Real-time Data Submodule + +## Overview + +``` +Module Name: Qortex RTD Provider +Module Type: RTD Provider +Maintainer: mannese@qortex.ai +``` + +## Description + +The Qortex RTD module appends contextual segments to the bidding object based on the content of a page using the Qortex API. + +Upon load, the Qortex context API will analyze the bidder page (video, text, image, etc.) and will return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26). The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data. + + +## Build +``` +gulp build --modules="rtdModule,qortexRtdProvider,qortexBidAdapter,..." +``` + +> `rtdModule` is a required module to use Qortex RTD module. + +## Configuration + +Please refer to [Prebid Documentation](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-realTimeData) on RTD module configuration for details on required and optional parameters of `realTimeData` + +When configuring Qortex as a data provider, refer to the template below to add the necessary information to ensure the proper connection is made. + +### RTD Module Setup + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: 'qortex', + waitForIt: true, + params: { + groupId: 'ABC123', //required + bidders: ['qortex', 'adapter2'], //optional (see below) + tagConfig: { // optional, please reach out to your account manager for configuration reccommendation + videoContainer: 'string', + htmlContainer: 'string', + attachToTop: 'string', + esm6Mod: 'string', + continuousLoad: 'string' + } + } + }] + } +}); +``` + +### Paramter Details + +#### `groupId` - Required +- The Qortex groupId linked to the publisher, this is required to make a request using this adapter + +#### `bidders` - optional +- If this parameter is included, it must be an array of the strings that match the bidder code of the prebid adapters you would like this module to impact. `ortb2.site.content` will be updated *only* for adapters in this array + +- If this parameter is omitted, the RTD module will default to updating `ortb2.site.content` on *all* bid adapters being used on the page + +#### `tagConfig` - optional +- This optional parameter is an object containing the config settings that could be usedto initialize the Qortex integration on your page. A preconfigured object for this step will be provided to you by the Qortex team. + +- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal. \ No newline at end of file diff --git a/src/adloader.js b/src/adloader.js index a87b930b7df..d69d2032b40 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -27,6 +27,7 @@ const _approvedLoadExternalJSList = [ 'clean.io', 'a1Media', 'geoedge', + 'qortex' ] /** diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js new file mode 100644 index 00000000000..f6cf8798850 --- /dev/null +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -0,0 +1,333 @@ +import * as utils from 'src/utils'; +import * as ajax from 'src/ajax.js'; +import * as events from 'src/events.js'; +import CONSTANTS from '../../../src/constants.json'; +import {loadExternalScript} from 'src/adloader.js'; +import { + qortexSubmodule as module, + getContext, + addContextToRequests, + setContextData, + initializeModuleData, + loadScriptTag +} from '../../../modules/qortexRtdProvider'; +import {server} from '../../mocks/xhr.js'; +import { cloneDeep } from 'lodash'; + +describe('qortexRtdProvider', () => { + let logWarnSpy; + let ortb2Stub; + + const defaultApiHost = 'https://demand.qortex.ai'; + const defaultGroupId = 'test'; + + const validBidderArray = ['qortex', 'test']; + const validTagConfig = { + videoContainer: 'my-video-container' + } + + const validModuleConfig = { + params: { + groupId: defaultGroupId, + apiUrl: defaultApiHost, + bidders: validBidderArray + } + }, + emptyModuleConfig = { + params: {} + } + + const validImpressionEvent = { + detail: { + uid: 'uid123', + type: 'qx-impression' + } + }, + validImpressionEvent2 = { + detail: { + uid: 'uid1234', + type: 'qx-impression' + } + }, + missingIdImpressionEvent = { + detail: { + type: 'qx-impression' + } + }, + invalidTypeQortexEvent = { + detail: { + type: 'invalid-type' + } + } + + const responseHeaders = { + 'content-type': 'application/json', + 'access-control-allow-origin': '*' + }; + + const responseObj = { + content: { + id: '123456', + episode: 15, + title: 'test episode', + series: 'test show', + season: '1', + url: 'https://example.com/file.mp4' + } + }; + + const apiResponse = JSON.stringify(responseObj); + + const reqBidsConfig = { + adUnits: [{ + bids: [ + { bidder: 'qortex' } + ] + }], + ortb2Fragments: { + bidder: {}, + global: {} + } + } + + beforeEach(() => { + ortb2Stub = sinon.stub(reqBidsConfig, 'ortb2Fragments').value({bidder: {}, global: {}}) + logWarnSpy = sinon.spy(utils, 'logWarn'); + }) + + afterEach(() => { + logWarnSpy.restore(); + ortb2Stub.restore(); + setContextData(null); + }) + + describe('init', () => { + it('returns true for valid config object', () => { + expect(module.init(validModuleConfig)).to.be.true; + }) + + it('returns false and logs error for missing groupId', () => { + expect(module.init(emptyModuleConfig)).to.be.false; + expect(logWarnSpy.calledOnce).to.be.true; + expect(logWarnSpy.calledWith('Qortex RTD module config does not contain valid groupId parameter. Config params: {}')).to.be.ok; + }) + + it('loads Qortex script if tagConfig is present in module config params', () => { + const config = cloneDeep(validModuleConfig); + config.params.tagConfig = validTagConfig; + expect(module.init(config)).to.be.true; + expect(loadExternalScript.calledOnce).to.be.true; + }) + }) + + describe('loadScriptTag', () => { + let addEventListenerSpy; + let billableEvents = []; + + let config = cloneDeep(validModuleConfig); + config.params.tagConfig = validTagConfig; + + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + billableEvents.push(e); + }) + + beforeEach(() => { + initializeModuleData(config); + addEventListenerSpy = sinon.spy(window, 'addEventListener'); + }) + + afterEach(() => { + addEventListenerSpy.restore(); + billableEvents = []; + }) + + it('adds event listener', () => { + loadScriptTag(config); + expect(addEventListenerSpy.calledOnce).to.be.true; + }) + + it('parses incoming qortex-impression events', () => { + loadScriptTag(config); + dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); + expect(billableEvents.length).to.be.equal(1); + expect(billableEvents[0].type).to.be.equal(validImpressionEvent.detail.type); + expect(billableEvents[0].transactionId).to.be.equal(validImpressionEvent.detail.uid); + }) + + it('will emit two events for impressions with two different ids', () => { + loadScriptTag(config); + dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); + dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent2)); + expect(billableEvents.length).to.be.equal(2); + expect(billableEvents[0].transactionId).to.be.equal(validImpressionEvent.detail.uid); + expect(billableEvents[1].transactionId).to.be.equal(validImpressionEvent2.detail.uid); + }) + + it('will not allow multiple events with the same id', () => { + loadScriptTag(config); + dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); + dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); + expect(billableEvents.length).to.be.equal(1); + expect(logWarnSpy.calledWith('recieved invalid billable event due to duplicate uid: qx-impression')).to.be.ok; + }) + + it('will not allow events with missing uid', () => { + loadScriptTag(config); + dispatchEvent(new CustomEvent('qortex-rtd', missingIdImpressionEvent)); + expect(billableEvents.length).to.be.equal(0); + expect(logWarnSpy.calledWith('recieved invalid billable event due to missing uid: qx-impression')).to.be.ok; + }) + + it('will not allow events with unavailable type', () => { + loadScriptTag(config); + dispatchEvent(new CustomEvent('qortex-rtd', invalidTypeQortexEvent)); + expect(billableEvents.length).to.be.equal(0); + expect(logWarnSpy.calledWith('recieved invalid billable event: invalid-type')).to.be.ok; + }) + }) + + describe('getBidRequestData', () => { + let callbackSpy; + + beforeEach(() => { + initializeModuleData(validModuleConfig); + callbackSpy = sinon.spy(); + }) + + afterEach(() => { + initializeModuleData(emptyModuleConfig); + callbackSpy.resetHistory(); + }) + + it('will call callback immediately if no adunits', () => { + const reqBidsConfigNoBids = { adUnits: [] }; + module.getBidRequestData(reqBidsConfigNoBids, callbackSpy); + expect(callbackSpy.calledOnce).to.be.true; + expect(logWarnSpy.calledWith('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfigNoBids))).to.be.ok; + }) + + it('will call callback if getContext does not throw', () => { + const cb = function () { + expect(logWarnSpy.calledOnce).to.be.false; + done(); + } + module.getBidRequestData(reqBidsConfig, cb); + server.requests[0].respond(200, responseHeaders, apiResponse); + }) + + it('will catch and log error and fire callback', (done) => { + const a = sinon.stub(ajax, 'ajax').throws(new Error('test')); + const cb = function () { + expect(logWarnSpy.calledWith('test')).to.be.eql(true); + done(); + } + module.getBidRequestData(reqBidsConfig, cb); + a.restore(); + }) + }) + + describe('getContext', () => { + beforeEach(() => { + initializeModuleData(validModuleConfig); + }) + + afterEach(() => { + initializeModuleData(emptyModuleConfig); + }) + + it('returns a promise', (done) => { + const result = getContext(); + expect(result).to.be.a('promise'); + done(); + }) + + it('uses request url generated from initialize function in config and resolves to content object data', (done) => { + let requestUrl = `${validModuleConfig.params.apiUrl}/api/v1/analyze/${validModuleConfig.params.groupId}/prebid`; + const ctx = getContext() + expect(server.requests.length).to.be.eql(1); + expect(server.requests[0].url).to.be.eql(requestUrl); + server.requests[0].respond(200, responseHeaders, apiResponse); + ctx.then(response => { + expect(response).to.be.eql(responseObj.content); + done(); + }); + }) + + it('will return existing context data instead of ajax call if the source was not updated', (done) => { + setContextData(responseObj.content); + const ctx = getContext(); + expect(server.requests.length).to.be.eql(0); + ctx.then(response => { + expect(response).to.be.eql(responseObj.content); + done(); + }); + }) + + it('returns null for non erroring api responses other than 200', (done) => { + const nullContentResponse = { content: null } + const ctx = getContext() + server.requests[0].respond(200, responseHeaders, JSON.stringify(nullContentResponse)) + ctx.then(response => { + expect(response).to.be.null; + expect(server.requests.length).to.be.eql(1); + expect(logWarnSpy.called).to.be.false; + done(); + }); + }) + }) + + describe(' addContextToRequests', () => { + it('logs error if no data was retrieved from get context call', () => { + initializeModuleData(validModuleConfig); + addContextToRequests(reqBidsConfig); + expect(logWarnSpy.calledOnce).to.be.true; + expect(logWarnSpy.calledWith('No context data recieved at this time')).to.be.ok; + expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); + expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); + }) + + it('adds site.content only to global ortb2 when bidders array is omitted', () => { + const omittedBidderArrayConfig = cloneDeep(validModuleConfig); + delete omittedBidderArrayConfig.params.bidders; + initializeModuleData(omittedBidderArrayConfig); + setContextData(responseObj.content); + addContextToRequests(reqBidsConfig); + expect(reqBidsConfig.ortb2Fragments.global).to.have.property('site'); + expect(reqBidsConfig.ortb2Fragments.global.site).to.have.property('content'); + expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(responseObj.content); + expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); + }) + + it('adds site.content only to bidder ortb2 when bidders array is included', () => { + initializeModuleData(validModuleConfig); + setContextData(responseObj.content); + addContextToRequests(reqBidsConfig); + + const qortexOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['qortex'] + expect(qortexOrtb2Fragment).to.not.be.null; + expect(qortexOrtb2Fragment).to.have.property('site'); + expect(qortexOrtb2Fragment.site).to.have.property('content'); + expect(qortexOrtb2Fragment.site.content).to.be.eql(responseObj.content); + + const testOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['test'] + expect(testOrtb2Fragment).to.not.be.null; + expect(testOrtb2Fragment).to.have.property('site'); + expect(testOrtb2Fragment.site).to.have.property('content'); + expect(testOrtb2Fragment.site.content).to.be.eql(responseObj.content); + + expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); + }) + + it('logs error if there is an empty bidder array', () => { + const invalidBidderArrayConfig = cloneDeep(validModuleConfig); + invalidBidderArrayConfig.params.bidders = []; + initializeModuleData(invalidBidderArrayConfig); + setContextData(responseObj.content) + addContextToRequests(reqBidsConfig); + + expect(logWarnSpy.calledWith('Config contains an empty bidders array, unable to determine which bids to enrich')).to.be.ok; + expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); + expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); + }) + }) +}) From a8753d5edbadc7b62e8b4b292444086232cbfa4c Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Tue, 26 Sep 2023 20:47:13 -0400 Subject: [PATCH 053/760] update appnexus PBS default configs (#10503) * update appnexus PBS configs * fix remaining appnexus defaultVendor references --- .../gpt/prebidServer_example.html | 40 ++++++++++++------- .../gpt/prebidServer_fledge_example.html | 2 +- modules/prebidServerBidAdapter/config.js | 15 ++----- test/spec/auctionmanager_spec.js | 4 +- .../modules/prebidServerBidAdapter_spec.js | 32 ++------------- 5 files changed, 35 insertions(+), 58 deletions(-) diff --git a/integrationExamples/gpt/prebidServer_example.html b/integrationExamples/gpt/prebidServer_example.html index f23554369bc..ded50777ad2 100644 --- a/integrationExamples/gpt/prebidServer_example.html +++ b/integrationExamples/gpt/prebidServer_example.html @@ -33,31 +33,41 @@ pbjs.que.push(function() { var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 13144370 - } + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [600, 500] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 12883451 } - ] - }]; + } + ] + }]; + pbjs.bidderSettings = { + appnexus: { + bidCpmAdjustment: function () { + return 10; + } + } + } pbjs.setConfig({ bidderTimeout: 3000, s2sConfig : { accountId : '1', enabled : true, //default value set to false - defaultVendor: 'appnexus', + defaultVendor: 'appnexuspsp', bidders : ['appnexus'], timeout : 1000, //default value is 1000 adapter : 'prebidServer', //if we have any other s2s adapter, default value is s2s + }, + ortb2: { + test: 1 } }); diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_fledge_example.html index 8523c0f2920..eb2fc438997 100644 --- a/integrationExamples/gpt/prebidServer_fledge_example.html +++ b/integrationExamples/gpt/prebidServer_fledge_example.html @@ -50,7 +50,7 @@ s2sConfig: [{ accountId : '1', enabled : true, - defaultVendor: 'appnexus', + defaultVendor: 'appnexuspsp', bidders : ['openx'], timeout : 1500, adapter : 'prebidServer' diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index f6b8ac9f86a..29e80dfcc9f 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -1,11 +1,11 @@ // accountId and bidders params are not included here, should be configured by end-user export const S2S_VENDORS = { - 'appnexus': { + 'appnexuspsp': { adapter: 'prebidServer', enabled: true, endpoint: { - p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', - noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction' + p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', + noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' }, syncEndpoint: { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', @@ -13,15 +13,6 @@ export const S2S_VENDORS = { }, timeout: 1000 }, - 'appnexuspsp': { - adapter: 'prebidServer', - enabled: true, - endpoint: { - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', - noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' - }, - timeout: 1000 - }, 'rubicon': { adapter: 'prebidServer', enabled: true, diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index be4a7f819cd..c18f2df9466 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -527,7 +527,7 @@ describe('auctionmanager.js', function () { s2sConfig: { accountId: '1', enabled: true, - defaultVendor: 'appnexus', + defaultVendor: 'appnexuspsp', bidders: ['appnexus'], timeout: 1000, adapter: 'prebidServer' @@ -1263,7 +1263,7 @@ describe('auctionmanager.js', function () { s2sConfig: [{ accountId: '1', enabled: true, - defaultVendor: 'appnexus', + defaultVendor: 'appnexuspsp', bidders: ['mock-s2s-1'], adapter: 'pbs' }, { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 717a5cd6c6d..ecace22f97f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3635,33 +3635,6 @@ describe('S2S Adapter', function () { sinon.assert.calledOnce(logErrorSpy); }); - it('should configure the s2sConfig object with appnexus vendor defaults unless specified by user', function () { - const options = { - accountId: '123', - bidders: ['appnexus'], - defaultVendor: 'appnexus', - timeout: 750 - }; - - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', '123'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['appnexus']); - expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({ - p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', - noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/openrtb2/auction' - }); - expect(vendorConfig.syncEndpoint).to.deep.equal({ - p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', - noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' - }); - expect(vendorConfig).to.have.property('timeout', 750); - }); - it('should configure the s2sConfig object with appnexuspsp vendor defaults unless specified by user', function () { const options = { accountId: '123', @@ -3682,7 +3655,10 @@ describe('S2S Adapter', function () { p1Consent: 'https://ib.adnxs.com/openrtb2/prebid', noP1Consent: 'https://ib.adnxs-simple.com/openrtb2/prebid' }); - expect(vendorConfig.syncEndpoint).to.be.undefined; + expect(vendorConfig.syncEndpoint).to.deep.equal({ + p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', + noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + }); expect(vendorConfig).to.have.property('timeout', 750); }); From 8da686e2b320178866c76bb8a929f369ee5512d7 Mon Sep 17 00:00:00 2001 From: tkrishnaviant Date: Tue, 26 Sep 2023 17:51:08 -0700 Subject: [PATCH 054/760] viantOrtbBidAdapter - update contact information in md file (#10538) Updating the contact email id for viantOrtbBidAdapter --- modules/viantOrtbBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/viantOrtbBidAdapter.md b/modules/viantOrtbBidAdapter.md index 178c2c18a0f..def93722b7b 100644 --- a/modules/viantOrtbBidAdapter.md +++ b/modules/viantOrtbBidAdapter.md @@ -2,7 +2,7 @@ Module Name: VIANT Bidder Adapter Module Type: Bidder Adapter -Maintainer: dist-runtime@viantinc.com +Maintainer: Marketplace@adelphic.com # Description From e0f80a4a1912f4d3ebf9b03aac25edbf8e7cf418 Mon Sep 17 00:00:00 2001 From: dani-nova <73398187+dani-nova@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:42:36 +0200 Subject: [PATCH 055/760] Dxkulture Bid Adapter: changing default video placement value (#10473) * Changing default value for video placement * Adjusting video placement parameter --------- Co-authored-by: kmdevops --- modules/dxkultureBidAdapter.js | 5 +---- test/spec/modules/dxkultureBidAdapter_spec.js | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/dxkultureBidAdapter.js b/modules/dxkultureBidAdapter.js index 2e6f6c77b85..a8f7b4b86ba 100644 --- a/modules/dxkultureBidAdapter.js +++ b/modules/dxkultureBidAdapter.js @@ -22,6 +22,7 @@ const OPENRTB_VIDEO_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -327,10 +328,6 @@ function buildVideoRequestData(bidRequest, bidderRequest) { } }); - // Placement Inference Rules: - // - If no placement is defined then default to 1 (In Stream) - video.placement = video.placement || 2; - // - If product is instream (for instream context) then override placement to 1 if (params.context === 'instream') { video.startdelay = video.startdelay || 0; diff --git a/test/spec/modules/dxkultureBidAdapter_spec.js b/test/spec/modules/dxkultureBidAdapter_spec.js index bf76ddd0c8a..ec7f6f146a3 100644 --- a/test/spec/modules/dxkultureBidAdapter_spec.js +++ b/test/spec/modules/dxkultureBidAdapter_spec.js @@ -174,6 +174,7 @@ describe('dxkultureBidAdapter:', function () { sid: 134, rewarded: 1, placement: 1, + plcmt: 1, hp: 1, inventoryid: 123 }, @@ -403,6 +404,8 @@ describe('dxkultureBidAdapter:', function () { expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); expect(data.imp[0].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.imp[0]['video']['placement']).to.equal(videoBidRequest.params.video['placement']); + expect(data.imp[0]['video']['plcmt']).to.equal(videoBidRequest.params.video['plcmt']); expect(data.ext.prebidver).to.equal('$prebid.version$'); expect(data.ext.adapterver).to.equal(spec.VERSION); }); From e4c13498dcc6fe908fcee481051a33cea9225e6e Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Wed, 27 Sep 2023 18:41:01 +0530 Subject: [PATCH 056/760] PubmaticAnalyticsAdapter: add prebid version in logger call (#10531) * Sending prebid version in logger call * Test cases * Kick off integration test and styles --------- Co-authored-by: Chris Huie --- modules/pubmaticAnalyticsAdapter.js | 1 + test/spec/modules/pubmaticAnalyticsAdapter_spec.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 0651b373f12..a636b7340b0 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -362,6 +362,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['pdvid'] = '' + profileVersionId; outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = getTgId(); + outputObj['pbv'] = getGlobal()?.version || '-1'; if (floorData && fetchStatus) { outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index ad471252f30..7fce08fc24f 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,10 +1,11 @@ import pubmaticAnalyticsAdapter, {getMetadata} from 'modules/pubmaticAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; -import {config} from 'src/config.js'; -import {setConfig} from 'modules/currency.js'; -import {server} from '../../mocks/xhr.js'; +import { config } from 'src/config.js'; +import { setConfig } from 'modules/currency.js'; +import { server } from '../../mocks/xhr.js'; import 'src/prebid.js'; +import { getGlobal } from 'src/prebidGlobal'; let events = require('src/events'); let ajax = require('src/ajax'); @@ -367,6 +368,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); + expect(data.pbv).to.equal(getGlobal()?.version || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -574,6 +576,7 @@ describe('pubmatic analytics adapter', function () { expect(data.pid).to.equal('1111'); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); + expect(data.pbv).to.equal(getGlobal()?.version || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.tgid).to.equal(0); @@ -648,6 +651,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); + expect(data.pbv).to.equal(getGlobal()?.version || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -1184,6 +1188,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); + expect(data.pbv).to.equal(getGlobal()?.version || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1305,6 +1310,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); + expect(data.pbv).to.equal(getGlobal()?.version || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); From 4f7fcc8f101d76c8b2fc749a33dfd20b603a5acf Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 27 Sep 2023 07:01:25 -0700 Subject: [PATCH 057/760] Core: do not log errors about invalid XML when serializing XHR objects (#10483) --- src/ajax.js | 24 +++++++++++++++--------- test/spec/unit/core/ajax_spec.js | 22 +++++++++++++++++----- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/ajax.js b/src/ajax.js index 0601cc0e22b..6f2f45e935e 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -89,6 +89,17 @@ export function fetcherFactory(timeout = 3000, {request, done} = {}) { function toXHR({status, statusText = '', headers, url}, responseText) { let xml = 0; + function getXML(onError) { + if (xml === 0) { + try { + xml = new DOMParser().parseFromString(responseText, headers?.get(CTYPE)?.split(';')?.[0]) + } catch (e) { + xml = null; + onError && onError(e) + } + } + return xml; + } return { readyState: XMLHttpRequest.DONE, status, @@ -98,17 +109,12 @@ function toXHR({status, statusText = '', headers, url}, responseText) { responseType: '', responseURL: url, get responseXML() { - if (xml === 0) { - try { - xml = new DOMParser().parseFromString(responseText, headers?.get(CTYPE)?.split(';')?.[0]) - } catch (e) { - xml = null; - logError(e); - } - } - return xml; + return getXML(logError); }, getResponseHeader: (header) => headers?.has(header) ? headers.get(header) : null, + toJSON() { + return Object.assign({responseXML: getXML()}, this) + } } } diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js index df0ce02c15c..a3a0459b980 100644 --- a/test/spec/unit/core/ajax_spec.js +++ b/test/spec/unit/core/ajax_spec.js @@ -1,7 +1,8 @@ -import {dep, attachCallbacks, fetcherFactory, toFetchRequest} from '../../../../src/ajax.js'; +import {attachCallbacks, dep, fetcherFactory, toFetchRequest} from '../../../../src/ajax.js'; import {config} from 'src/config.js'; import {server} from '../../../mocks/xhr.js'; -import {sandbox} from 'sinon'; +import * as utils from 'src/utils.js'; +import {logError} from 'src/utils.js'; const EXAMPLE_URL = 'https://www.example.com'; @@ -312,13 +313,24 @@ describe('attachCallbacks', () => { const cbType = success ? 'success' : 'error'; describe(`for ${t}`, () => { - let response, body; + let sandbox, response, body; beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.spy(utils, 'logError'); ({response, body} = makeResponse()); }); + afterEach(() => { + sandbox.restore(); + }) + function checkXHR(xhr) { - sinon.assert.match(xhr, { + utils.logError.resetHistory(); + const serialized = JSON.parse(JSON.stringify(xhr)) + // serialization of `responseXML` should not generate console messages + sinon.assert.notCalled(utils.logError); + + sinon.assert.match(serialized, { readyState: XMLHttpRequest.DONE, status: response.status, statusText: response.statusText, @@ -330,7 +342,7 @@ describe('attachCallbacks', () => { if (xml) { expect(xhr.responseXML.querySelectorAll('*').length > 0).to.be.true; } else { - expect(xhr.responseXML).to.not.exist; + expect(serialized.responseXML).to.not.exist; } Array.from(response.headers.entries()).forEach(([name, value]) => { expect(xhr.getResponseHeader(name)).to.eql(value); From 94a3e5bef698ff28d1e39513957c8ad33ed870f3 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 27 Sep 2023 21:28:38 +0300 Subject: [PATCH 058/760] Illumin Bid Adapter: initial release (#10375) * MinuteMediaPlus Bid Adapter: pass gpp consent to userSync server. * Illumin Bid Adapter: added adapter. * remove changes that not relevant for this PR. * change extract method. * fix test --- modules/illuminBidAdapter.js | 336 +++++++++++ modules/illuminBidAdapter.md | 35 ++ test/spec/modules/illuminBidAdapter_spec.js | 634 ++++++++++++++++++++ 3 files changed, 1005 insertions(+) create mode 100644 modules/illuminBidAdapter.js create mode 100644 modules/illuminBidAdapter.md create mode 100644 test/spec/modules/illuminBidAdapter_spec.js diff --git a/modules/illuminBidAdapter.js b/modules/illuminBidAdapter.js new file mode 100644 index 00000000000..36ff7bea0a3 --- /dev/null +++ b/modules/illuminBidAdapter.js @@ -0,0 +1,336 @@ +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; + +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'illumin'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.illumin.com`; +} + +export function extractCID(params) { + return params.cId; +} + +export function extractPID(params) { + return params.pId; +} + +export function extractSubDomain(params) { + return params.subDomain; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + gpid: gpid, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {bidId} = request.data; + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + metaData, + advertiserDomains, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.illumin.com/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.illumin.com/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/illuminBidAdapter.md b/modules/illuminBidAdapter.md new file mode 100644 index 00000000000..8ca656230e4 --- /dev/null +++ b/modules/illuminBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Illumin Bid Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** integrations@illumin.com + +# Description + +Module that connects to Illumin's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'illumin', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js new file mode 100644 index 00000000000..9b702c027f9 --- /dev/null +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -0,0 +1,634 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/illuminBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + }, + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['illumin.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('IlluminBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + illumin: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + prebidVersion: version, + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '0123456789' + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.illumin.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.illumin.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['illumin.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['illumin.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['illumin.com'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cId': '1'}); + const pid = extractPID({'pId': '2'}); + const subDomain = extractSubDomain({'subDomain': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + illumin: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + illumin: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From 24fc39db70174573099e467d01f775f23bc7dd9d Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Thu, 28 Sep 2023 09:46:33 +0100 Subject: [PATCH 059/760] Alkimi bid adapter: Resolve AUCTION_PRICE macro for VAST ads (#10536) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support * Support new OpenRTB parameters * Support new oRTB2 parameters * remove pos parameter * Add gvl_id into Alkimi adapter * Insert keywords into bid-request param * Resolve AUCTION_PRICE macro on prebid-server for VAST ads --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: mik --- modules/alkimiBidAdapter.js | 25 +++++++--------------- test/spec/modules/alkimiBidAdapter_spec.js | 6 +++--- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 81d993e9ac8..53f6460434f 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -1,7 +1,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, deepClone, getDNT, generateUUID} from '../src/utils.js'; +import {deepAccess, deepClone, getDNT, generateUUID, replaceAuctionPrice} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; -import {VIDEO} from '../src/mediaTypes.js'; +import {VIDEO, BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; const BIDDER_CODE = 'alkimi'; @@ -116,7 +116,7 @@ export const spec = { // banner or video if (VIDEO === bid.mediaType) { - bid.vastXml = bid.ad; + bid.vastUrl = replaceAuctionPrice(bid.winUrl, bid.cpm); } bid.meta = {}; @@ -129,21 +129,12 @@ export const spec = { }, onBidWon: function (bid) { - let winUrl; - if (bid.winUrl || bid.vastUrl) { - winUrl = bid.winUrl ? bid.winUrl : bid.vastUrl; - winUrl = winUrl.replace(/\$\{AUCTION_PRICE}/, bid.cpm); - } else if (bid.ad) { - let trackImg = bid.ad.match(/(?!^)/); - bid.ad = bid.ad.replace(trackImg[0], ''); - winUrl = trackImg[0].split('"')[1]; - winUrl = winUrl.replace(/\$%7BAUCTION_PRICE%7D/, bid.cpm); - } else { - return false; + if (BANNER == bid.mediaType && bid.winUrl) { + const winUrl = replaceAuctionPrice(bid.winUrl, bid.cpm); + ajax(winUrl, null); + return true; } - - ajax(winUrl, null); - return true; + return false; } } diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 3101fac7500..90a9e409e69 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -68,7 +68,7 @@ const BIDDER_VIDEO_RESPONSE = { 'ttl': 200, 'creativeId': 2, 'netRevenue': true, - 'winUrl': 'http://test.com', + 'winUrl': 'http://test.com?price=${AUCTION_PRICE}', 'mediaType': 'video', 'adomain': ['test.com'] }] @@ -195,9 +195,9 @@ describe('alkimiBidAdapter', function () { expect(result[0]).to.have.property('ttl').equal(200) expect(result[0]).to.have.property('creativeId').equal(2) expect(result[0]).to.have.property('netRevenue').equal(true) - expect(result[0]).to.have.property('winUrl').equal('http://test.com') + expect(result[0]).to.have.property('winUrl').equal('http://test.com?price=${AUCTION_PRICE}') expect(result[0]).to.have.property('mediaType').equal('video') - expect(result[0]).to.have.property('vastXml').equal('vast') + expect(result[0]).to.have.property('vastUrl').equal('http://test.com?price=800.4') expect(result[0].meta).to.exist.property('advertiserDomains') expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) }) From 702bbbec675baebda1fe355cdcefe2ebe5d95739 Mon Sep 17 00:00:00 2001 From: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:33:37 +0300 Subject: [PATCH 060/760] AcuityAds Bid Adapter: add gpp support (#10544) * add prebid.js adapter * changes * changes * changes * changes * fix downolad * add gpp --- ...dsBidAdapter.js => acuityadsBidAdapter.js} | 9 +++++ ...dsBidAdapter.md => acuityadsBidAdapter.md} | 2 +- ...er_spec.js => acuityadsBidAdapter_spec.js} | 33 +++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) rename modules/{acuityAdsBidAdapter.js => acuityadsBidAdapter.js} (94%) rename modules/{acuityAdsBidAdapter.md => acuityadsBidAdapter.md} (98%) rename test/spec/modules/{acuityAdsBidAdapter_spec.js => acuityadsBidAdapter_spec.js} (92%) diff --git a/modules/acuityAdsBidAdapter.js b/modules/acuityadsBidAdapter.js similarity index 94% rename from modules/acuityAdsBidAdapter.js rename to modules/acuityadsBidAdapter.js index b0bb132ddae..5b12eb2133b 100644 --- a/modules/acuityAdsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -153,6 +153,15 @@ export const spec = { tmax: bidderRequest.timeout }; + // Add GPP consent + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; diff --git a/modules/acuityAdsBidAdapter.md b/modules/acuityadsBidAdapter.md similarity index 98% rename from modules/acuityAdsBidAdapter.md rename to modules/acuityadsBidAdapter.md index a19e0a6b0ba..7f001cd9376 100644 --- a/modules/acuityAdsBidAdapter.md +++ b/modules/acuityadsBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: AcuityAds Bidder Adapter Module Type: AcuityAds Bidder Adapter -Maintainer: sa-support@brightcom.com +Maintainer: rafi.babler@acuityads.com ``` # Description diff --git a/test/spec/modules/acuityAdsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js similarity index 92% rename from test/spec/modules/acuityAdsBidAdapter_spec.js rename to test/spec/modules/acuityadsBidAdapter_spec.js index 05c59036ff3..31ef9dd6466 100644 --- a/test/spec/modules/acuityAdsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/acuityAdsBidAdapter'; +import { spec } from '../../../modules/acuityadsBidAdapter'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; @@ -77,7 +77,8 @@ describe('AcuityAdsBidAdapter', function () { refererInfo: { referer: 'https://test.com' }, - timeout: 500 + timeout: 500, + ortb2: {} }; describe('isBidRequestValid', function () { @@ -187,6 +188,34 @@ describe('AcuityAdsBidAdapter', function () { expect(data.gdpr).to.not.exist; }); + describe('Returns data with gppConsent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + }) + }); + it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([], bidderRequest); let data = serverRequest.data; From 6c7356a1b9013ba13a5ee2afbd4b0b7778621e7f Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:05:48 +0200 Subject: [PATCH 061/760] Oxxion Rtd Module: cleaning and change in sampling (#10539) * remove video stuffs * update sampling * listen for 'ova' parameter * update sampling --- modules/oxxionAnalyticsAdapter.js | 2 +- modules/oxxionRtdProvider.js | 103 ++++---------------- modules/oxxionRtdProvider.md | 9 +- test/spec/modules/oxxionRtdProvider_spec.js | 95 ------------------ 4 files changed, 21 insertions(+), 188 deletions(-) diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js index cc69443d8bf..d4dcd33be88 100644 --- a/modules/oxxionAnalyticsAdapter.js +++ b/modules/oxxionAnalyticsAdapter.js @@ -23,7 +23,7 @@ let auctionEnd = {} let initOptions = {} let mode = {}; let endpoint = 'https://default' -let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids', 'adId']; +let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids', 'adId', 'ova']; function getAdapterNameForAlias(aliasName) { return adapterManager.aliasRegistry[aliasName] || aliasName; diff --git a/modules/oxxionRtdProvider.js b/modules/oxxionRtdProvider.js index c6f8b9a902b..c979a10d00c 100644 --- a/modules/oxxionRtdProvider.js +++ b/modules/oxxionRtdProvider.js @@ -1,12 +1,10 @@ import { submodule } from '../src/hook.js' -import { deepAccess, logInfo, logError } from '../src/utils.js' +import { logInfo, logError } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; -const oxxionRtdSearchFor = [ 'adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'userId', 'labelAny', 'adId' ]; const LOG_PREFIX = 'oxxionRtdProvider submodule: '; -const allAdUnits = []; const bidderAliasRegistry = adapterManager.aliasRegistry || {}; /** @type {RtdSubmodule} */ @@ -14,14 +12,12 @@ export const oxxionSubmodule = { name: 'oxxionRtd', init: init, getBidRequestData: getAdUnits, - onBidResponseEvent: insertVideoTracking, getRequestsList: getRequestsList, getFilteredAdUnitsOnBidRates: getFilteredAdUnitsOnBidRates, }; function init(config, userConsent) { if (!config.params || !config.params.domain) { return false } - if (config.params.contexts && Array.isArray(config.params.contexts) && config.params.contexts.length > 0) { return true; } if (typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') { return true } return false; } @@ -53,81 +49,6 @@ function getAdUnits(reqBidsConfigObj, callback, config, userConsent) { if (typeof callback == 'function') { callback(); } }).catch(error => logError(LOG_PREFIX, 'bidInterestError', error)); } - if (config.params.contexts && Array.isArray(config.params.contexts) && config.params.contexts.length > 0) { - const reqAdUnits = reqBidsConfigObj.adUnits; - if (Array.isArray(reqAdUnits)) { - reqAdUnits.forEach(adunit => { - if (config.params.contexts.includes(deepAccess(adunit, 'mediaTypes.video.context'))) { - allAdUnits.push(adunit); - } - }); - } - if (!(typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') && typeof callback == 'function') { - callback(); - } - } -} - -function insertVideoTracking(bidResponse, config, userConsent) { - // this should only be do for video bids - if (bidResponse.mediaType === 'video') { - let maxCpm = 0; - const trackingUrl = getImpUrl(config, bidResponse, maxCpm); - if (!trackingUrl) { - return; - } - // Vast Impression URL - if (bidResponse.vastUrl) { - bidResponse.vastImpUrl = bidResponse.vastImpUrl - ? trackingUrl + '&url=' + encodeURI(bidResponse.vastImpUrl) - : trackingUrl; - logInfo(LOG_PREFIX + 'insert into vastImpUrl for adId ' + bidResponse.adId); - } - // Vast XML document - if (bidResponse.vastXml !== undefined) { - const doc = new DOMParser().parseFromString(bidResponse.vastXml, 'text/xml'); - const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine'); - let hasAltered = false; - if (wrappers.length) { - wrappers.forEach(wrapper => { - const impression = doc.createElement('Impression'); - impression.appendChild(doc.createCDATASection(trackingUrl)); - wrapper.appendChild(impression) - }); - bidResponse.vastXml = new XMLSerializer().serializeToString(doc); - hasAltered = true; - } - if (hasAltered) { - logInfo(LOG_PREFIX + 'insert into vastXml for adId ' + bidResponse.adId); - } - } - } -} - -function getImpUrl(config, data, maxCpm) { - const adUnitCode = data.adUnitCode; - const adUnits = allAdUnits.find(adunit => adunit.code === adUnitCode && - 'mediaTypes' in adunit && - 'video' in adunit.mediaTypes && - typeof adunit.mediaTypes.video.context === 'string'); - const context = adUnits !== undefined - ? adUnits.mediaTypes.video.context - : 'unknown'; - if (!config.params.contexts.includes(context)) { - return false; - } - let trackingImpUrl = 'https://' + config.params.domain + '.oxxion.io/analytics/vast_imp?'; - trackingImpUrl += oxxionRtdSearchFor.reduce((acc, param) => { - switch (typeof data[param]) { - case 'string': - case 'number': - acc += param + '=' + data[param] + '&' - break; - } - return acc; - }, ''); - const cpmIncrement = 0.0; - return trackingImpUrl + 'cpmIncrement=' + cpmIncrement + '&context=' + context; } function getPromisifiedAjax (url, data = {}, options = {}) { @@ -146,22 +67,27 @@ function getPromisifiedAjax (url, data = {}, options = {}) { function getFilteredAdUnitsOnBidRates (bidsRateInterests, adUnits, params, useSampling) { const { threshold, samplingRate } = params; + const sampling = getRandomNumber(100) < samplingRate && useSampling; const filteredBids = []; // Separate bidsRateInterests in two groups against threshold & samplingRate - const { interestingBidsRates, uninterestingBidsRates } = bidsRateInterests.reduce((acc, interestingBid) => { + const { interestingBidsRates, uninterestingBidsRates, sampledBidsRates } = bidsRateInterests.reduce((acc, interestingBid) => { const isBidRateUpper = typeof threshold == 'number' ? interestingBid.rate === true || interestingBid.rate > threshold : interestingBid.suggestion; - const isBidInteresting = isBidRateUpper || (getRandomNumber(100) < samplingRate && useSampling); + const isBidInteresting = isBidRateUpper || sampling; const key = isBidInteresting ? 'interestingBidsRates' : 'uninterestingBidsRates'; acc[key].push(interestingBid); + if (!isBidRateUpper && sampling) { + acc['sampledBidsRates'].push(interestingBid); + } return acc; }, { interestingBidsRates: [], - uninterestingBidsRates: [] // Do something with later + uninterestingBidsRates: [], // Do something with later + sampledBidsRates: [] }); logInfo(LOG_PREFIX, 'getFilteredAdUnitsOnBidRates()', interestingBidsRates, uninterestingBidsRates); // Filter bids and adUnits against interesting bids rates const newAdUnits = adUnits.filter(({ bids = [] }, adUnitIndex) => { - adUnits[adUnitIndex].bids = bids.filter(bid => { + adUnits[adUnitIndex].bids = bids.filter((bid, bidIndex) => { if (!params.bidders || params.bidders.includes(bid.bidder)) { const index = interestingBidsRates.findIndex(({ id }) => id === bid._id); if (index == -1) { @@ -173,10 +99,19 @@ function getFilteredAdUnitsOnBidRates (bidsRateInterests, adUnits, params, useSa delete tmpBid.floorData; } filteredBids.push(tmpBid); + adUnits[adUnitIndex].bids[bidIndex]['ova'] = 'filtered'; + } else { + if (sampledBidsRates.findIndex(({ id }) => id === bid._id) == -1) { + adUnits[adUnitIndex].bids[bidIndex]['ova'] = 'cleared'; + } else { + adUnits[adUnitIndex].bids[bidIndex]['ova'] = 'sampled'; + logInfo(LOG_PREFIX + ' sampled ! '); + } } delete bid._id; return index !== -1; } else { + adUnits[adUnitIndex].bids[bidIndex]['ova'] = 'protected'; return true; } }); diff --git a/modules/oxxionRtdProvider.md b/modules/oxxionRtdProvider.md index 14b4abec5c2..bfdbfae1fa9 100644 --- a/modules/oxxionRtdProvider.md +++ b/modules/oxxionRtdProvider.md @@ -7,7 +7,7 @@ Maintainer: tech@oxxion.io # Oxxion Real-Time-Data submodule Oxxion helps you to understand how your prebid stack performs. -This Rtd module is to use in order to improve video events tracking and/or to filter bidder requested. +This Rtd module purpose is to filter bidders requested. # Integration @@ -30,7 +30,6 @@ pbjs.setConfig( waitForIt: true, params: { domain: "test.endpoint", - contexts: ["instream"], threshold: false, samplingRate: 10, } @@ -47,12 +46,6 @@ pbjs.setConfig( |:---------------------------------|:---------|:------------------------------------------------------------------------------------------------------------| | domain | String | This string identifies yourself in Oxxion's systems and is provided to you by your Oxxion representative. | -# setConfig Parameters for Video Tracking - -| Name | Type | Description | -|:---------------------------------|:---------|:------------------------------------------------------------------------------------------------------------| -| contexts | Array | Array defining which video contexts to add tracking events into. Values can be instream and/or outstream. | - # setConfig Parameters for bidder filtering | Name | Type | Description | diff --git a/test/spec/modules/oxxionRtdProvider_spec.js b/test/spec/modules/oxxionRtdProvider_spec.js index 7bccf2319a4..2a8024f3565 100644 --- a/test/spec/modules/oxxionRtdProvider_spec.js +++ b/test/spec/modules/oxxionRtdProvider_spec.js @@ -113,77 +113,6 @@ let bids = [{ }, ]; -let originalBidderRequests = [{ - 'bidderCode': 'rubicon', - 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', - 'bidderRequestId': '16c2bceb2e891a', - 'bids': [ - { - 'bidder': 'rubicon', - 'params': { - 'accountId': 1234, - 'siteId': 2345, - 'zoneId': 3456 - }, - 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', - 'mediaTypes': {'banner': {'sizes': [[970, 250]]}}, - 'adUnitCode': 'adunit1', - 'transactionId': '8f20b49c-5e47-4bb5-a7d5-0b816cf527f3', - 'bidId': '2d9920072ab028', - 'bidderRequestId': '16c2bceb2e891a', - }, - { - 'bidder': 'rubicon', - 'params': { - 'accountId': 1234, - 'siteId': 2345, - 'zoneId': 4567 - }, - 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', - 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, - 'adUnitCode': 'adunit2', - 'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4', - 'bidId': '331c3d708f4864', - 'bidderRequestId': '16c2bceb2e891a', - 'src': 'client', - } - ], - 'auctionStart': 1683383333809, - 'timeout': 3000, - 'gdprConsent': { - 'consentString': 'consent_hash', - 'gdprApplies': true, - 'apiVersion': 2 - } -}, -{ - 'bidderCode': 'appnexusAst', - 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', - 'bidderRequestId': '4d83b8c60d45e7', - 'bids': [ - { - 'bidder': 'appnexusAst', - 'params': { - 'placementId': 10471298 - }, - 'auctionId': 'dd42b870-2072-4b71-8ab7-e7789b14c5ce', - 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, - 'adUnitCode': 'adunit2', - 'transactionId': '4161f09e-7870-4486-b2a6-b4158a327bc4', - 'bidId': '5b7cd5abc6aea3', - 'bidderRequestId': '4d83b8c60d45e7', - } - ], - 'auctionStart': 1683383333809, - 'timeout': 3000, - 'gdprConsent': { - 'consentString': 'consent_hash', - 'gdprApplies': true, - 'apiVersion': 2 - } -} -]; - let bidInterests = [ {'id': 0, 'rate': 50.0, 'suggestion': true}, {'id': 1, 'rate': 12.0, 'suggestion': false}, @@ -212,8 +141,6 @@ describe('oxxionRtdProvider', () => { auctionEnd.bidsReceived = bids; it('call everything', function() { oxxionSubmodule.getBidRequestData(request, null, moduleConfig); - oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[0], moduleConfig); - oxxionSubmodule.onBidResponseEvent(auctionEnd.bidsReceived[1], moduleConfig); }); it('check bid filtering', function() { let requestsList = oxxionSubmodule.getRequestsList(request); @@ -229,27 +156,5 @@ describe('oxxionRtdProvider', () => { expect(filteredBiddderRequests[1]).to.have.property('bids'); expect(filteredBiddderRequests[1].bids.length).to.equal(1); }); - it('check vastImpUrl', function() { - expect(auctionEnd.bidsReceived[0]).to.have.property('vastImpUrl'); - let expectVastImpUrl = 'https://' + moduleConfig.params.domain + '.oxxion.io/analytics/vast_imp?'; - expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(expectVastImpUrl); - expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('https://some.tracking-url.com')); - }); - it('check vastXml', function() { - expect(auctionEnd.bidsReceived[0]).to.have.property('vastXml'); - let vastWrapper = new DOMParser().parseFromString(auctionEnd.bidsReceived[0].vastXml, 'text/xml'); - let impressions = vastWrapper.querySelectorAll('VAST Ad Wrapper Impression'); - expect(impressions.length).to.equal(2); - expect(auctionEnd.bidsReceived[1]).to.have.property('vastXml'); - expect(auctionEnd.bidsReceived[1].adId).to.equal('4b2e1581c0ca1a'); - let vastInline = new DOMParser().parseFromString(auctionEnd.bidsReceived[1].vastXml, 'text/xml'); - let inline = vastInline.querySelectorAll('VAST Ad InLine'); - expect(inline).to.have.lengthOf(1); - let inlineImpressions = vastInline.querySelectorAll('VAST Ad InLine Impression'); - expect(inlineImpressions).to.have.lengthOf.above(0); - }); - it('check cpmIncrement', function() { - expect(auctionEnd.bidsReceived[1].vastImpUrl).to.contain(encodeURI('cpmIncrement=0')); - }); }); }); From 4178c55188908a5679c587b51080a60cc27e991b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 29 Sep 2023 12:22:37 +0000 Subject: [PATCH 062/760] Prebid 8.17.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2f0f242d86..409f17e0f9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.17.0-pre", + "version": "8.17.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 0bffde226f6..a028d89bf47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.17.0-pre", + "version": "8.17.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From fa8f9c1b793cfd3efa433258e96e0405a13d716a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 29 Sep 2023 12:22:37 +0000 Subject: [PATCH 063/760] Increment version to 8.18.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 409f17e0f9c..c4c4f47e470 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.17.0", + "version": "8.18.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a028d89bf47..40b86d8f850 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.17.0", + "version": "8.18.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From c4b0aff9be930c19ed1d0b31c2f1df460741081e Mon Sep 17 00:00:00 2001 From: Sam Ghitelman Date: Fri, 29 Sep 2023 15:07:57 -0400 Subject: [PATCH 064/760] Concert Bid Adapter: add TDID (#10549) * collect EIDs for bid request * add ad slot positioning to payload * RPO-2012: Update local storage name-spacing for c_uid (#8) * Updates c_uid namespacing to be more specific for concert * fixes unit tests * remove console.log * RPO-2012: Add check for shared id (#9) * Adds check for sharedId * Updates cookie name * remove trailing comma * [RPO-3152] Enable Support for GPP Consent (#12) * Adds gpp consent integration to concert bid adapter * Update tests to check for gpp consent string param * removes user sync endpoint and tests * updates comment * cleans up consentAllowsPpid function * comment fix * rename variables for clarity * fixes conditional logic for consent allows function (#13) * [RPO-3262] Update getUid function to check for pubcid and sharedid (#14) * Update getUid function to check for pubcid and sharedid * updates adapter version * [RPO-3405] Add browserLanguage to request meta object * ConcertBidAdapter: Add TDID (#20) * Add tdid to meta object * Fix null handling and add tests --------- Co-authored-by: antoin Co-authored-by: Antoin Co-authored-by: Brett Bloxom <38990705+BrettBlox@users.noreply.github.com> --- modules/concertBidAdapter.js | 9 +++++ test/spec/modules/concertBidAdapter_spec.js | 40 ++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index 7042c895bfb..bc2c4555c54 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -49,6 +49,7 @@ export const spec = { uspConsent: bidderRequest.uspConsent, gdprConsent: bidderRequest.gdprConsent, gppConsent: bidderRequest.gppConsent, + tdid: getTdid(bidderRequest, validBidRequests), } }; @@ -263,3 +264,11 @@ function getOffset(el) { }; } } + +function getTdid(bidderRequest, validBidRequests) { + if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) { + return null; + } + + return deepAccess(validBidRequests[0], 'userId.tdid') || null; +} diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 4a6a4f2ba60..40294deb26d 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -116,7 +116,20 @@ describe('ConcertAdapter', function () { expect(payload).to.have.property('meta'); expect(payload).to.have.property('slots'); - const metaRequiredFields = ['prebidVersion', 'pageUrl', 'screen', 'debug', 'uid', 'optedOut', 'adapterVersion', 'uspConsent', 'gdprConsent', 'gppConsent', 'browserLanguage']; + const metaRequiredFields = [ + 'prebidVersion', + 'pageUrl', + 'screen', + 'debug', + 'uid', + 'optedOut', + 'adapterVersion', + 'uspConsent', + 'gdprConsent', + 'gppConsent', + 'browserLanguage', + 'tdid' + ]; const slotsRequiredFields = ['name', 'bidId', 'transactionId', 'sizes', 'partnerId', 'slotType']; metaRequiredFields.forEach(function(field) { @@ -199,6 +212,31 @@ describe('ConcertAdapter', function () { expect(slot.offsetCoordinates.x).to.equal(100) expect(slot.offsetCoordinates.y).to.equal(0) }) + + it('should not pass along tdid if the user has opted out', function() { + storage.setDataInLocalStorage('c_nap', 'true'); + const request = spec.buildRequests(bidRequests, bidRequest); + const payload = JSON.parse(request.data); + + expect(payload.meta.tdid).to.be.null; + }); + + it('should not pass along tdid if USP consent disallows', function() { + storage.removeDataFromLocalStorage('c_nap'); + const request = spec.buildRequests(bidRequests, { ...bidRequest, uspConsent: '1YY' }); + const payload = JSON.parse(request.data); + + expect(payload.meta.tdid).to.be.null; + }); + + it('should pass along tdid if the user has not opted out', function() { + storage.removeDataFromLocalStorage('c_nap', 'true'); + const tdid = '123abc'; + const bidRequestsWithTdid = [{ ...bidRequests[0], userId: { tdid } }] + const request = spec.buildRequests(bidRequestsWithTdid, bidRequest); + const payload = JSON.parse(request.data); + expect(payload.meta.tdid).to.equal(tdid); + }); }); describe('spec.interpretResponse', function() { From 5a1ebad99a685211db913eb60364d2ba16c4aa7c Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Mon, 2 Oct 2023 08:35:24 -0400 Subject: [PATCH 065/760] Sharethrough Bid Adapter: revise getUserSyncs method (#10556) * Update sharethroughBidAdapter.js * Removing GDPR and GPP query-string params from userSync url creation as our ad-server team gets this info from the bid request sent earlier. * Update Sharethrough bid adapter and spec * Further updating `sharethroughBidAdapter.js` to remove now-unneeded params from `getUserSyncs()` method. * Updating spec file to remove no-longer-necc tests for query params in getUserSync url. --- modules/sharethroughBidAdapter.js | 21 +++------------ .../modules/sharethroughBidAdapter_spec.js | 27 +++---------------- 2 files changed, 8 insertions(+), 40 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index a8beb018b73..3684e793dcb 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -216,26 +216,13 @@ export const sharethroughAdapterSpec = { }); }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, gppConsent) => { + getUserSyncs: (syncOptions, serverResponses) => { const shouldCookieSync = syncOptions.pixelEnabled && deepAccess(serverResponses, '0.body.cookieSyncUrls') !== undefined; - let syncurl = ''; - - // Attaching GDPR Consent Params in UserSync url - if (gdprConsent) { - syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); - syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); - } - if (gppConsent) { - syncurl += '&gpp=' + encodeURIComponent(gppConsent?.gppString); - syncurl += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); - } - - return shouldCookieSync ? serverResponses[0].body.cookieSyncUrls.map((url) => ( - { type: 'image', - url: url + syncurl - })) : []; + return shouldCookieSync + ? serverResponses[0].body.cookieSyncUrls.map((url) => ({ type: 'image', url: url })) + : []; }, // Empty implementation for prebid core to be able to find it diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 68bf14ae9c1..eb0b45dda11 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -409,12 +409,12 @@ describe('sharethrough adapter spec', function () { it('should properly attach GPP information to the request when applicable', () => { bidderRequest.gppConsent = { gppString: 'some-gpp-string', - applicableSections: [3, 5] + applicableSections: [3, 5], }; const openRtbReq = spec.buildRequests(bidRequests, bidderRequest)[0].data; - expect(openRtbReq.regs.gpp).to.equal(bidderRequest.gppConsent.gppString) - expect(openRtbReq.regs.gpp_sid).to.equal(bidderRequest.gppConsent.applicableSections) + expect(openRtbReq.regs.gpp).to.equal(bidderRequest.gppConsent.gppString); + expect(openRtbReq.regs.gpp_sid).to.equal(bidderRequest.gppConsent.applicableSections); }); it('should populate request accordingly when gpp explicitly does not apply', function () { @@ -618,7 +618,7 @@ describe('sharethrough adapter spec', function () { badv: ['domain1.com', 'domain2.com'], regs: { gpp: 'gpp_string', - gpp_sid: [7] + gpp_sid: [7], }, }; @@ -870,25 +870,6 @@ describe('sharethrough adapter spec', function () { const syncArray = spec.getUserSyncs({ pixelEnabled: false }, serverResponses); expect(syncArray).to.be.an('array').that.is.empty; }); - - it('returns GDPR Consent Params in UserSync url', function () { - const syncArray = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, { gdprApplies: true, - consentString: 'consent' }); - expect(syncArray).to.deep.equal([ - { type: 'image', url: 'cookieUrl1&gdpr=1&gdpr_consent=consent' }, - { type: 'image', url: 'cookieUrl2&gdpr=1&gdpr_consent=consent' }, - { type: 'image', url: 'cookieUrl3&gdpr=1&gdpr_consent=consent' }, - ]); - }); - - it('returns GPP Consent Params in UserSync url', function () { - const syncArray = spec.getUserSyncs({ pixelEnabled: true }, serverResponses, {}, {gppString: 'gpp-string', applicableSections: [1, 2]}); - expect(syncArray).to.deep.equal([ - { type: 'image', url: 'cookieUrl1&gdpr=0&gdpr_consent=&gpp=gpp-string&gpp_sid=1%2C2' }, - { type: 'image', url: 'cookieUrl2&gdpr=0&gdpr_consent=&gpp=gpp-string&gpp_sid=1%2C2' }, - { type: 'image', url: 'cookieUrl3&gdpr=0&gdpr_consent=&gpp=gpp-string&gpp_sid=1%2C2' }, - ]); - }); }); }); }); From ef0a7c8ef2704aaca809873161e4145e33f02247 Mon Sep 17 00:00:00 2001 From: Edge226Ads <144908224+Edge226Ads@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:41:28 +0300 Subject: [PATCH 066/760] New adapter Edge226 (#10489) --- modules/edge226BidAdapter.js | 188 ++++++++++ modules/edge226BidAdapter.md | 79 +++++ test/spec/modules/edge226BidAdapter_spec.js | 373 ++++++++++++++++++++ 3 files changed, 640 insertions(+) create mode 100644 modules/edge226BidAdapter.js create mode 100644 modules/edge226BidAdapter.md create mode 100644 test/spec/modules/edge226BidAdapter_spec.js diff --git a/modules/edge226BidAdapter.js b/modules/edge226BidAdapter.js new file mode 100644 index 00000000000..6d1e2466abe --- /dev/null +++ b/modules/edge226BidAdapter.js @@ -0,0 +1,188 @@ +import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'edge226'; +const AD_URL = 'https://ssp.dauup.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + gdpr: bidderRequest.gdprConsent || undefined, + tmax: bidderRequest.timeout + }; + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + } +}; + +registerBidder(spec); diff --git a/modules/edge226BidAdapter.md b/modules/edge226BidAdapter.md new file mode 100644 index 00000000000..b38ff67065f --- /dev/null +++ b/modules/edge226BidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Edge226 Bidder Adapter +Module Type: Edge226 Bidder Adapter +Maintainer: audit@edge226.com +``` + +# Description + +Connects to Edge226 exchange for bids. +Edge226 bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'edge226', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'edge226', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'edge226', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js new file mode 100644 index 00000000000..4819d8d4a4e --- /dev/null +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -0,0 +1,373 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/edge226BidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'edge226' + +describe('Edge226BidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://ssp.dauup.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('string'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 7600b38cebde8d11000916f74d9cc5f5842ebb8e Mon Sep 17 00:00:00 2001 From: redaguermas Date: Tue, 3 Oct 2023 06:53:46 -0700 Subject: [PATCH 067/760] NoBid Bid Adapter : add support for first party user id (#10519) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Delete nobidBidAdapter.js.orig * Delete a * Delete .jsdtscope * Delete org.eclipse.wst.jsdt.ui.superType.container * Delete org.eclipse.wst.jsdt.ui.superType.name * Delete .project * Fix tests * Added support for First Party User ID in NoBid Bid Adapter. --------- Co-authored-by: Reda Guermas --- modules/nobidBidAdapter.js | 123 ++++++++++++++++++++- test/spec/modules/nobidBidAdapter_spec.js | 129 +++++++++++++++------- 2 files changed, 212 insertions(+), 40 deletions(-) diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 68010b32b37..fb052a99695 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -8,12 +8,17 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.3.3'; +window.nobidVersion = '1.4.1'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; window.nobid.bidWonTotal = 0; window.nobid.refreshCount = 0; +window.nobid.firstPartyIds = null; +window.nobid.firstPartyIdEnabled = false; +const FIRST_PARTY_KEY = 'fppcid.nobid.io'; +const FIRST_PARTY_SOURCE_KEY = 'fpid.nobid.io'; +const FIRST_PARTY_DATA_EXPIRY_DAYS = 7 * 24 * 3600 * 1000; function log(msg, obj) { logInfo('-NoBid- ' + msg, obj) } @@ -135,8 +140,10 @@ function nobidBuildRequests(bids, bidderRequest) { src.push({source: eid.source, uids: ids}); } }); + if (window.nobid.firstPartyIds && window.nobid.firstPartyIds) src.push({source: FIRST_PARTY_SOURCE_KEY, uids: [{id: window.nobid.firstPartyIds.ids}]}); return src; } + if (window.nobid.firstPartyIds && window.nobid.firstPartyIds.ids) return [{source: FIRST_PARTY_SOURCE_KEY, uids: [{id: window.nobid.firstPartyIds.ids}]}]; } var state = {}; state['sid'] = siteId; @@ -286,6 +293,12 @@ function nobidInterpretResponse(response, bidRequest) { var setRefreshLimit = function(response) { if (response && typeof response.rlimit !== 'undefined') window.nobid.refreshLimit = response.rlimit; } + var setFirstPartyIdEnabled = function(response) { + if (response && typeof response.fpid !== 'undefined') window.nobid.firstPartyIdEnabled = response.fpid; + if (window?.nobid?.firstPartyIdEnabled) { + nobidFirstPartyData.loadOrCreateFirstPartyData(); + } + } var setUserBlock = function(response) { if (response && typeof response.ublock !== 'undefined') { nobidSetCookie('_ublock', '1', response.ublock); @@ -293,6 +306,7 @@ function nobidInterpretResponse(response, bidRequest) { } setRefreshLimit(response); setUserBlock(response); + setFirstPartyIdEnabled(response); var bidResponses = []; for (var i = 0; response.bids && i < response.bids.length; i++) { var bid = response.bids[i]; @@ -359,6 +373,113 @@ window.addEventListener('message', function (event) { } } }, false); +const nobidFirstPartyData = { + isJson: function (str) { + return str && str.startsWith('{') && str.endsWith('}'); + }, + hasLocalStorage: function () { + try { + return window.localStorage; + } catch (error) { + logWarn('Local storage api disabled', error); + } + return false; + }, + readFirstPartyDataIds: function () { + try { + if (this.hasLocalStorage()) { + const idsStr = window.localStorage.getItem(FIRST_PARTY_SOURCE_KEY); + if (this.isJson(idsStr)) { + const idsObj = JSON.parse(idsStr); + if (idsObj.ts + FIRST_PARTY_DATA_EXPIRY_DAYS < Date.now()) return { pid: idsObj.pid }; // expired? + return idsObj; + } + return null; + } + } catch (error) { + logWarn('Local storage api disabled', error); + } + return null; + }, + loadOrCreateFirstPartyData: function () { + const storeFirstPartyDataIds = function ({ids: theIds, pid: thePid}) { + try { + if (nobidFirstPartyData.hasLocalStorage()) { + window.localStorage.setItem(FIRST_PARTY_SOURCE_KEY, JSON.stringify({ids: theIds, pid: thePid, ts: Date.now()})); + } + } catch (error) { + logWarn('Local storage api disabled', error); + } + }; + const readFirstPartyId = function () { + try { + if (nobidFirstPartyData.hasLocalStorage()) { + const idStr = window.localStorage.getItem(FIRST_PARTY_KEY); + if (nobidFirstPartyData.isJson(idStr)) { + return JSON.parse(idStr); + } + return null; + } + } catch (error) { + logWarn('Local storage api disabled', error); + } + return null; + }; + const storeFirstPartyId = function (theId) { + try { + if (nobidFirstPartyData.hasLocalStorage()) { + window.localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify(theId)); + } + } catch (error) { + logWarn('Local storage api disabled', error); + } + }; + const _loadOrCreateFirstPartyData = function () { + const generateGUID = function () { + let d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); + }; + const ajaxGet = function (ajaxParams, callback) { + const ajax = new XMLHttpRequest(); + ajax.withCredentials = false; + ajax.timeout = ajaxParams.timeout; + ajax.open('GET', ajaxParams.url, true); + ajax.onreadystatechange = function () { + if (this.readyState === XMLHttpRequest.DONE) { + callback(this.response); + } + }; + ajax.send(ajaxParams.data); + }; + let firstPartyIdObj = readFirstPartyId(); + if (!firstPartyIdObj || !firstPartyIdObj.id || !firstPartyIdObj.ts) { + const firstPartyId = generateGUID(); + firstPartyIdObj = {id: firstPartyId, ts: Date.now()}; + storeFirstPartyId(firstPartyIdObj); + } + let firstPartyIds = nobidFirstPartyData.readFirstPartyDataIds(); + if (firstPartyIdObj?.ts && !firstPartyIds?.ids) { + const pid = firstPartyIds?.pid || ''; + const pdate = firstPartyIdObj.ts; + const firstPartyId = firstPartyIdObj.id; + const url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&pt=17&dpn=1&iiqidtype=2&dpi=430542822&iiqpcid=${firstPartyId}&iiqpciddate=${pdate}&pid=${pid}`; + if (window.nobid.firstPartyRequestInProgress) return; + window.nobid.firstPartyRequestInProgress = true; + ajaxGet({ url: url }, function (response) { + response = JSON.parse(response); + if (response?.data) storeFirstPartyDataIds({ ids: response.data, pid: response.pid }); + }); + } + }; + window.nobid.firstPartyIds = this.readFirstPartyDataIds(); + if (window.nobid.firstPartyIdEnabled && !window.nobid.firstPartyIds?.ids) _loadOrCreateFirstPartyData(); + } +}; +window.nobid.firstPartyIds = nobidFirstPartyData.readFirstPartyDataIds(); export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index b1e303bde6e..f2059900a2e 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -57,27 +57,26 @@ describe('Nobid Adapter', function () { 'auctionId': '1d1a030790a475', }; - it('should return true when required params found', function () { + it('should return true when required params found 1', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + it('should return true when required params found 2', function () { + let mybid = Object.assign({}, bid); + delete mybid.params; + mybid.params = { 'siteId': 2 }; - - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(mybid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let mybid = Object.assign({}, bid); + delete mybid.params; + mybid.params = { 'siteId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(mybid)).to.equal(false); }); }); @@ -253,12 +252,12 @@ describe('Nobid Adapter', function () { }); it('sends bid request to site id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.a).to.exist; - expect(payload.a[0].sid).to.equal(2); - expect(payload.a[0].at).to.equal('banner'); - expect(payload.a[0].params.siteId).to.equal(2); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].sid).to.equal(2); + expect(payload.a[0].at).to.equal('banner'); + expect(payload.a[0].params.siteId).to.equal(2); }); it('sends bid request to ad type', function () { @@ -423,6 +422,58 @@ describe('Nobid Adapter', function () { }); }); + describe('First Party ID Test', function () { + const CREATIVE_ID_300x250 = 'CREATIVE-100'; + const ADUNIT_300x250 = 'ADUNIT-1'; + const ADMARKUP_300x250 = 'ADMARKUP-300x250'; + const PRICE_300x250 = 0.51; + const REQUEST_ID = '3db3773286ee59'; + const DEAL_ID = 'deal123'; + let response = { + country: 'US', + ip: '68.83.15.75', + device: 'COMPUTER', + site: 2, + fpid: true, + bids: [ + {id: 1, + bdrid: 101, + divid: ADUNIT_300x250, + creativeid: CREATIVE_ID_300x250, + size: {'w': 300, 'h': 250}, + adm: ADMARKUP_300x250, + price: '' + PRICE_300x250 + } + ] + }; + + it('first party ID', function () { + const bidderRequest = { + bids: [{ + bidId: REQUEST_ID, + adUnitCode: ADUNIT_300x250 + }] + } + const bidRequests = [ + { + 'bidder': 'nobid', + 'params': { + 'siteId': 2 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [[300, 250]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + } + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.method).to.equal('POST'); + spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); + expect(window.nobid.firstPartyIdEnabled).to.equal(true); + }); + }); + describe('isVideoBidRequestValid', function () { let bid = { bidder: 'nobid', @@ -635,12 +686,12 @@ describe('Nobid Adapter', function () { }); it('sends bid request to site id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.a).to.exist; - expect(payload.a[0].sid).to.equal(2); - expect(payload.a[0].at).to.equal('banner'); - expect(payload.a[0].params.siteId).to.equal(2); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].sid).to.equal(2); + expect(payload.a[0].at).to.equal('banner'); + expect(payload.a[0].params.siteId).to.equal(2); }); it('sends bid request to ad type', function () { @@ -939,19 +990,19 @@ describe('Nobid Adapter', function () { auctionId: '1d1a030790a475', coppa: true, schain: { - validation: 'strict', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - name: 'name.com', - hp: 1 - } - ] - } + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + name: 'name.com', + hp: 1 + } + ] + } } } ]; @@ -1001,7 +1052,7 @@ describe('Nobid Adapter', function () { ] }; - it('should ULimit be respected', function () { + it('Limit should be respected', function () { const bidderRequest = { bids: [{ bidId: REQUEST_ID, @@ -1060,8 +1111,8 @@ describe('Nobid Adapter', function () { }); it('should get correct user sync when !iframeEnabled', function () { - let pixel = spec.getUserSyncs({}) - expect(pixel.length).to.equal(0); + let pixel = spec.getUserSyncs({}) + expect(pixel.length).to.equal(0); }); }); From bad2d69f99c8d31074f6b33efacb2494dfa5d2fc Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 3 Oct 2023 07:21:49 -0700 Subject: [PATCH 068/760] Liveintent & UID2 ID systems: share EID configuration (#10559) --- libraries/uid2Eids/uid2Eids.js | 14 ++++++++++++++ modules/liveIntentIdSystem.js | 2 ++ modules/uid2IdSystem.js | 16 ++-------------- 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 libraries/uid2Eids/uid2Eids.js diff --git a/libraries/uid2Eids/uid2Eids.js b/libraries/uid2Eids/uid2Eids.js new file mode 100644 index 00000000000..ce4f4fa3b2a --- /dev/null +++ b/libraries/uid2Eids/uid2Eids.js @@ -0,0 +1,14 @@ +export const UID2_EIDS = { + 'uid2': { + source: 'uidapi.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + } +} diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 8fab266ecce..d80f7a14ddf 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 } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; const DEFAULT_AJAX_TIMEOUT = 5000 const EVENTS_TOPIC = 'pre_lips' @@ -249,6 +250,7 @@ export const liveIntentIdSubmodule = { return { callback: result }; }, eids: { + ...UID2_EIDS, 'lipb': { getValue: function(data) { return data.lipbid; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index a36b24e3ae3..d3b35a5a1aa 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -14,6 +14,7 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here. // eslint-disable-next-line prebid/validate-imports import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js'; +import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; const MODULE_NAME = 'uid2'; const MODULE_REVISION = Uid2CodeVersion; @@ -83,20 +84,7 @@ export const uid2IdSubmodule = { _logInfo(`UID2 getId returned`, result); return result; }, - eids: { - 'uid2': { - source: 'uidapi.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - }, + eids: UID2_EIDS }; function decodeImpl(value) { From 2ad6400532cbf561470f1a7d05a218d91242caeb Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Tue, 3 Oct 2023 07:57:13 -0700 Subject: [PATCH 069/760] GumGum Bid Adapter: Send pubProvidedId in the query string (#10561) * Add logic to send pubProvidedId * Send pubProvidedId in the query string --- modules/gumgumBidAdapter.js | 19 +++++++++++- test/spec/modules/gumgumBidAdapter_spec.js | 35 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 811af29c57b..e14dc7433dc 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -14,6 +14,7 @@ const JCSI = { t: 0, rq: 8, pbv: '$prebid.version$' } const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const TIME_TO_LIVE = 60; const DELAY_REQUEST_TIME = 1800000; // setting to 30 mins +const pubProvidedIdSources = ['dac.co.jp', 'audigent.com', 'id5-sync.com', 'liveramp.com', 'intentiq.com', 'liveintent.com', 'crwdcntrl.net', 'quantcast.com', 'adserver.org', 'yahoo.com'] let invalidRequestIds = {}; let pageViewId = null; @@ -310,7 +311,23 @@ function buildRequests(validBidRequests, bidderRequest) { // ADTS-174 Removed unnecessary checks to fix failing test data.lt = lt; data.to = to; - + function jsoStringifynWithMaxLength(data, maxLength) { + let jsonString = JSON.stringify(data); + if (jsonString.length <= maxLength) { + return jsonString; + } else { + const truncatedData = data.slice(0, Math.floor(data.length * (maxLength / jsonString.length))); + jsonString = JSON.stringify(truncatedData); + return jsonString; + } + } + // Send filtered pubProvidedId's + if (userId && userId.pubProvidedId) { + let filteredData = userId.pubProvidedId.filter(item => pubProvidedIdSources.includes(item.source)); + let maxLength = 1800; // replace this with your desired maximum length + let truncatedJsonString = jsoStringifynWithMaxLength(filteredData, maxLength); + data.pubProvidedId = truncatedJsonString + } // ADJS-1286 Read id5 id linktype field if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { data.id5Id = userId.id5id.uid || null diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 2b12547ca8f..56c89d329dc 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -121,6 +121,30 @@ describe('gumgumAdapter', function () { } } }, + pubProvidedId: [ + { + uids: [ + { + ext: { + stype: 'ppuid', + }, + id: 'aac4504f-ef89-401b-a891-ada59db44336', + }, + ], + source: 'sonobi.com', + }, + { + uids: [ + { + ext: { + stype: 'ppuid', + }, + id: 'y-zqTHmW9E2uG3jEETC6i6BjGcMhPXld2F~A', + }, + ], + source: 'aol.com', + }, + ], adUnitCode: 'adunit-code', sizes: sizesArray, bidId: '30b31c1838de1e', @@ -168,6 +192,11 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); }); + it('should set pubProvidedId if the uid and pubProvidedId are available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(bidRequests[0].userId.pubProvidedId)); + }); it('should set id5Id and id5IdLinkType if the uid and linkType are available', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; @@ -488,6 +517,12 @@ describe('gumgumAdapter', function () { expect(request.data).to.not.include.any.keys('eAdBuyId'); expect(request.data).to.not.include.any.keys('adBuyId'); }); + it('should set pubProvidedId if the uid and pubProvidedId are available', function () { + const request = { ...bidRequests[0] }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(bidRequests[0].userId.pubProvidedId)); + }); + it('should add gdpr consent parameters if gdprConsent is present', function () { const gdprConsent = { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', gdprApplies: true }; const fakeBidRequest = { gdprConsent: gdprConsent }; From 2905b4c21c0330e385f12c8f74d8c42e72aa8f3c Mon Sep 17 00:00:00 2001 From: wsusrasp <106743463+wsusrasp@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:57:50 +0200 Subject: [PATCH 070/760] RAS Bid Adapter: fledge support (#10477) * add fledge support to rasBidAdapter * add unit test, add params to auction signals --- modules/rasBidAdapter.js | 52 +++++++++++++++++++++++-- test/spec/modules/rasBidAdapter_spec.js | 52 +++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/modules/rasBidAdapter.js b/modules/rasBidAdapter.js index 801457aa552..4e93f2aa8eb 100644 --- a/modules/rasBidAdapter.js +++ b/modules/rasBidAdapter.js @@ -130,6 +130,36 @@ const getGdprParams = (bidderRequest) => { return queryString; }; +const parseAuctionConfigs = (serverResponse, bidRequest) => { + if (isEmpty(bidRequest)) { + return null; + } + const auctionConfigs = []; + const gctx = serverResponse && serverResponse.body?.gctx; + + bidRequest.bidIds.filter(bid => bid.fledgeEnabled).forEach((bid) => { + auctionConfigs.push({ + 'bidId': bid.bidId, + 'config': { + 'seller': 'https://csr.onet.pl', + 'decisionLogicUrl': `https://csr.onet.pl/${encodeURIComponent(bid.params.network)}/v1/protected-audience-api/decision-logic.js`, + 'interestGroupBuyers': ['https://csr.onet.pl'], + 'auctionSignals': { + 'params': bid.params, + 'sizes': bid.sizes, + 'gctx': gctx + } + } + }); + }); + + if (auctionConfigs.length === 0) { + return null; + } else { + return auctionConfigs; + } +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], @@ -146,8 +176,16 @@ export const spec = { const slotsQuery = getSlots(bidRequests); const contextQuery = getContextParams(bidRequests, bidderRequest); const gdprQuery = getGdprParams(bidderRequest); - const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, bidId: bid.bidId })); + const fledgeEligible = Boolean(bidderRequest && bidderRequest.fledgeEnabled); const network = bidRequests[0].params.network; + const bidIds = bidRequests.map((bid) => ({ + slot: bid.params.slot, + bidId: bid.bidId, + sizes: getAdUnitSizes(bid), + params: bid.params, + fledgeEnabled: fledgeEligible + })); + return [{ method: 'GET', url: getEndpoint(network) + contextQuery + slotsQuery + gdprQuery, @@ -157,10 +195,16 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { const response = serverResponse.body; - if (!response || !response.ads || response.ads.length === 0) { - return []; + + const fledgeAuctionConfigs = parseAuctionConfigs(serverResponse, bidRequest); + const bids = (!response || !response.ads || response.ads.length === 0) ? [] : response.ads.map(buildBid).filter((bid) => !isEmpty(bid)); + + if (fledgeAuctionConfigs) { + // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. + return {bids, fledgeAuctionConfigs}; + } else { + return bids; } - return response.ads.map(buildBid).filter((bid) => !isEmpty(bid)); } }; diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/rasBidAdapter_spec.js index bfa72a2510e..719e15ad695 100644 --- a/test/spec/modules/rasBidAdapter_spec.js +++ b/test/spec/modules/rasBidAdapter_spec.js @@ -1,6 +1,7 @@ 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&'; @@ -192,5 +193,56 @@ describe('rasBidAdapter', function () { const resp = spec.interpretResponse({ body: res }, {}); expect(resp).to.deep.equal([]); }); + + it('should generate auctionConfig when fledge is enabled', function () { + let bidRequest = { + method: 'GET', + url: 'https://example.com', + bidIds: [{ + slot: 'top', + bidId: '123', + network: 'testnetwork', + sizes: ['300x250'], + params: { + site: 'testsite', + area: 'testarea', + network: 'testnetwork' + }, + fledgeEnabled: true + }, + { + slot: 'top', + bidId: '456', + network: 'testnetwork', + sizes: ['300x250'], + params: { + site: 'testsite', + area: 'testarea', + network: 'testnetwork' + }, + fledgeEnabled: false + }] + }; + + let auctionConfigs = [{ + 'bidId': '123', + 'config': { + 'seller': 'https://csr.onet.pl', + 'decisionLogicUrl': 'https://csr.onet.pl/testnetwork/v1/protected-audience-api/decision-logic.js', + 'interestGroupBuyers': ['https://csr.onet.pl'], + 'auctionSignals': { + 'params': { + site: 'testsite', + area: 'testarea', + network: 'testnetwork' + }, + 'sizes': ['300x250'], + 'gctx': '1234567890' + } + } + }]; + const resp = spec.interpretResponse({body: {gctx: '1234567890'}}, bidRequest); + expect(resp).to.deep.equal({bids: [], fledgeAuctionConfigs: auctionConfigs}); + }); }); }); From a2fc26a34447fa0cbd5ba30cbc4f0ef9fe684155 Mon Sep 17 00:00:00 2001 From: KiviAds <117356365+KiviAds@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:08:39 +0200 Subject: [PATCH 071/760] Lm_Kiviads Bid Adapter : initial adapter release (#10415) * new adapter - KiviAds * add gpp, change tests, use bidderRequest instead of config.getConfig() * Update kiviadsBidAdapter_spec.js * add new bid adapter * Update lm_kiviadsBidAdapter: change placement -> pid according to server-side parameters * fix aliases --------- Co-authored-by: Patrick McCann Co-authored-by: Chucky-choo --- modules/lm_kiviadsBidAdapter.js | 206 ++++++++ modules/lm_kiviadsBidAdapter.md | 54 +++ .../spec/modules/lm_kiviadsBidAdapter_spec.js | 455 ++++++++++++++++++ 3 files changed, 715 insertions(+) create mode 100644 modules/lm_kiviadsBidAdapter.js create mode 100644 modules/lm_kiviadsBidAdapter.md create mode 100644 test/spec/modules/lm_kiviadsBidAdapter_spec.js diff --git a/modules/lm_kiviadsBidAdapter.js b/modules/lm_kiviadsBidAdapter.js new file mode 100644 index 00000000000..010d453e0aa --- /dev/null +++ b/modules/lm_kiviadsBidAdapter.js @@ -0,0 +1,206 @@ +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {getAdUnitSizes, parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; + +const CUR = 'USD'; +const BIDDER_CODE = 'lm_kiviads'; +const ENDPOINT = 'https://pbjs.kiviads.live'; + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(req) { + if (req && typeof req.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { + logError('Env or pid is not present in bidder params'); + return false; + } + + if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(validBidRequests, bidderRequest) { + const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; + const requests = validBidRequests.map(req => { + const request = {}; + request.bidId = req.bidId; + request.banner = deepAccess(req, 'mediaTypes.banner'); + request.auctionId = req.ortb2?.source?.tid; + request.transactionId = req.ortb2Imp?.ext?.tid; + request.sizes = parseSizesInput(getAdUnitSizes(req)); + request.schain = req.schain; + request.location = { + page: refererInfo.page, + location: refererInfo.location, + domain: refererInfo.domain, + whost: window.location.host, + ref: refererInfo.ref, + isAmp: refererInfo.isAmp + }; + request.device = { + ua: navigator.userAgent, + lang: navigator.language + }; + request.env = { + env: req.params.env, + pid: req.params.pid + }; + request.ortb2 = req.ortb2; + request.ortb2Imp = req.ortb2Imp; + request.tz = new Date().getTimezoneOffset(); + request.ext = req.params.ext; + request.bc = req.bidRequestsCount; + request.floor = getBidFloor(req); + + if (req.userIdAsEids && req.userIdAsEids.length !== 0) { + request.userEids = req.userIdAsEids; + } else { + request.userEids = []; + } + if (gdprConsent.gdprApplies) { + request.gdprApplies = Number(gdprConsent.gdprApplies); + request.consentString = gdprConsent.consentString; + } else { + request.gdprApplies = 0; + request.consentString = ''; + } + if (uspConsent) { + request.usPrivacy = uspConsent; + } else { + request.usPrivacy = ''; + } + if (config.getConfig('coppa')) { + request.coppa = 1; + } else { + request.coppa = 0; + } + + const video = deepAccess(req, 'mediaTypes.video'); + if (video) { + request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); + request.video = video; + } + + return request; + }); + + return { + method: 'POST', + url: ENDPOINT + '/bid', + data: JSON.stringify(requests), + withCredentials: true, + bidderRequest, + options: { + contentType: 'application/json', + } + }; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse(serverResponse, {bidderRequest}) { + const response = []; + if (!isArray(deepAccess(serverResponse, 'body.data'))) { + return response; + } + + serverResponse.body.data.forEach(serverBid => { + const bid = { + requestId: bidderRequest.bidId, + dealId: bidderRequest.dealId || null, + ...serverBid + }; + response.push(bid); + }); + + return response; +} + +/** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ +function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); + + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + + pixels.forEach(pixel => { + const [type, url] = pixel; + const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; + if (type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push(sync) + } else if (type === 'image' && syncOptions.pixelEnabled) { + syncs.push(sync) + } + }); + } + + return syncs; +} + +/** + * Get valid floor value from getFloor fuction. + * + * @param {Object} bid Current bid request. + * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. + */ +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: CUR, + mediaType: '*', + size: '*' + }); + + if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { + return floor.floor; + } + + return null; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['kivi'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/lm_kiviadsBidAdapter.md b/modules/lm_kiviadsBidAdapter.md new file mode 100644 index 00000000000..fc1b05d1ef7 --- /dev/null +++ b/modules/lm_kiviadsBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: lm_kiviads Bidder Adapter +Module Type: lm_kiviads Bidder Adapter +Maintainer: pavlo@xe.works +``` + +# Description + +Module that connects to kiviads.com demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'lm_kiviads', + params: { + env: 'lm_kiviads', + pid: '40', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'lm_kiviads', + params: { + env: 'lm_kiviads', + pid: '40', + ext: {} + } + }] + } +]; +``` diff --git a/test/spec/modules/lm_kiviadsBidAdapter_spec.js b/test/spec/modules/lm_kiviadsBidAdapter_spec.js new file mode 100644 index 00000000000..68ac73289cd --- /dev/null +++ b/test/spec/modules/lm_kiviadsBidAdapter_spec.js @@ -0,0 +1,455 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec, getBidFloor} from 'modules/lm_kiviadsBidAdapter.js'; +import {deepClone} from 'src/utils'; + +const ENDPOINT = 'https://pbjs.kiviads.live'; + +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'lm_kiviads', + params: { + env: 'lm_kiviads', + pid: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; +describe('lm_kiviadsBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required env param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.env; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('coppa').and.to.equal(0); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + env: 'lm_kiviads', + pid: '40' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with coppa 1', function () { + config.setConfig({ + coppa: true + }); + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('coppa').and.equals(1); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['lm_kiviads'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['lm_kiviads']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequestVideo}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}) From b7ff9fe201cce20e8e82dfebe9981d1917e888f6 Mon Sep 17 00:00:00 2001 From: Mark Kuhar Date: Tue, 3 Oct 2023 19:42:30 +0200 Subject: [PATCH 072/760] Outbrain adapter: Read OB user token from local storage (#10382) * read ob user token from local storage * lint fix * add test * test improvement --- modules/outbrainBidAdapter.js | 9 +++++++ test/spec/modules/outbrainBidAdapter_spec.js | 25 +++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 0637d680912..b4f74872082 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; import {OUTSTREAM} from '../src/video.js'; import {_map, deepAccess, deepSetValue, isArray, logWarn, replaceAuctionPrice} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; @@ -23,6 +24,9 @@ const NATIVE_PARAMS = { cta: { id: 1, type: 12, name: 'data' } }; const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const OB_USER_TOKEN_KEY = 'OB-USER-TOKEN'; + +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -130,6 +134,11 @@ export const spec = { request.test = 1; } + const obUserToken = storage.getDataFromLocalStorage(OB_USER_TOKEN_KEY) + if (obUserToken) { + deepSetValue(request, 'user.ext.obusertoken', obUserToken) + } + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString) deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1) diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index d8690aeb6a5..ba394a68675 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/outbrainBidAdapter.js'; +import { spec, storage } from 'modules/outbrainBidAdapter.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr'; @@ -213,15 +213,18 @@ describe('Outbrain Adapter', function () { }) describe('buildRequests', function () { + let getDataFromLocalStorageStub; + before(() => { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') config.setConfig({ outbrain: { bidderUrl: 'https://bidder-url.com', } - } - ) + }) }) after(() => { + getDataFromLocalStorageStub.restore() config.resetConfig() }) @@ -522,6 +525,22 @@ describe('Outbrain Adapter', function () { ]); }); + it('should pass OB user token', function () { + getDataFromLocalStorageStub.returns('12345'); + + let bidRequest = { + bidId: 'bidId', + params: {}, + ...commonBidRequest, + }; + + let res = spec.buildRequests([bidRequest], commonBidderRequest); + const resData = JSON.parse(res.data) + expect(resData.user.ext.obusertoken).to.equal('12345') + expect(getDataFromLocalStorageStub.called).to.be.true; + sinon.assert.calledWith(getDataFromLocalStorageStub, 'OB-USER-TOKEN'); + }); + it('should pass bidfloor', function () { const bidRequest = { ...commonBidRequest, From dea49c275c2931cd689268ac7d72c6356fbaacbe Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 3 Oct 2023 12:23:19 -0600 Subject: [PATCH 073/760] Fix getAdUnitSize location (#10563) --- modules/lm_kiviadsBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/lm_kiviadsBidAdapter.js b/modules/lm_kiviadsBidAdapter.js index 010d453e0aa..9ba26052727 100644 --- a/modules/lm_kiviadsBidAdapter.js +++ b/modules/lm_kiviadsBidAdapter.js @@ -1,7 +1,8 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getAdUnitSizes, parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; +import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; const CUR = 'USD'; const BIDDER_CODE = 'lm_kiviads'; From 8cb373cc52d64741b252675dd8a7f3fe2ce7bbca Mon Sep 17 00:00:00 2001 From: Timothy Ace Date: Wed, 4 Oct 2023 07:36:24 -0400 Subject: [PATCH 074/760] imds Bid Adapter : fix incorrectly named user sync property (#10562) * imds: Rename usersync from "pixel" to correct name of "image". * imds: Update documentation for "DFP Video Creative" to match prebid.github.io. * imds: Test modifications for renamed pixel->image usersync * imds: Remove warning for valid no-bid empty body 204 responses. --------- Co-authored-by: Timothy M. Ace --- modules/imdsBidAdapter.js | 3 +-- modules/imdsBidAdapter.md | 8 ++++---- test/spec/modules/imdsBidAdapter_spec.js | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/imdsBidAdapter.js b/modules/imdsBidAdapter.js index 122662feb8a..4cad1d614c5 100644 --- a/modules/imdsBidAdapter.js +++ b/modules/imdsBidAdapter.js @@ -224,7 +224,6 @@ export const spec = { }; if (!serverResponse.body || typeof serverResponse.body != 'object') { - logWarn('IMDS: server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); return; } const {id, seatbid: seatbids} = serverResponse.body; @@ -324,7 +323,7 @@ export const spec = { }); } else if (syncOptions.pixelEnabled) { syncs.push({ - type: 'pixel', + type: 'image', url: `${USER_SYNC_PIXEL_URL}?srv=cs&${queryParams.join('&')}` }); } diff --git a/modules/imdsBidAdapter.md b/modules/imdsBidAdapter.md index 15fb407e7ef..2a50868d726 100644 --- a/modules/imdsBidAdapter.md +++ b/modules/imdsBidAdapter.md @@ -11,11 +11,11 @@ Maintainer: eng-demand@imds.tv The iMedia Digital Services adapter requires setup and approval from iMedia Digital Services. Please reach out to your account manager for more information. -### DFP Video Creative -To use video, setup a `VAST redirect` creative within Google AdManager (DFP) with the following VAST tag URL: +### Google Ad Manager Video Creative +To use video, setup a `VAST redirect` creative within Google Ad Manager with the following VAST tag URL: -``` -https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_cache_id_synacorm%%&AUCTION_PRICE=%%PATTERN:hb_pb_synacormedia%% +```text +https://track.technoratimedia.com/openrtb/tags?ID=%%PATTERN:hb_uuid_imds%%&AUCTION_PRICE=%%PATTERN:hb_pb_imds%% ``` # Test Parameters diff --git a/test/spec/modules/imdsBidAdapter_spec.js b/test/spec/modules/imdsBidAdapter_spec.js index 7d808a2528f..b71a0bc51d9 100644 --- a/test/spec/modules/imdsBidAdapter_spec.js +++ b/test/spec/modules/imdsBidAdapter_spec.js @@ -1362,17 +1362,17 @@ describe('imdsBidAdapter ', function () { expect(usersyncs[0].url).to.contain('https://ad-cdn.technoratimedia.com/html/usersync.html'); }); - it('should return a pixel usersync when pixels is enabled', function () { + it('should return an image usersync when pixels are enabled', function () { let usersyncs = spec.getUserSyncs({ pixelEnabled: true }, null); expect(usersyncs).to.be.an('array').with.lengthOf(1); - expect(usersyncs[0]).to.have.property('type', 'pixel'); + expect(usersyncs[0]).to.have.property('type', 'image'); expect(usersyncs[0]).to.have.property('url'); expect(usersyncs[0].url).to.contain('https://sync.technoratimedia.com/services'); }); - it('should return an iframe usersync when both iframe and pixels is enabled', function () { + it('should return an iframe usersync when both iframe and pixel are enabled', function () { let usersyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true From 1155fb9798b37b8efec8f370f9dbe64c5c5852ae Mon Sep 17 00:00:00 2001 From: PrecisoSRL <134591565+PrecisoSRL@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:09:20 +0530 Subject: [PATCH 075/760] Preciso BidAdapter : modified BidderRequest params (#10569) * New bid adapter : Preciso * Added deafualt statement in interpretNativeAd * removed trailing space * Added Protected Audience API (FLEDGE) support * updated with getConfig method f pr pulling ortb2 data * updated the precisoBidAdapter * updated the test cases * changed user sync url and also fixed the CORS error * removed test params from hello_world.html and 204 error fix * changed responses fields in the precisoBidAdapter.js * error fix * removed test params * reverted the test params * modified the request * removed the empty line * removed blank line in precisoBidAdapter_spec.js * precisoIdSystem module created * PrecisoIdSystem.spec.js cretaed for testing the PrecisoIdSystem.js * removed the test params from hello_world.html * error fix * error fix * error fix * Changes in bid Request * removed the unwanted space --------- Co-authored-by: Nikhil Gopal Chennissery --- integrationExamples/gpt/hello_world.html | 30 ++++---- modules/precisoBidAdapter.js | 78 +++++++++++++++++---- test/spec/modules/precisoBidAdapter_spec.js | 30 +++++--- 3 files changed, 100 insertions(+), 38 deletions(-) diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 47ba5b8f18a..03a2356f0ef 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -8,6 +8,7 @@ --> + @@ -19,9 +20,10 @@ code: 'div-gpt-ad-1460505748561-0', mediaTypes: { banner: { - sizes: [[300, 250], [300,600]], + sizes: [[300, 250]], } }, + // Replace this object to test a new Adapter! bids: [{ bidder: 'appnexus', @@ -40,12 +42,13 @@ - +

Prebid.js Test

+
Div-1
+
+ +
+ \ No newline at end of file diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index c7f7db56fd4..9125f6f3911 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, isFn, deepAccess } from '../src/utils.js'; +import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -6,9 +6,10 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'preciso'; const AD_URL = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; -const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=preciso_srl'; +const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; const GVLID = 874; +let userId = 'NA'; export const spec = { code: BIDDER_CODE, @@ -22,9 +23,17 @@ export const spec = { buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - + // userId = validBidRequests[0].userId.pubcid; let winTop = window; let location; + var offset = new Date().getTimezoneOffset(); + logInfo('timezone ' + offset); + var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + logInfo('location test' + city) + + const countryCode = getCountryCodeByTimezone(city); + logInfo(`The country code for ${city} is ${countryCode}`); + // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin try { location = new URL(bidderRequest.refererInfo.page) @@ -34,20 +43,18 @@ export const spec = { logMessage(e); }; - let site = { - 'domain': location.domain || '', - 'page': location || '' - } - let request = { - id: '123456678', + id: validBidRequests[0].bidderRequestId, + imp: validBidRequests.map(request => { - const { bidId, sizes, mediaType } = request + const { bidId, sizes, mediaType, ortb2 } = request const item = { id: bidId, region: request.params.region, traffic: mediaType, - bidFloor: getBidFloor(request) + bidFloor: getBidFloor(request), + ortb2: ortb2 + } if (request.mediaTypes.banner) { @@ -62,17 +69,28 @@ export const spec = { item.schain = request.schain; } + if (request.floorData) { + item.bidFloor = request.floorData.floorMin; + } return item }), - - 'site': site, + auctionId: validBidRequests[0].auctionId, 'deviceWidth': winTop.screen.width, 'deviceHeight': winTop.screen.height, 'language': (navigator && navigator.language) ? navigator.language : '', - 'secure': 1, + geo: navigator.geolocation.getCurrentPosition(position => { + const { latitude, longitude } = position.coords; + return { + latitude: latitude, + longitude: longitude + } + // Show a map centered at latitude / longitude. + }) || { utcoffset: new Date().getTimezoneOffset() }, + city: city, 'host': location.host, 'page': location.pathname, 'coppa': config.getConfig('coppa') === true ? 1 : 0 + // userId: validBidRequests[0].userId }; request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) @@ -127,10 +145,13 @@ export const spec = { let syncs = []; let { gdprApplies, consentString = '' } = gdprConsent; + if (serverResponses.length > 0) { + logInfo('preciso bidadapter getusersync serverResponses:' + serverResponses.toString); + } if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=4` + url: `${URL_SYNC}id=${userId}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=4` }); } else { syncs.push({ @@ -144,6 +165,33 @@ export const spec = { }; +function getCountryCodeByTimezone(city) { + try { + const now = new Date(); + const options = { + timeZone: city, + timeZoneName: 'long', + }; + const [timeZoneName] = new Intl.DateTimeFormat('en-US', options) + .formatToParts(now) + .filter((part) => part.type === 'timeZoneName'); + + if (timeZoneName) { + // Extract the country code from the timezone name + const parts = timeZoneName.value.split('-'); + if (parts.length >= 2) { + return parts[1]; + } + } + } catch (error) { + // Handle errors, such as an invalid timezone city + logInfo(error); + } + + // Handle the case where the city is not found or an error occurred + return 'Unknown'; +} + function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return deepAccess(bid, 'params.bidFloor', 0); diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 1a7e24d64cb..78a1615a02e 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -22,10 +22,15 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'prebid-eu' - } + }, + userId: { + pubcid: '12355454test' + + }, + geo: 'NA', + city: 'Asia,delhi' }; describe('isBidRequestValid', function () { @@ -54,7 +59,7 @@ describe('PrecisoAdapter', 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.be.an('object'); // expect(data).to.have.all.keys('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); @@ -62,15 +67,20 @@ describe('PrecisoAdapter', function () { expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).to.be.a('number'); expect(data.language).to.be.a('string'); - expect(data.secure).to.be.within(0, 1); + // expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); + + expect(data.city).to.be.a('string'); + expect(data.geo).to.be.a('object'); + // expect(data.userId).to.be.a('string'); + // expect(data.imp).to.be.a('object'); }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); - let data = serverRequest.data; - expect(data.imp).to.be.an('array').that.is.empty; - }); + // it('Returns empty data if no valid requests are passed', function () { + /// serverRequest = spec.buildRequests([]); + // let data = serverRequest.data; + // expect(data.imp).to.be.an('array').that.is.empty; + // }); }); describe('with COPPA', function () { @@ -135,7 +145,7 @@ describe('PrecisoAdapter', function () { }) }) describe('getUserSyncs', function () { - const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=preciso_srl&gdpr=0&gdpr_consent=&us_privacy=&t=4'; + const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=NA&gdpr=0&gdpr_consent=&us_privacy=&t=4'; const syncOptions = { iframeEnabled: true }; From 5cb4bcbb545705ba20aa8e9a461c538b10638418 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 4 Oct 2023 06:35:08 -0700 Subject: [PATCH 076/760] Core & currency module: fix bug where bid responses are collected after auction ends (#10558) * Core & currency module: fix bug where bid responses are collected after auction ends * hook fn does not need to be a hook --- modules/currency.js | 23 +++--- src/auction.js | 41 +++------- test/spec/auctionmanager_spec.js | 120 +++++++---------------------- test/spec/modules/currency_spec.js | 26 +++---- 4 files changed, 60 insertions(+), 150 deletions(-) diff --git a/modules/currency.js b/modules/currency.js index 3da0cfe73e8..0ae8c8ad0a6 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -22,14 +22,7 @@ export var currencyRates = {}; var bidderCurrencyDefault = {}; var defaultRates; -export const ready = (() => { - let ctl; - function reset() { - ctl = defer(); - } - reset(); - return {done: () => ctl.resolve(), reset, promise: () => ctl.promise} -})(); +export let responseReady = defer(); /** * Configuration function for currency @@ -137,6 +130,7 @@ function initCurrency(url) { // Adding conversion function to prebid global for external module and on page use getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); getHook('addBidResponse').before(addBidResponseHook, 100); + getHook('responsesReady').before(responsesReadyHook); // call for the file if we haven't already if (needToCallForCurrencyFile) { @@ -150,19 +144,15 @@ function initCurrency(url) { conversionCache = {}; currencyRatesLoaded = true; processBidResponseQueue(); - ready.done(); } catch (e) { errorSettingsRates('Failed to parse currencyRates response: ' + response); } }, error: function (...args) { errorSettingsRates(...args); - ready.done(); } } ); - } else { - ready.done(); } } @@ -170,6 +160,7 @@ function resetCurrency() { logInfo('Uninstalling addBidResponse decorator for currency module', arguments); getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); delete getGlobal().convertCurrency; adServerCurrency = 'USD'; @@ -179,6 +170,11 @@ function resetCurrency() { needToCallForCurrencyFile = true; currencyRates = {}; bidderCurrencyDefault = {}; + responseReady = defer(); +} + +function responsesReadyHook(next, ready) { + next(ready.then(() => responseReady.promise)); } export const addBidResponseHook = timedBidResponseHook('currency', function addBidResponseHook(fn, adUnitCode, bid, reject) { @@ -215,8 +211,6 @@ export const addBidResponseHook = timedBidResponseHook('currency', function addB bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid, reject])); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); - } else { - fn.untimed.bail(ready.promise()); } }); @@ -224,6 +218,7 @@ function processBidResponseQueue() { while (bidResponseQueue.length > 0) { (bidResponseQueue.shift())(); } + responseReady.resolve() } function wrapFunction(fn, context, params) { diff --git a/src/auction.js b/src/auction.js index 4bdd590f7ea..df1b2cdef55 100644 --- a/src/auction.js +++ b/src/auction.js @@ -410,6 +410,14 @@ export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { this.dispatch.call(null, adUnitCode, bid); }, 'addBidResponse'); +/** + * Delay hook for adapter responses. + * + * `ready` is a promise; auctions wait for it to resolve before closing. Modules can hook into this + * to delay the end of auctions while they perform initialization that does not need to delay their start. + */ +export const responsesReady = hook('sync', (ready) => ready, 'responsesReady'); + export const addBidderRequests = hook('sync', function(bidderRequests) { this.dispatch.call(this.context, bidderRequests); }, 'addBidderRequests'); @@ -425,32 +433,6 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM let allAdapterCalledDone = false; let bidderRequestsDone = new Set(); let bidResponseMap = {}; - const ready = {}; - - function waitFor(requestId, result) { - if (ready[requestId] == null) { - ready[requestId] = GreedyPromise.resolve(); - } - ready[requestId] = ready[requestId].then(() => GreedyPromise.resolve(result).catch(() => {})) - } - - function guard(bidderRequest, fn) { - let timeout = bidderRequest.timeout; - if (timeout == null || timeout > auctionInstance.getTimeout()) { - timeout = auctionInstance.getTimeout(); - } - const timeRemaining = auctionInstance.getAuctionStart() + timeout - Date.now(); - const wait = ready[bidderRequest.bidderRequestId]; - const orphanWait = ready['']; // also wait for "orphan" responses that are not associated with any request - if ((wait != null || orphanWait != null) && timeRemaining > 0) { - GreedyPromise.race([ - GreedyPromise.timeout(timeRemaining), - GreedyPromise.resolve(orphanWait).then(() => wait) - ]).then(fn); - } else { - fn(); - } - } function afterBidAdded() { outstandingBidsAdded--; @@ -525,8 +507,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM return { addBidResponse: (function () { function addBid(adUnitCode, bid) { - const bidderRequest = index.getBidderRequest(bid); - waitFor((bidderRequest && bidderRequest.bidderRequestId) || '', addBidResponse.call({ + addBidResponse.call({ dispatch: acceptBidResponse, }, adUnitCode, bid, (() => { let rejected = false; @@ -536,13 +517,13 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM rejected = true; } } - })())); + })()) } addBid.reject = rejectBidResponse; return addBid; })(), adapterDone: function () { - guard(this, adapterDone.bind(this)) + responsesReady(GreedyPromise.resolve()).finally(() => adapterDone.call(this)); } } } diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index c18f2df9466..b43f630752b 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -5,7 +5,7 @@ import { adjustBids, getMediaTypeGranularity, getPriceByGranularity, - addBidResponse, resetAuctionState + addBidResponse, resetAuctionState, responsesReady } from 'src/auction.js'; import CONSTANTS from 'src/constants.json'; import * as auctionModule from 'src/auction.js'; @@ -1803,116 +1803,54 @@ describe('auctionmanager.js', function () { sinon.assert.calledWith(auction.addBidReceived, sinon.match({cpm: 1.23})); }) - describe('when addBidResponse hook returns promises', () => { - let resolvers, callbacks, bids; + describe('when responsesReady defers', () => { + let resolve, reject, promise, callbacks, bids; - function hook(next, ...args) { - next.bail(new Promise((resolve, reject) => { - resolvers.resolve.push(resolve); - resolvers.reject.push(reject); - }).finally(() => next(...args))); + function hook(next, ready) { + next(ready.then(() => promise)); } - function invokeCallbacks() { - bids.forEach((bid) => callbacks.addBidResponse(ADUNIT_CODE, bid)); - bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest)); - } + before(() => { + responsesReady.before(hook); + }); - function delay(ms = 0) { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }); - } + after(() => { + responsesReady.getHooks({hook}).remove(); + }); beforeEach(() => { + // eslint-disable-next-line promise/param-names + promise = new Promise((rs, rj) => { + resolve = rs; + reject = rj; + }); bids = [ mockBid({bidderCode: BIDDER_CODE1}), mockBid({bidderCode: BIDDER_CODE}) ] bidRequests = bids.map((b) => mockBidRequest(b)); - resolvers = {resolve: [], reject: []}; - addBidResponse.before(hook); callbacks = auctionCallbacks(doneSpy, auction); Object.assign(auction, { addNoBid: sinon.spy() }); }); - afterEach(() => { - addBidResponse.getHooks({hook: hook}).remove(); - }); - - it('should wait for bids without a request bids before calling auctionDone', () => { - callbacks.addBidResponse(ADUNIT_CODE, Object.assign(mockBid(), {requestId: null})); - invokeCallbacks(); - resolvers.resolve.slice(1, 3).forEach((fn) => fn()); - return delay().then(() => { - expect(doneSpy.called).to.be.false; - resolvers.resolve[0](); - return delay(); - }).then(() => { - expect(doneSpy.called).to.be.true; - }); - }); - Object.entries({ - 'all succeed': ['resolve', 'resolve'], - 'some fail': ['resolve', 'reject'], - 'all fail': ['reject', 'reject'] - }).forEach(([test, results]) => { - describe(`(and ${test})`, () => { - it('should wait for them to complete before calling auctionDone', () => { - invokeCallbacks(); - return delay().then(() => { - expect(doneSpy.called).to.be.false; - expect(auction.addNoBid.called).to.be.false; - resolvers[results[0]][0](); - return delay(); - }).then(() => { - expect(doneSpy.called).to.be.false; - expect(auction.addNoBid.called).to.be.false; - resolvers[results[1]][1](); - return delay(); - }).then(() => { - expect(doneSpy.called).to.be.true; - }); - }); + 'resolve': () => resolve(), + 'reject': () => reject(), + }).forEach(([t, resolver]) => { + it(`should wait for responsesReady to ${t} before calling auctionDone`, (done) => { + bidRequests.forEach(bidRequest => callbacks.adapterDone.call(bidRequest)); + setTimeout(() => { + sinon.assert.notCalled(doneSpy); + resolver(); + setTimeout(() => { + sinon.assert.called(doneSpy); + done(); + }) + }) }); }); - - Object.entries({ - bidder: (timeout) => { - bidRequests.forEach((r) => r.timeout = timeout); - auction.getTimeout = () => timeout + 10000 - }, - auction: (timeout) => { - auction.getTimeout = () => timeout; - bidRequests.forEach((r) => r.timeout = timeout + 10000) - } - }).forEach(([test, setTimeout]) => { - it(`should respect ${test} timeout if they never complete`, () => { - const start = Date.now() - 2900; - auction.getAuctionStart = () => start; - setTimeout(3000); - invokeCallbacks(); - return delay().then(() => { - expect(doneSpy.called).to.be.false; - return delay(100); - }).then(() => { - expect(doneSpy.called).to.be.true; - }); - }); - - it(`should not wait if ${test} has already timed out`, () => { - const start = Date.now() - 2000; - auction.getAuctionStart = () => start; - setTimeout(1000); - invokeCallbacks(); - return delay().then(() => { - expect(doneSpy.called).to.be.true; - }); - }); - }) }); describe('when bids are rejected', () => { diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index f7c2580f3f3..ef80a17d2db 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -10,7 +10,7 @@ import { addBidResponseHook, currencySupportEnabled, currencyRates, - ready + responseReady } from 'modules/currency.js'; import {createBid} from '../../../src/bidfactory.js'; import CONSTANTS from '../../../src/constants.json'; @@ -32,7 +32,6 @@ describe('currency', function () { beforeEach(function () { fakeCurrencyFileServer = server; - ready.reset(); }); afterEach(function () { @@ -321,29 +320,26 @@ describe('currency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); - var bid = { 'cpm': 1, 'currency': 'USD' }; + const bid = { 'cpm': 1, 'currency': 'USD' }; setConfig({ 'adServerCurrency': 'JPY' }); - var marker = false; - let promiseResolved = false; + let responseAdded = false; + let isReady = false; + responseReady.promise.then(() => { isReady = true }); + addBidResponseHook(Object.assign(function() { - marker = true; - }, { - bail: function (promise) { - promise.then(() => promiseResolved = true); - } + responseAdded = true; }), 'elementId', bid); - expect(marker).to.equal(false); - setTimeout(() => { - expect(promiseResolved).to.be.false; + expect(responseAdded).to.equal(false); + expect(isReady).to.equal(false); fakeCurrencyFileServer.respond(); setTimeout(() => { - expect(marker).to.equal(true); - expect(promiseResolved).to.be.true; + expect(responseAdded).to.equal(true); + expect(isReady).to.equal(true); done(); }); }); From fd8af621a01dcc59a98d60298ca4368e70b2d17e Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:05:33 +0200 Subject: [PATCH 077/760] Mediasquare Bid Adapter: add attribute to track (#10571) * forward ova attribute * remove unexpected trailing space --- modules/mediasquareBidAdapter.js | 4 ++-- test/spec/modules/mediasquareBidAdapter_spec.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 87404b7a9ff..1db4dad84cc 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -125,7 +125,7 @@ export const spec = { 'advertiserDomains': value['adomain'] } }; - let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment']; + let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment', 'ova']; paramsToSearchFor.forEach(param => { if (param in value) { bidResponse['mediasquare'][param] = value[param]; @@ -174,7 +174,7 @@ export const spec = { } let params = { pbjs: '$prebid.version$', referer: encodeURIComponent(getRefererInfo().page || getRefererInfo().topmostLocation) }; let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD; - let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment']; + let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment', 'ova']; if (bid.hasOwnProperty('mediasquare')) { paramsToSearchFor.forEach(param => { if (bid['mediasquare'].hasOwnProperty(param)) { diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 125d4bef02b..d7984c05967 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -101,6 +101,7 @@ describe('MediaSquare bid adapter tests', function () { 'adomain': ['test.com'], 'context': 'instream', 'increment': 1.0, + 'ova': 'cleared', }], }}; @@ -171,6 +172,7 @@ describe('MediaSquare bid adapter tests', function () { expect(bid.mediasquare.increment).to.exist; expect(bid.mediasquare.increment).to.equal(1.0); expect(bid.mediasquare.code).to.equal([DEFAULT_PARAMS[0].params.owner, DEFAULT_PARAMS[0].params.code].join('/')); + expect(bid.mediasquare.ova).to.exist.and.to.equal('cleared'); expect(bid.meta).to.exist; expect(bid.meta.advertiserDomains).to.exist; expect(bid.meta.advertiserDomains).to.have.lengthOf(1); @@ -213,6 +215,7 @@ describe('MediaSquare bid adapter tests', function () { let message = JSON.parse(server.requests[0].requestBody); expect(message).to.have.property('increment').exist; expect(message).to.have.property('increment').and.to.equal('1'); + expect(message).to.have.property('ova').and.to.equal('cleared'); }); it('Verifies user sync without cookie in bid response', function () { var syncs = spec.getUserSyncs({}, [BID_RESPONSE], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); From 3f95c97d6d5b291ee2c6e1b87a039afcf738646f Mon Sep 17 00:00:00 2001 From: PeiZ <74068135+peixunzhang@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:26:05 +0200 Subject: [PATCH 078/760] LiveIntent UserId module: Add support for openx and pubmatic id (#10570) * add support for openx and pubmatic * lint --- modules/liveIntentIdSystem.js | 32 +++++++++ test/spec/modules/eids_spec.js | 66 +++++++++++++++++++ .../modules/liveIntentIdMinimalSystem_spec.js | 10 +++ test/spec/modules/liveIntentIdSystem_spec.js | 10 +++ 4 files changed, 118 insertions(+) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index d80f7a14ddf..900b0c41119 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -211,6 +211,14 @@ export const liveIntentIdSubmodule = { result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.openx) { + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.pubmatic) { + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + } + return result } @@ -312,6 +320,30 @@ export const liveIntentIdSubmodule = { return data.ext; } } + }, + 'openx': { + source: 'openx.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'pubmatic': { + source: 'pubmatic.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } } } }; diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 1597790e652..f2ccdcb3024 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -304,6 +304,72 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('openx', function () { + const userId = { + openx: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'openx.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('openx with ext', function () { + const userId = { + openx: { '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: 'openx.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('pubmatic', function() { + const userId = { + pubmatic: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubmatic.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('pubmatic with ext', function() { + const userId = { + pubmatic: {'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: 'pubmatic.com', + 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/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 0929a022937..ed78d8f6e40 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -271,6 +271,16 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode an openx id to a seperate object when present', function () { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an pubmatic id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 4f11af57711..a51082f26bd 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -383,6 +383,16 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode an openx id to a seperate object when present', function () { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an pubmatic id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { From 3396f47890cf1c7d4d9edfc971707ae45c736f17 Mon Sep 17 00:00:00 2001 From: shahinrahbariasl <56240400+shahinrahbariasl@users.noreply.github.com> Date: Wed, 4 Oct 2023 16:48:32 -0400 Subject: [PATCH 079/760] IX Bid Adapter: add support for signaling privacy sandbox (fledge) auction requests [PB-1841] (#10537) * feat: add support for signaling fledge auction request [PB-1841] feat: add test coverage [PB-1841] feat: add more test coverage [PB-1841] feat: fix code smell [PB-1841] feat: fix code smell [PB-1841] feat: fix code smell and update test adunit code [PB-1841] feat: update ix adapter doc for fledge [PB-1841] feat: update auction config response interpretation [PB-1841] fix: fix typo [PB-1841] fix: add missing null check for banner ext [PB-1841] fix: add missing null check for banner ext [PB-1841] feat: update interpret response to consider igbid in response ext [PB-1841] feat: add extra validation and tests [PB-1841] feat: add test case [PB-1841] feat: update interpret response with new auctiuon confis [PB-1841] fix: update config name feat: update tests for new auction configs list response [PB-1841] * feat: update banner ext definition [PB-1841] * feat: update ixdiag and add integer check when signaling ae [PB-1841] * feat: change order of reading ae config to respect ad unit level override [PB-1841] * feat: add test case [PB-1841] * feat: fix lint issue [PB-1841] * feat: update fledgeenabled check in build request [PB-1841] --------- Co-authored-by: shahin.rahbariasl --- modules/ixBidAdapter.js | 89 ++++++-- modules/ixBidAdapter.md | 5 + test/spec/modules/ixBidAdapter_spec.js | 271 +++++++++++++++++++++++++ 3 files changed, 350 insertions(+), 15 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 6c5f90b7a2a..8b6b32e83ec 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -696,7 +696,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) // getting ixdiags for adunits of the video, outstream & multi format (MF) style - let ixdiag = buildIXDiag(validBidRequests); + const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + let ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); for (var key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; } @@ -952,6 +953,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { const dfpAdUnitCode = impressions[impKeys[adUnitIndex]].dfp_ad_unit_code; const tid = impressions[impKeys[adUnitIndex]].tid; const sid = impressions[impKeys[adUnitIndex]].sid; + const auctionEnvironment = impressions[impKeys[adUnitIndex]].ae; const bannerImpressions = impressionObjects.filter(impression => BANNER in impression); const otherImpressions = impressionObjects.filter(impression => !(BANNER in impression)); @@ -991,12 +993,18 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid) { + if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment) { _bannerImpression.ext = {}; + _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; _bannerImpression.ext.gpid = gpid; _bannerImpression.ext.tid = tid; _bannerImpression.ext.sid = sid; + + // enable fledge auction + if (auctionEnvironment == 1) { + _bannerImpression.ext.ae = 1; + } } if ('bidfloor' in bannerImps[0]) { @@ -1220,15 +1228,16 @@ function _getUserIds(bidRequest) { /** * Calculates IX diagnostics values and packages them into an object * - * @param {array} validBidRequests The valid bid requests from prebid + * @param {array} validBidRequests - The valid bid requests from prebid + * @param {bool} fledgeEnabled - Flag indicating if protected audience (fledge) is enabled * @return {Object} IX diag values for ad units */ -function buildIXDiag(validBidRequests) { +function buildIXDiag(validBidRequests, fledgeEnabled) { var adUnitMap = validBidRequests .map(bidRequest => bidRequest.adUnitCode) .filter((value, index, arr) => arr.indexOf(value) === index); - var ixdiag = { + let ixdiag = { mfu: 0, bu: 0, iu: 0, @@ -1239,12 +1248,13 @@ function buildIXDiag(validBidRequests) { version: '$prebid.version$', userIds: _getUserIds(validBidRequests[0]), url: window.location.href.split('?')[0], - vpd: defaultVideoPlacement + vpd: defaultVideoPlacement, + ae: fledgeEnabled }; // create ad unit map and collect the required diag properties - for (let i = 0; i < adUnitMap.length; i++) { - var bid = validBidRequests.filter(bidRequest => bidRequest.adUnitCode === adUnitMap[i])[0]; + for (let adUnit of adUnitMap) { + let bid = validBidRequests.filter(bidRequest => bidRequest.adUnitCode === adUnit)[0]; if (deepAccess(bid, 'mediaTypes')) { if (Object.keys(bid.mediaTypes).length > 1) { @@ -1350,7 +1360,7 @@ function createVideoImps(validBidRequest, videoImps) { * @param {object} missingBannerSizes reference to missing banner config sizes * @param {object} bannerImps reference to created banner impressions */ -function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { +function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidderRequest) { let imp = bidToBannerImp(validBidRequest); const bannerSizeDefined = includesSize(deepAccess(validBidRequest, 'mediaTypes.banner.sizes'), deepAccess(validBidRequest, 'params.size')); @@ -1366,6 +1376,21 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps) { bannerImps[validBidRequest.adUnitCode].tagId = deepAccess(validBidRequest, 'params.tagId'); bannerImps[validBidRequest.adUnitCode].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); + // Add Fledge flag if enabled + const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + if (fledgeEnabled) { + const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') + if (auctionEnvironment) { + if (isInteger(auctionEnvironment)) { + bannerImps[validBidRequest.adUnitCode].ae = auctionEnvironment; + } else { + logWarn('error setting auction environment flag - must be an integer') + } + } else if (deepAccess(bidderRequest, 'defaultForSlots') == 1) { + bannerImps[validBidRequest.adUnitCode].ae = 1 + } + } + // AdUnit-Specific First Party Data const adUnitFPD = deepAccess(validBidRequest, 'ortb2Imp.ext.data'); if (adUnitFPD) { @@ -1720,7 +1745,7 @@ export const spec = { for (const type in adUnitMediaTypes) { switch (adUnitMediaTypes[type]) { case BANNER: - createBannerImps(validBidRequest, missingBannerSizes, bannerImps); + createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidderRequest); break; case VIDEO: createVideoImps(validBidRequest, videoImps) @@ -1795,13 +1820,18 @@ export const spec = { const bids = []; let bid = null; - if (!serverResponse.hasOwnProperty('body') || !serverResponse.body.hasOwnProperty('seatbid')) { - FEATURE_TOGGLES.setFeatureToggles(serverResponse); + // Extract the FLEDGE auction configuration list from the response + let fledgeAuctionConfigs = deepAccess(serverResponse, 'body.ext.protectedAudienceAuctionConfigs') || []; + + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + + if (!serverResponse.hasOwnProperty('body')) { return bids; } const responseBody = serverResponse.body; - const seatbid = responseBody.seatbid; + const seatbid = responseBody.seatbid || []; + for (let i = 0; i < seatbid.length; i++) { if (!seatbid[i].hasOwnProperty('bid')) { continue; @@ -1837,8 +1867,28 @@ export const spec = { } } - FEATURE_TOGGLES.setFeatureToggles(serverResponse); - return bids; + if (Array.isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { + // Validate and filter fledgeAuctionConfigs + fledgeAuctionConfigs = fledgeAuctionConfigs.filter(config => { + if (!isValidAuctionConfig(config)) { + logWarn('Malformed auction config detected:', config); + return false; + } + return true; + }); + + try { + return { + bids, + fledgeAuctionConfigs, + }; + } catch (error) { + logWarn('Error attaching AuctionConfigs', error); + return bids; + } + } else { + return bids; + } }, /** @@ -2059,6 +2109,15 @@ function getFormatCount(imp) { return formatCount; } +/** + * Checks if auction config is valid + * @param {object} config + * @returns bool + */ +function isValidAuctionConfig(config) { + return typeof config === 'object' && config !== null; +} + /** * Adds device.w / device.h info * @param {object} r diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 638cb11c5ab..0705c5932cf 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -469,6 +469,11 @@ pbjs.setConfig({ The timeout value must be a positive whole number in milliseconds. +Protected Audience API (FLEDGE) +=========================== + +In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) module to build and enable Fledge. + Additional Information ====================== diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 853215d95ad..eb87f51b6f9 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -188,6 +188,35 @@ describe('IndexexchangeAdapter', function () { } ]; + const DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED = [ + { + bidder: 'ix', + params: { + siteId: '123', + size: [300, 250] + }, + sizes: [[300, 250], [300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + pos: 0 + } + }, + ortb2Imp: { + ext: { + tid: '173f49a8-7549-4218-a23c-e7ba59b47229', + ae: 1 // Fledge enabled + }, + }, + adUnitCode: 'div-fledge-ad-1460505748561-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47229', + bidId: '1a2b3c4d', + bidderRequestId: '11a22b33c44d', + auctionId: '1aa2bb3cc4dd', + schain: SAMPLE_SCHAIN + } + ]; + const DEFAULT_BANNER_VALID_BID_PARAM_NO_SIZE = [ { bidder: 'ix', @@ -735,6 +764,49 @@ describe('IndexexchangeAdapter', function () { } }; + const DEFAULT_OPTION_FLEDGE_ENABLED_GLOBALLY = { + gdprConsent: { + gdprApplies: true, + consentString: '3huaa11=qu3198ae', + vendorData: {} + }, + refererInfo: { + page: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + ortb2: { + site: { + page: 'https://www.prebid.org' + }, + source: { + tid: 'mock-tid' + } + }, + fledgeEnabled: true, + defaultForSlots: 1 + }; + + const DEFAULT_OPTION_FLEDGE_ENABLED = { + gdprConsent: { + gdprApplies: true, + consentString: '3huaa11=qu3198ae', + vendorData: {} + }, + refererInfo: { + page: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page' + }, + ortb2: { + site: { + page: 'https://www.prebid.org' + }, + source: { + tid: 'mock-tid' + } + }, + fledgeEnabled: true + }; + const DEFAULT_IDENTITY_RESPONSE = { IdentityIp: { responsePending: false, @@ -3146,6 +3218,71 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('buildRequestFledge', function () { + it('impression should have ae=1 in ext when fledge module is enabled and ae is set in ad unit', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.ext.ae).to.equal(1); + }); + + it('impression should have ae=1 in ext when fledge module is enabled globally and default is set through setConfig', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED_GLOBALLY); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.ext.ae).to.equal(1); + }); + + it('impression should have ae=1 in ext when fledge module is enabled globally but no default set through setConfig but set at ad unit level', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.ext.ae).to.equal(1); + }); + + it('impression should not have ae=1 in ext when fledge module is enabled globally through setConfig but overidden at ad unit level', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.ext.ae).to.be.undefined; + }); + + it('impression should not have ae=1 in ext when fledge module is disabled', function () { + const bidderRequest = deepClone(DEFAULT_OPTION); + const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + + expect(impression.ext.ae).to.be.undefined; + }); + + it('should contain correct IXdiag ae property for Fledge', function () { + const bid = DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]; + const bidderRequestWithFledgeEnabled = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + const request = spec.buildRequests([bid], bidderRequestWithFledgeEnabled); + const diagObj = extractPayload(request[0]).ext.ixdiag; + expect(diagObj.ae).to.equal(true); + }); + + it('should log warning for non integer auction environment in ad unit for fledge', () => { + const logWarnSpy = sinon.spy(utils, 'logWarn'); + const bid = DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]; + bid.ortb2Imp.ext.ae = 'malformed' + const bidderRequestWithFledgeEnabled = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + spec.buildRequests([bid], bidderRequestWithFledgeEnabled); + expect(logWarnSpy.calledWith('error setting auction environment flag - must be an integer')).to.be.true; + logWarnSpy.restore(); + }); + }); + describe('interpretResponse', function () { // generate bidderRequest with real buildRequest logic for intepretResponse testing let bannerBidderRequest @@ -3669,6 +3806,140 @@ describe('IndexexchangeAdapter', function () { const result = spec.interpretResponse({ body: DEFAULT_NATIVE_BID_RESPONSE }, nativeBidderRequest); expect(result[0]).to.deep.equal(expectedParse[0]); }); + + describe('Auction config response', function () { + let bidderRequestWithFledgeEnabled; + let serverResponseWithoutFledgeConfigs; + let serverResponseWithFledgeConfigs; + let serverResponseWithMalformedAuctionConfig; + let serverResponseWithMalformedAuctionConfigs; + + beforeEach(() => { + bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; + bidderRequestWithFledgeEnabled.fledgeEnabled = true; + + serverResponseWithoutFledgeConfigs = { + body: { + ...DEFAULT_BANNER_BID_RESPONSE + } + }; + + serverResponseWithFledgeConfigs = { + body: { + ...DEFAULT_BANNER_BID_RESPONSE, + ext: { + protectedAudienceAuctionConfigs: [ + { + bidId: '59f219e54dc2fc', + config: { + seller: 'https://seller.test.indexexchange.com', + decisionLogicUrl: 'https://seller.test.indexexchange.com/decision-logic.js', + interestGroupBuyers: ['https://buyer.test.indexexchange.com'], + sellerSignals: { + callbackURL: 'https://test.com/ig/v1/ck74j8bcvc9c73a8eg6g' + }, + perBuyerSignals: { + 'https://buyer.test.indexexchange.com': {} + } + } + } + ] + } + } + }; + + serverResponseWithMalformedAuctionConfig = { + body: { + ...DEFAULT_BANNER_BID_RESPONSE, + ext: { + protectedAudienceAuctionConfigs: ['malformed'] + } + } + }; + + serverResponseWithMalformedAuctionConfigs = { + body: { + ...DEFAULT_BANNER_BID_RESPONSE, + ext: { + protectedAudienceAuctionConfigs: 'malformed' + } + } + }; + }); + + it('should correctly interpret response with auction configs', () => { + const result = spec.interpretResponse(serverResponseWithFledgeConfigs, bidderRequestWithFledgeEnabled); + const expectedOutput = [ + { + bidId: '59f219e54dc2fc', + config: { + ...serverResponseWithFledgeConfigs.body.ext.protectedAudienceAuctionConfigs[0].config, + perBuyerSignals: { + 'https://buyer.test.indexexchange.com': {} + } + } + } + ]; + expect(result.fledgeAuctionConfigs).to.deep.equal(expectedOutput); + }); + + it('should correctly interpret response without auction configs', () => { + const result = spec.interpretResponse(serverResponseWithoutFledgeConfigs, bidderRequestWithFledgeEnabled); + expect(result.fledgeAuctionConfigs).to.be.undefined; + }); + + it('should handle malformed auction configs gracefully', () => { + const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); + expect(result.fledgeAuctionConfigs).to.be.empty; + }); + + it('should log warning for malformed auction configs', () => { + const logWarnSpy = sinon.spy(utils, 'logWarn'); + spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); + expect(logWarnSpy.calledWith('Malformed auction config detected:', 'malformed')).to.be.true; + logWarnSpy.restore(); + }); + + it('should return bids when protected audience auction conigs is malformed', () => { + const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfigs, bidderRequestWithFledgeEnabled); + expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.length).to.be.greaterThan(0); + }); + }); + + describe('interpretResponse when server response is empty', function() { + let serverResponseWithoutBody; + let serverResponseWithoutSeatbid; + let bidderRequestWithFledgeEnabled; + let bidderRequestWithoutFledgeEnabled; + + beforeEach(() => { + serverResponseWithoutBody = {}; + + serverResponseWithoutSeatbid = { + body: {} + }; + + bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; + bidderRequestWithFledgeEnabled.fledgeEnabled = true; + + bidderRequestWithoutFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; + }); + + it('should return empty bids when response does not have body', function () { + let result = spec.interpretResponse(serverResponseWithoutBody, bidderRequestWithFledgeEnabled); + expect(result).to.deep.equal([]); + result = spec.interpretResponse(serverResponseWithoutBody, bidderRequestWithoutFledgeEnabled); + expect(result).to.deep.equal([]); + }); + + it('should return empty bids when response body does not have seatbid', function () { + let result = spec.interpretResponse(serverResponseWithoutSeatbid, bidderRequestWithFledgeEnabled); + expect(result).to.deep.equal([]); + result = spec.interpretResponse(serverResponseWithoutSeatbid, bidderRequestWithoutFledgeEnabled); + expect(result).to.deep.equal([]); + }); + }); }); describe('bidrequest consent', function () { From 8f102372b9acb0903f627e3832b82f9f48ba40a2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 4 Oct 2023 15:50:14 -0700 Subject: [PATCH 080/760] consentManagementUsp: fix bug where data deletion events are triggered when CMP does not support `registerDeletion` (#10574) --- modules/consentManagementUsp.js | 2 +- test/spec/modules/consentManagementUsp_spec.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index fb65a76c87b..1218c3724f4 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -90,7 +90,7 @@ function lookupUspConsent({onSuccess, onError}) { cmp({ command: 'registerDeletion', - callback: adapterManager.callDataDeletionRequest + callback: (res, success) => (success == null || success) && adapterManager.callDataDeletionRequest(res) }).catch(e => { logError('Error invoking CMP `registerDeletion`:', e); }); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index e98486754ab..c372c66f7f0 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -522,6 +522,19 @@ describe('consentManagement', function () { setConsentConfig(goodConfig); expect(uspDataHandler.getConsentData()).to.eql('string'); }); + + it('does not invoke registerDeletion if the CMP calls back with an error', () => { + sandbox.stub(window, '__uspapi').callsFake((cmd, _, cb) => { + if (cmd === 'registerDeletion') { + cb(null, false); + } else { + // eslint-disable-next-line standard/no-callback-literal + cb({uspString: 'string'}, true); + } + }); + setConsentConfig(goodConfig); + sinon.assert.notCalled(adapterManager.callDataDeletionRequest); + }) }); }); }); From 7c5d169ab5505452092904a306e7ef3f89165e3c Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:04:57 +0200 Subject: [PATCH 081/760] Oxxion Analytics Adapter : changes in bidwon (#10564) * push ova parameter in bidwon * minor change * remove useless lines * remove comment --- modules/oxxionAnalyticsAdapter.js | 12 ++++++++++- .../modules/oxxionAnalyticsAdapter_spec.js | 21 ++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js index d4dcd33be88..25732d440ff 100644 --- a/modules/oxxionAnalyticsAdapter.js +++ b/modules/oxxionAnalyticsAdapter.js @@ -183,6 +183,15 @@ function handleBidWon(args) { } }); } + if (auction['auctionId'] == args['auctionId'] && typeof auction['bidderRequests'] == 'object') { + auction['bidderRequests'].forEach((req) => { + req.bids.forEach((bid) => { + if (bid['bidId'] == args['requestId'] && bid['transactionId'] == args['transactionId']) { + args['ova'] = bid['ova']; + } + }); + }); + } }); } args['cpmIncrement'] = increment; @@ -232,7 +241,8 @@ let oxxionAnalytics = Object.assign(adapter({url, analyticsType}), { addTimeout(args); break; } - }}); + } +}); // save the base class function oxxionAnalytics.originEnableAnalytics = oxxionAnalytics.enableAnalytics; diff --git a/test/spec/modules/oxxionAnalyticsAdapter_spec.js b/test/spec/modules/oxxionAnalyticsAdapter_spec.js index 13dc395968a..9d06be24f68 100644 --- a/test/spec/modules/oxxionAnalyticsAdapter_spec.js +++ b/test/spec/modules/oxxionAnalyticsAdapter_spec.js @@ -86,20 +86,21 @@ describe('Oxxion Analytics', function () { } }, 'adUnitCode': 'tag_200124_banner', - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', 'sizes': [ [ 300, 600 ] ], - 'bidId': '34a63e5d5378a3', + 'bidId': '2bd3e8ff8a113f', 'bidderRequestId': '11dc6ff6378de7', 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, - 'bidderWinsCount': 0 + 'bidderWinsCount': 0, + 'ova': 'cleared' } ], 'auctionStart': 1647424261187, @@ -149,12 +150,12 @@ describe('Oxxion Analytics', function () { 'bidsReceived': [ { 'bidderCode': 'appnexus', - 'width': 300, - 'height': 600, + 'width': 970, + 'height': 250, 'statusMessage': 'Bid available', - 'adId': '7a4ced80f33d33', - 'requestId': '34a63e5d5378a3', - 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', 'mediaType': 'video', 'source': 'client', @@ -187,7 +188,7 @@ describe('Oxxion Analytics', function () { 'size': '300x600', 'adserverTargeting': { 'hb_bidder': 'appnexus', - 'hb_adid': '7a4ced80f33d33', + 'hb_adid': '65d16ef039a97a', 'hb_pb': '20.000000', 'hb_size': '300x600', 'hb_source': 'client', @@ -342,7 +343,7 @@ describe('Oxxion Analytics', function () { expect(message).to.have.property('adId') expect(message).to.have.property('cpmIncrement').and.to.equal(27.4276); expect(message).to.have.property('oxxionMode').and.to.have.property('abtest').and.to.equal(true); - // sinon.assert.callCount(oxxionAnalytics.track, 1); + expect(message).to.have.property('ova').and.to.equal('cleared'); }); }); }); From 2f6089eda8c2852b03a30bbb8c265e60f8a146af Mon Sep 17 00:00:00 2001 From: Samuel Adu Date: Thu, 5 Oct 2023 13:01:57 +0100 Subject: [PATCH 082/760] YahooAds Bid Adapter: Documentation update (#10576) * YahooAds bid adapter documentation update * Slight updates. --------- Co-authored-by: slimkrazy --- modules/yahoosspBidAdapter.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/yahoosspBidAdapter.md b/modules/yahoosspBidAdapter.md index c8c42930e5b..e7074e64682 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahoosspBidAdapter.md @@ -19,6 +19,16 @@ The Yahoo Advertising Bid Adapter is an OpenRTB interface that consolidates all * First Party Data (ortb2 & ortb2Imp) * Custom TTL (time to live) +# Adapter Aliases +Whilst the primary bidder code for this bid adapter is `yahooAds`, the aliases `yahoossp` and `yahooAdvertising` can be used to enable this adapter. If you wish to set Prebid configuration specifically for this bid adapter, then the configuration key _must_ match the used bidder code. All examples in this documentation use the primiry bidder code, but switching `yahooAds` with one of the relevant aliases may be required for your setup. Let's take [setting the request mode](#adapter-request-mode) as an example; if you used the `yahoossp` alias, then the corresponding `setConfig` API call would look like this: + +```javascript +pbjs.setConfig({ + yahoossp: { + mode: 'banner' // 'all', 'video', 'banner' (default) + } +}); +``` # Adapter Request mode Since the Yahoo Advertising bid adapter supports both Banner and Video adUnits, a controller was needed to allow you to define when the adapter should generate a bid-requests to the Yahoo bid endpoint. From ee9ad4c24e73226ea94c4cff5e3ea9f6a53348e8 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 5 Oct 2023 10:31:14 -0300 Subject: [PATCH 083/760] DFP Ad Server Video: bugfix - encode description url (#10575) * encodes page in description url * adds test * typo --- modules/dfpAdServerVideo.js | 6 ++++-- test/spec/modules/dfpAdServerVideo_spec.js | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 3394fd8b3f4..a3e26dc7202 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -249,7 +249,9 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { */ function buildUrlFromAdserverUrlComponents(components, bid, options) { const descriptionUrl = getDescriptionUrl(bid, components, 'search'); - if (descriptionUrl) { components.search.description_url = descriptionUrl; } + if (descriptionUrl) { + components.search.description_url = descriptionUrl; + } components.search.cust_params = getCustParams(bid, options, components.search.cust_params); return buildUrl(components); @@ -264,7 +266,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { * @return {string | undefined} The encoded vast url if it exists, or undefined */ function getDescriptionUrl(bid, components, prop) { - return deepAccess(components, `${prop}.description_url`) || dep.ri().page; + return deepAccess(components, `${prop}.description_url`) || encodeURIComponent(dep.ri().page); } /** diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 89485adf28b..4c12e9fa211 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -58,8 +58,18 @@ describe('The DFP video support module', function () { }, options))); const prm = utils.parseQS(url.query); expect(prm.description_url).to.eql('example.com'); - }) - }) + }); + + it('should use a URI encoded page location as default for description_url', () => { + sandbox.stub(dep, 'ri').callsFake(() => ({page: 'https://example.com?iu=/99999999/news&cust_params=current_hour%3D12%26newscat%3Dtravel&pbjs_debug=true'})); + const url = parse(buildDfpVideoUrl(Object.assign({ + adUnit: adUnit, + bid: bid, + }, options))); + const prm = utils.parseQS(url.query); + expect(prm.description_url).to.eql('https%3A%2F%2Fexample.com%3Fiu%3D%2F99999999%2Fnews%26cust_params%3Dcurrent_hour%253D12%2526newscat%253Dtravel%26pbjs_debug%3Dtrue'); + }); + }); }) it('should make a legal request URL when given the required params', function () { From b1b6ab9dddda7aba9d231bcf8b37b8077c89d6de Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 5 Oct 2023 22:03:50 +0000 Subject: [PATCH 084/760] Prebid 8.18.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4c4f47e470..0e4133b6b91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.18.0-pre", + "version": "8.18.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 40b86d8f850..3126d3cdd7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.18.0-pre", + "version": "8.18.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2c878c7c5b3e697db71b82ab0cb595acab9d492b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 5 Oct 2023 22:03:50 +0000 Subject: [PATCH 085/760] Increment version to 8.19.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e4133b6b91..bc544036ad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.18.0", + "version": "8.19.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 3126d3cdd7b..317e23b9a06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.18.0", + "version": "8.19.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 25bd335e61c3541c49088d4da21de6f8ef0c42ad Mon Sep 17 00:00:00 2001 From: sourbh-doceree <65942945+sourbh-doceree@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:54:14 +0530 Subject: [PATCH 086/760] Doceree Bid Adaptor: bidwon and timeout hooks added (#10543) * doceree adapter * add fix for impression register * update doceree adaptor with hooks * lint changes and testcases --- modules/docereeBidAdapter.js | 29 ++++++++++++++++++ test/spec/modules/docereeBidAdapter_spec.js | 33 +++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/modules/docereeBidAdapter.js b/modules/docereeBidAdapter.js index fa4446ede47..2731e1ff397 100644 --- a/modules/docereeBidAdapter.js +++ b/modules/docereeBidAdapter.js @@ -1,9 +1,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { triggerPixel } from '../src/utils.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BIDDER_CODE = 'doceree'; const END_POINT = 'https://bidder.doceree.com' +const TRACKING_END_POINT = 'https://tracking.doceree.com' export const spec = { code: BIDDER_CODE, @@ -69,6 +71,33 @@ export const spec = { } }; return [bidResponse]; + }, + onTimeout: function(timeoutData) { + if (timeoutData == null || !timeoutData.length) { + return; + } + timeoutData.forEach(td => { + const encodedBuf = window.btoa(encodeURIComponent(JSON.stringify({ + bidId: td.bidId, + timeout: td.timeout, + }))); + triggerPixel(TRACKING_END_POINT + '/v1/hbTimeout?adp=prebidjs&data=' + encodedBuf); + }) + }, + onBidWon: function (bidWon) { + if (bidWon == null) { + return; + } + const encodedBuf = window.btoa(encodeURIComponent(JSON.stringify({ + requestId: bidWon.requestId, + cpm: bidWon.cpm, + adId: bidWon.adId, + currency: bidWon.currency, + netRevenue: bidWon.netRevenue, + status: bidWon.status, + hb_pb: bidWon.adserverTargeting && bidWon.adserverTargeting.hb_pb, + }))); + triggerPixel(TRACKING_END_POINT + '/v1/hbBidWon?adp=prebidjs&data=' + encodedBuf); } }; diff --git a/test/spec/modules/docereeBidAdapter_spec.js b/test/spec/modules/docereeBidAdapter_spec.js index dadbb56b0c0..25da8b256fc 100644 --- a/test/spec/modules/docereeBidAdapter_spec.js +++ b/test/spec/modules/docereeBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec} from '../../../modules/docereeBidAdapter.js'; import { config } from '../../../src/config.js'; +import * as utils from 'src/utils.js'; describe('BidlabBidAdapter', function () { config.setConfig({ @@ -102,4 +103,36 @@ describe('BidlabBidAdapter', function () { expect(dataItem.meta.advertiserDomains[0]).to.equal('doceree.com') }); }) + describe('onBidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon({}); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }); + describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('exists and is a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should return nothing', function () { + var response = spec.onBidWon([]); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.called).to.equal(true); + }); + }); }); From 9554607d70f8cef89c0f4e508e14d61fb52040e7 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Fri, 6 Oct 2023 17:18:01 +0300 Subject: [PATCH 087/760] minduration non-required (#10581) --- modules/sovrnBidAdapter.js | 1 - test/spec/modules/sovrnBidAdapter_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 79481b81936..f72c482735a 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -44,7 +44,6 @@ const ORTB_VIDEO_PARAMS = { const REQUIRED_VIDEO_PARAMS = { context: (value) => value !== ADPOD, mimes: ORTB_VIDEO_PARAMS.mimes, - minduration: ORTB_VIDEO_PARAMS.minduration, maxduration: ORTB_VIDEO_PARAMS.maxduration, protocols: ORTB_VIDEO_PARAMS.protocols } diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 90913c6f130..032f959e559 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -64,6 +64,29 @@ describe('sovrnBidAdapter', function() { expect(spec.isBidRequestValid(bidRequest)).to.equal(false) }) + + it('should return true when minduration is not passed', function() { + const width = 300 + const height = 250 + const mimes = ['video/mp4', 'application/javascript'] + const protocols = [2, 5] + const maxduration = 60 + const startdelay = 0 + const videoBidRequest = { + ...baseBidRequest, + mediaTypes: { + video: { + mimes, + protocols, + playerSize: [[width, height], [360, 240]], + maxduration, + startdelay + } + } + } + + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(true) + }) }) describe('buildRequests', function () { From 2ffe7860978e98fb177bb4f736ae32baf50a4383 Mon Sep 17 00:00:00 2001 From: Ibrahima Niass <48965172+Niass@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:43:24 +0000 Subject: [PATCH 088/760] Bliink Bid Adapter: enhance request data with eids, bidFloor, device info & domLoadingDuration (#10499) * fix(bliink): ttl unit tests * Bliink Bid Adapter: enhance request data with userIds, ect, refresh * Bliink Bid Adapter: fix tests * Bliink Bid Adapter: enhance request data with eids, bidFloor, device info & domLoadingDuration --------- Co-authored-by: Samous --- modules/bliinkBidAdapter.js | 76 ++++++++++++++--- test/spec/modules/bliinkBidAdapter_spec.js | 98 +++++++++++++++++----- 2 files changed, 142 insertions(+), 32 deletions(-) diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 6f3f5e21cb8..7042dd86cb9 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -2,7 +2,7 @@ // eslint-disable-next-line prebid/validate-imports import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' -import {_each, deepAccess, deepSetValue} from '../src/utils.js' +import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' export const BIDDER_CODE = 'bliink' export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' @@ -15,6 +15,7 @@ const BANNER = 'banner' window.bliinkBid = window.bliinkBid || {}; const supportedMediaTypes = [BANNER, VIDEO] const aliasBidderCode = ['bk'] +const CURRENCY = 'EUR'; /** * @description get coppa value from config @@ -49,9 +50,8 @@ export function getEffectiveConnectionType() { */ export function getUserIds(validBidRequests) { /** @type {Object} */ - const firstBidRequest = validBidRequests?.[0] - if (firstBidRequest?.userIds) { - return firstBidRequest.userIds + if (validBidRequests?.[0]?.userIdAsEids) { + return validBidRequests[0].userIdAsEids; } } export function getMetaList(name) { @@ -122,6 +122,35 @@ export function getKeywords() { return []; } +function canAccessTopWindow() { + try { + if (getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} + +/** + * domLoading feature is computed on window.top if reachable. + */ +export function getDomLoadingDuration() { + let domLoadingDuration = -1; + let performance; + + performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; + + if (performance && performance.timing && performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + + return domLoadingDuration; +} + /** * @param bidRequest * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} @@ -151,7 +180,7 @@ export const buildBid = (bidResponse) => { } return Object.assign(bid, { cpm: bidResponse.price, - currency: bidResponse.currency || 'EUR', + currency: bidResponse.currency || CURRENCY, creativeId: deepAccess(bidResponse, 'extras.deal_id'), requestId: deepAccess(bidResponse, 'extras.transaction_id'), width: deepAccess(bidResponse, `creative.${bid.mediaType}.width`) || 1, @@ -180,37 +209,58 @@ export const isBidRequestValid = (bid) => { */ export const buildRequests = (validBidRequests, bidderRequest) => { if (!validBidRequests || !bidderRequest || !bidderRequest.bids) return null - + const domLoadingDuration = getDomLoadingDuration().toString(); const tags = bidderRequest.bids.map((bid) => { + let bidFloor; + const sizes = bid.sizes.map((size) => ({ w: size[0], h: size[1] })); + const mediaTypes = Object.keys(bid.mediaTypes) + if (typeof bid.getFloor === 'function') { + bidFloor = bid.getFloor({ + currency: CURRENCY, + mediaType: mediaTypes[0], + size: sizes[0] + }); + } const id = bid.params.tagId - return { + const request = { sizes: bid.sizes.map((size) => ({ w: size[0], h: size[1] })), id, // TODO: bidId is globally unique, is it a good choice for transaction ID (vs ortb2Imp.ext.tid)? transactionId: bid.bidId, - mediaTypes: Object.keys(bid.mediaTypes), + mediaTypes: mediaTypes, imageUrl: deepAccess(bid, 'params.imageUrl', ''), videoUrl: deepAccess(bid, 'params.videoUrl', ''), refresh: (window.bliinkBid[id] = (window.bliinkBid[id] ?? -1) + 1) || undefined, - }; + } + if (bidFloor) { + request.bidFloor = bidFloor + } + return request; }); let request = { tags, pageTitle: document.title, - pageUrl: deepAccess(bidderRequest, 'refererInfo.page'), + pageUrl: deepAccess(bidderRequest, 'refererInfo.page').replace(/\?.*$/, ''), pageDescription: getMetaValue(META_DESCRIPTION), keywords: getKeywords().join(','), ect: getEffectiveConnectionType(), }; const schain = deepAccess(validBidRequests[0], 'schain') - const userIds = getUserIds(validBidRequests) + const eids = getUserIds(validBidRequests) + const device = bidderRequest.ortb2?.device if (schain) { request.schain = schain } - if (userIds) { - request.userIds = userIds + if (domLoadingDuration > -1) { + request.domLoadingDuration = domLoadingDuration + } + if (device) { + request.device = device + } + if (eids) { + request.eids = eids } const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); if (!!gdprConsent && gdprConsent.gdprApplies) { diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index d0320ab6ec1..cd1dcf4a20a 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -7,6 +7,7 @@ import { BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME, getEffectiveConnectionType, getUserIds, + getDomLoadingDuration, } from 'modules/bliinkBidAdapter.js'; import { config } from 'src/config.js'; @@ -31,6 +32,7 @@ import { config } from 'src/config.js'; */ const connectionType = getEffectiveConnectionType(); +const domLoadingDuration = getDomLoadingDuration().toString(); const getConfigBid = (placement) => { return { adUnitCode: '/19968336/test', @@ -57,6 +59,7 @@ const getConfigBid = (placement) => { }, }, }, + domLoadingDuration, ect: connectionType, params: { placement: placement, @@ -348,11 +351,31 @@ const GetUserIds = [ want: undefined, }, { - title: 'Should return userIds if exists', + title: 'Should return eids if exists', args: { - fn: getUserIds([{ userIds: { criteoId: 'testId' } }]), + fn: getUserIds([{ userIdAsEids: [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'testId', + 'atype': 1 + } + ] + } + ] }]), }, - want: { criteoId: 'testId' }, + want: [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'testId', + 'atype': 1 + } + ] + } + ], }, ]; @@ -655,12 +678,13 @@ const testsBuildRequests = [ method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, keywords: '', pageDescription: '', pageTitle: '', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', tags: [ { transactionId: '2def0c5b2a7f6e', @@ -697,6 +721,7 @@ const testsBuildRequests = [ method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, gdpr: true, gdprConsent: 'XXXX', @@ -704,7 +729,7 @@ const testsBuildRequests = [ pageTitle: '', keywords: '', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', tags: [ { transactionId: '2def0c5b2a7f6e', @@ -742,6 +767,7 @@ const testsBuildRequests = [ method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, gdpr: true, uspConsent: 'uspConsent', @@ -750,7 +776,7 @@ const testsBuildRequests = [ pageTitle: '', keywords: '', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', tags: [ { transactionId: '2def0c5b2a7f6e', @@ -801,6 +827,7 @@ const testsBuildRequests = [ method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, gdpr: true, gdprConsent: 'XXXX', @@ -808,7 +835,7 @@ const testsBuildRequests = [ pageTitle: '', keywords: '', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', schain: { ver: '1.0', complete: 1, @@ -840,16 +867,31 @@ const testsBuildRequests = [ }, }, { - title: 'Should build request with userIds if exists', + title: 'Should build request with eids if exists', args: { fn: spec.buildRequests( [ { - userIds: { - criteoId: - 'vG4RRF93V05LRlJUTVVOQTJJJTJGbG1rZWxEeDVvc0NXWE42TzJqU2hG', - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - }, + userIdAsEids: [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'vG4RRF93V05LRlJUTVVOQTJJJTJGbG1rZWxEeDVvc0NXWE42TzJqU2hG', + 'atype': 1 + } + ] + }, + { + 'source': 'netid.de', + 'uids': [ + { + 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + 'atype': 1 + } + ] + } + ], }, ], Object.assign(getConfigBuildRequest('banner'), { @@ -864,6 +906,7 @@ const testsBuildRequests = [ method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, gdpr: true, gdprConsent: 'XXXX', @@ -871,11 +914,27 @@ const testsBuildRequests = [ pageTitle: '', keywords: '', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', - userIds: { - criteoId: 'vG4RRF93V05LRlJUTVVOQTJJJTJGbG1rZWxEeDVvc0NXWE42TzJqU2hG', - netId: 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', - }, + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', + eids: [ + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'vG4RRF93V05LRlJUTVVOQTJJJTJGbG1rZWxEeDVvc0NXWE42TzJqU2hG', + 'atype': 1 + } + ] + }, + { + 'source': 'netid.de', + 'uids': [ + { + 'id': 'fH5A3n2O8_CZZyPoJVD-eabc6ECb7jhxCicsds7qSg', + 'atype': 1 + } + ] + } + ], tags: [ { transactionId: '2def0c5b2a7f6e', @@ -1053,6 +1112,7 @@ describe('BLIINK Adapter keywords & coppa true', function () { method: 'POST', url: BLIINK_ENDPOINT_ENGINE, data: { + domLoadingDuration, ect: connectionType, gdpr: true, coppa: 1, @@ -1061,7 +1121,7 @@ describe('BLIINK Adapter keywords & coppa true', function () { pageTitle: '', keywords: 'Bliink,Saber,Prebid', pageUrl: - 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html?pbjs_debug=true', + 'http://localhost:9999/integrationExamples/gpt/bliink-adapter.html', tags: [ { transactionId: '2def0c5b2a7f6e', From 503f9984f0e04867282072bff3466563d5c998ff Mon Sep 17 00:00:00 2001 From: yuansi-piao <60088520+yuansi-piao@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:08:41 +0800 Subject: [PATCH 089/760] Add gvlid for OperaAds module (#10586) --- modules/operaadsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index b45c0452319..b28a24ef57a 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -70,7 +70,7 @@ const NATIVE_DEFAULTS = { export const spec = { code: BIDDER_CODE, - + gvlid: 1135, // short code aliases: ['opera'], From 2be311071e112637ce62135ff12b962838674105 Mon Sep 17 00:00:00 2001 From: Yuki Tsujii Date: Tue, 10 Oct 2023 02:59:14 +0900 Subject: [PATCH 090/760] Dgkeyword Bid Adapter : use ortb2 for keywords (#10542) --- modules/dgkeywordRtdProvider.js | 48 ++++++++++++++----- .../spec/modules/dgkeywordRtdProvider_spec.js | 34 +++++++++---- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js index 99df3b18a39..76f5f04ac03 100644 --- a/modules/dgkeywordRtdProvider.js +++ b/modules/dgkeywordRtdProvider.js @@ -6,8 +6,7 @@ * @module modules/dgkeywordProvider * @requires module:modules/realTimeData */ - -import {logMessage, deepSetValue, logError, logInfo, mergeDeep} from '../src/utils.js'; +import { logMessage, deepSetValue, logError, logInfo, isStr, isArray } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getGlobal } from '../src/prebidGlobal.js'; @@ -57,20 +56,12 @@ export function getDgKeywordsAndSet(reqBidsConfigObj, callback, moduleConfig, us if (Object.keys(keywords).length > 0) { const targetBidKeys = {}; for (let bid of setKeywordTargetBidders) { - // set keywords to params - bid.params.keywords = keywords; + // set keywords to ortb2Imp + deepSetValue(bid, 'ortb2Imp.ext.data.keywords', convertKeywordsToString(keywords)); if (!targetBidKeys[bid.bidder]) { targetBidKeys[bid.bidder] = true; } } - - if (!reqBidsConfigObj._ignoreSetOrtb2) { - // set keywrods to ortb2 - let addOrtb2 = {}; - deepSetValue(addOrtb2, 'site.keywords', keywords); - deepSetValue(addOrtb2, 'user.keywords', keywords); - mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, Object.fromEntries(Object.keys(targetBidKeys).map(bidder => [bidder, addOrtb2]))); - } } } isFinish = true; @@ -156,4 +147,37 @@ function init(moduleConfig) { function registerSubModule() { submodule('realTimeData', dgkeywordSubmodule); } + +// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' +export function convertKeywordsToString(keywords) { + let result = ''; + Object.keys(keywords).forEach(key => { + // if 'text' or '' + if (isStr(keywords[key])) { + if (keywords[key] !== '') { + result += `${key}=${keywords[key]},` + } else { + result += `${key},`; + } + } else if (isArray(keywords[key])) { + let isValSet = false + keywords[key].forEach(val => { + if (isStr(val) && val) { + result += `${key}=${val},` + isValSet = true + } + }); + if (!isValSet) { + result += `${key},` + } + } else { + result += `${key},` + } + }); + + // remove last trailing comma + result = result.substring(0, result.length - 1); + return result; +} + registerSubModule(); diff --git a/test/spec/modules/dgkeywordRtdProvider_spec.js b/test/spec/modules/dgkeywordRtdProvider_spec.js index 754740b7a64..ff88ea0512f 100644 --- a/test/spec/modules/dgkeywordRtdProvider_spec.js +++ b/test/spec/modules/dgkeywordRtdProvider_spec.js @@ -91,6 +91,22 @@ describe('Digital Garage Keyword Module', function () { expect(dgRtd.getTargetBidderOfDgKeywords(adUnits_no_target)).an('array') .that.is.empty; }); + it('convertKeywordsToString method unit test', function () { + const keywordsTest = [ + { keywords: { param1: 'keywords1' }, result: 'param1=keywords1' }, + { keywords: { param1: 'keywords1', param2: 'keywords2' }, result: 'param1=keywords1,param2=keywords2' }, + { keywords: { p1: 'k1', p2: 'k2', p: 'k' }, result: 'p1=k1,p2=k2,p=k' }, + { keywords: { p1: 'k1', p2: 'k2', p: ['k'] }, result: 'p1=k1,p2=k2,p=k' }, + { keywords: { p1: 'k1', p2: ['k21', 'k22'], p: ['k'] }, result: 'p1=k1,p2=k21,p2=k22,p=k' }, + { keywords: { p1: ['k11', 'k12', 'k13'], p2: ['k21', 'k22'], p: ['k'] }, result: 'p1=k11,p1=k12,p1=k13,p2=k21,p2=k22,p=k' }, + { keywords: { p1: [], p2: ['', ''], p: [''] }, result: 'p1,p2,p' }, + { keywords: { p1: 1, p2: [1, 'k2'], p: '' }, result: 'p1,p2=k2,p' }, + { keywords: { p1: ['k1', 2, 'k3'], p2: [1, 2], p: 3 }, result: 'p1=k1,p1=k3,p2,p' }, + ]; + for (const test of keywordsTest) { + expect(dgRtd.convertKeywordsToString(test.keywords)).equal(test.result); + } + }) it('should have targets', function () { const adUnits_targets = [ { @@ -242,16 +258,16 @@ describe('Digital Garage Keyword Module', function () { expect(targets[1].bidder).to.be.equal('dg2'); expect(targets[1].params.placementId).to.be.equal(99999998); expect(targets[1].params.dgkeyword).to.be.an('undefined'); - expect(targets[1].params.keywords).to.be.an('undefined'); + expect(targets[1].params.ortb2Imp).to.be.an('undefined'); targets = pbjs.adUnits[1].bids; expect(targets[0].bidder).to.be.equal('dg'); expect(targets[0].params.placementId).to.be.equal(99999996); expect(targets[0].params.dgkeyword).to.be.an('undefined'); - expect(targets[0].params.keywords).to.be.an('undefined'); + expect(targets[0].params.ortb2Imp).to.be.an('undefined'); expect(targets[2].bidder).to.be.equal('dg3'); expect(targets[2].params.placementId).to.be.equal(99999994); expect(targets[2].params.dgkeyword).to.be.an('undefined'); - expect(targets[2].params.keywords).to.be.an('undefined'); + expect(targets[2].params.ortb2Imp).to.be.an('undefined'); expect(pbjs.getBidderConfig()).to.be.deep.equal({}); @@ -275,16 +291,16 @@ describe('Digital Garage Keyword Module', function () { expect(targets[1].bidder).to.be.equal('dg2'); expect(targets[1].params.placementId).to.be.equal(99999998); expect(targets[1].params.dgkeyword).to.be.an('undefined'); - expect(targets[1].params.keywords).to.be.an('undefined'); + expect(targets[1].params.ortb2Imp).to.be.an('undefined'); targets = pbjs.adUnits[1].bids; expect(targets[0].bidder).to.be.equal('dg'); expect(targets[0].params.placementId).to.be.equal(99999996); expect(targets[0].params.dgkeyword).to.be.an('undefined'); - expect(targets[0].params.keywords).to.be.an('undefined'); + expect(targets[0].params.ortb2Imp).to.be.an('undefined'); expect(targets[2].bidder).to.be.equal('dg3'); expect(targets[2].params.placementId).to.be.equal(99999994); expect(targets[2].params.dgkeyword).to.be.an('undefined'); - expect(targets[2].params.keywords).to.be.an('undefined'); + expect(targets[2].params.ortb2Imp).to.be.an('undefined'); expect(pbjs.getBidderConfig()).to.be.deep.equal({}); @@ -318,16 +334,16 @@ describe('Digital Garage Keyword Module', function () { expect(targets[1].bidder).to.be.equal('dg2'); expect(targets[1].params.placementId).to.be.equal(99999998); expect(targets[1].params.dgkeyword).to.be.an('undefined'); - expect(targets[1].params.keywords).to.be.deep.equal(SUCCESS_RESULT); + expect(targets[1].ortb2Imp.ext.data.keywords).to.be.deep.equal(dgRtd.convertKeywordsToString(SUCCESS_RESULT)); targets = pbjs.adUnits[1].bids; expect(targets[0].bidder).to.be.equal('dg'); expect(targets[0].params.placementId).to.be.equal(99999996); expect(targets[0].params.dgkeyword).to.be.an('undefined'); - expect(targets[0].params.keywords).to.be.deep.equal(SUCCESS_RESULT); + expect(targets[0].ortb2Imp.ext.data.keywords).to.be.deep.equal(dgRtd.convertKeywordsToString(SUCCESS_RESULT)); expect(targets[2].bidder).to.be.equal('dg3'); expect(targets[2].params.placementId).to.be.equal(99999994); expect(targets[2].params.dgkeyword).to.be.an('undefined'); - expect(targets[2].params.keywords).to.be.an('undefined'); + expect(targets[2].ortb2Imp).to.be.an('undefined'); if (!IGNORE_SET_ORTB2) { expect(pbjs.getBidderConfig()).to.be.deep.equal({ From c4bf6aefc4954e87d76d847a66603bffa68b5970 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:17:58 +0300 Subject: [PATCH 091/760] Smartyads Bid Adapter : add_regions (#10545) * SmartyadsBidAdapter/add_regions * fix docs --------- Co-authored-by: vrishko --- modules/smartyadsBidAdapter.js | 34 +++++++++++++++++-- modules/smartyadsBidAdapter.md | 18 ++++++---- test/spec/modules/smartyadsBidAdapter_spec.js | 6 +++- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 3e6c5cf360b..a7d194e8db4 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -6,7 +6,12 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'smartyads'; -const AD_URL = 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js'; +const adUrls = { + US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + SGP: 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' +} + const URL_SYNC = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a'; function isBidResponseValid(bid) { @@ -26,6 +31,25 @@ function isBidResponseValid(bid) { } } +function getAdUrlByRegion(bid) { + let adUrl; + + if (bid.params.region && adUrls[bid.params.region]) { + adUrl = adUrls[bid.params.region]; + } else { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + if (region === 'Europe') adUrl = adUrls['EU']; + else adUrl = adUrls['US_EAST']; + } catch (err) { + adUrl = adUrls['US_EAST']; + } + } + + return adUrl; +} + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -73,8 +97,11 @@ export const spec = { } const len = validBidRequests.length; + let adUrl; + for (let i = 0; i < len; i++) { let bid = validBidRequests[i]; + if (i === 0) adUrl = getAdUrlByRegion(bid); let traff = bid.params.traffic || BANNER placements.push({ placementId: bid.params.sourceid, @@ -87,11 +114,12 @@ export const spec = { placements.schain = bid.schain; } } + return { method: 'POST', - url: AD_URL, + url: adUrl, data: request - }; + } }, interpretResponse: (serverResponse) => { diff --git a/modules/smartyadsBidAdapter.md b/modules/smartyadsBidAdapter.md index e0d6023a794..443d5ab5978 100644 --- a/modules/smartyadsBidAdapter.md +++ b/modules/smartyadsBidAdapter.md @@ -14,10 +14,11 @@ Module that connects to SmartyAds' demand sources | Name | Scope | Description | Example | | :------------ | :------- | :------------------------ | :------------------- | -| `sourceid` | required (for prebid.js) | placement ID | "0" | -| `host` | required (for prebid-server) | const value, set to "prebid" | "prebid" | -| `accountid` | required (for prebid-server) | partner ID | "1901" | +| `sourceid` | required (for prebid.js) | Placement ID | "0" | +| `host` | required (for prebid-server) | Const value, set to "prebid" | "prebid" | +| `accountid` | required (for prebid-server) | Partner ID | "1901" | | `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | +| `region` | optional (for prebid.js) | Prefix of the region to which prebid must send requests. Possible values: "US_EAST", "EU" | "US_EAST" | # Test Parameters ``` @@ -35,7 +36,9 @@ Module that connects to SmartyAds' demand sources host: 'prebid', sourceid: '0', accountid: '0', - traffic: 'native' + traffic: 'native', + region: 'US_EAST' + } } ] @@ -55,7 +58,8 @@ Module that connects to SmartyAds' demand sources host: 'prebid', sourceid: '0', accountid: '0', - traffic: 'banner' + traffic: 'banner', + region: 'US_EAST' } } ] @@ -76,7 +80,9 @@ Module that connects to SmartyAds' demand sources host: 'prebid', sourceid: '0', accountid: '0', - traffic: 'video' + traffic: 'video', + region: 'US_EAST' + } } ] diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 992fff14f33..458ccc37759 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -52,7 +52,11 @@ describe('SmartyadsAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js'); + expect(serverRequest.url).to.be.oneOf([ + 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' + ]); }); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; From 1a40e5911810437da920bfa4c980aa2550732eb6 Mon Sep 17 00:00:00 2001 From: Neil Flynn Date: Tue, 10 Oct 2023 15:33:43 +0100 Subject: [PATCH 092/760] Kargo Bid Adapter: adding site to Kargo adapter (#10589) * KargoBidAdapter: GPP Support * kargo adapter to forward schain object (#21) * wrap in if statement (#22) * KRKPD-572: Add spec for schain (#23) * wrap in if statement * update test for schain, file formatting * Adding site to Kargo adapter. * KRKPD-619 Updating Site object. * KRKPD-619 Adding null check for Site object. * Update modules/kargoBidAdapter.js Co-authored-by: Julian Gan * Reducing the size of Site object. * remove white space that is causing linting error --------- Co-authored-by: Jeremy Sadwith Co-authored-by: Julian Gan --- modules/kargoBidAdapter.js | 6 ++++++ test/spec/modules/kargoBidAdapter_spec.js | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 1dde4453222..01470354755 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -97,6 +97,12 @@ function buildRequests(validBidRequests, bidderRequest) { user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), }); + if (firstBidRequest.ortb2 != null) { + krakenParams.site = { + cat: firstBidRequest.ortb2.site.cat + } + } + if (firstBidRequest.schain && firstBidRequest.schain.nodes) { krakenParams.schain = firstBidRequest.schain } diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 9f7a4854063..f7010c1886f 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -142,6 +142,11 @@ describe('kargo adapter tests', function () { model: 'model', source: 1, } + }, + site: { + id: '1234', + name: 'SiteName', + cat: ['IAB1', 'IAB2', 'IAB3'] } }, ortb2Imp: { @@ -439,6 +444,9 @@ describe('kargo adapter tests', function () { source: 1 }, }, + site: { + cat: ['IAB1', 'IAB2', 'IAB3'] + }, imp: [ { code: '101', From fe8918e40817e83fc80278a5948c74b4b0467012 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Tue, 10 Oct 2023 19:08:03 +0300 Subject: [PATCH 093/760] AdMatic Bid Adapter: add model param (#10585) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add --- modules/admaticBidAdapter.js | 1 + test/spec/modules/admaticBidAdapter_spec.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 52c06318ec0..eeb0cddde89 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -106,6 +106,7 @@ export const spec = { netRevenue: true, creativeId: bid.creative_id, meta: { + model: bid.mime_type, advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, bidder: bid.bidder, diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 8c9969e4d46..b378ec2d2a4 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -348,6 +348,7 @@ describe('admaticBidAdapter', () => { 'price': 0.01, 'type': 'banner', 'bidder': 'admatic', + 'mime_type': 'iframe', 'adomain': ['admatic.com.tr'], 'party_tag': '
', 'iurl': 'https://www.admatic.com.tr' @@ -359,6 +360,7 @@ describe('admaticBidAdapter', () => { 'height': 250, 'price': 0.01, 'type': 'video', + 'mime_type': 'iframe', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': '', @@ -371,6 +373,7 @@ describe('admaticBidAdapter', () => { 'height': 250, 'price': 0.01, 'type': 'video', + 'mime_type': 'iframe', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': 'https://www.admatic.com.tr', @@ -393,6 +396,7 @@ describe('admaticBidAdapter', () => { ad: '
', creativeId: '374', meta: { + model: 'iframe', advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -410,6 +414,7 @@ describe('admaticBidAdapter', () => { vastXml: '', creativeId: '3741', meta: { + model: 'iframe', advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -427,6 +432,7 @@ describe('admaticBidAdapter', () => { vastXml: 'https://www.admatic.com.tr', creativeId: '3741', meta: { + model: 'iframe', advertiserDomains: ['admatic.com.tr'] }, ttl: 60, From 353bad412e90a6de35a7007593966f76b5b8e578 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Tue, 10 Oct 2023 18:10:43 +0200 Subject: [PATCH 094/760] add ortb2Imp in Greenbids analytics module payload (#10591) --- modules/greenbidsAnalyticsAdapter.js | 1 + .../modules/greenbidsAnalyticsAdapter_spec.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index edc0c9c6c5c..2189172e16f 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -112,6 +112,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER ...(adUnit.mediaTypes.video !== undefined) && {video: adUnit.mediaTypes.video}, ...(adUnit.mediaTypes.native !== undefined) && {native: adUnit.mediaTypes.native} }, + ortb2Imp: adUnit.ortb2Imp || {}, bidders: [], }); }); diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 870fbd23870..3cfdc9b9749 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -246,6 +246,13 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { skip: 1, protocols: [1, 2, 3, 4] }, + }, + ortb2Imp: { + ext: { + data: { + adunitDFP: 'adunitcustomPathExtension' + } + } } }, ], @@ -266,6 +273,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { sizes: [[300, 250], [300, 600]] } }, + ortb2Imp: {}, bidders: [ { bidder: 'greenbids', @@ -281,6 +289,13 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }, { code: 'adunit-2', + ortb2Imp: { + ext: { + data: { + adunitDFP: 'adunitcustomPathExtension' + } + } + }, mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] From 6c7f17fff75dc2cf494d8ea9977ae7b5a75f578d Mon Sep 17 00:00:00 2001 From: msmeza Date: Tue, 10 Oct 2023 19:00:03 +0200 Subject: [PATCH 095/760] Prebid core: cached bids should contain the ID of the auction they actually won (#10541) * Cached bids should contain the ID of the auction they actually won * Property latestTargetedAuctionId should be set on all bids in the pool --- src/targeting.js | 6 ++++++ test/spec/unit/core/targeting_spec.js | 22 ++++++++++++++++++++++ test/spec/unit/pbjs_api_spec.js | 16 +++++++++++----- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/targeting.js b/src/targeting.js index 0aa395aa9a3..e46d1ae47a4 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -470,6 +470,12 @@ export function newTargeting(auctionManager) { .filter(bid => deepAccess(bid, 'video.context') !== ADPOD) .filter(isBidUsable); + bidsReceived + .forEach(bid => { + bid.latestTargetedAuctionId = latestAuctionForAdUnit[bid.adUnitCode]; + return bid; + }); + return getHighestCpmBidsFromBidPool(bidsReceived, getOldestHighestCpmBid); } diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 4716e5749cb..ba9aeff70d1 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -955,6 +955,7 @@ describe('targeting tests', function () { expect(bids.length).to.equal(1); expect(bids[0].adId).to.equal('adid-1'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); useBidCache = false; @@ -962,6 +963,7 @@ describe('targeting tests', function () { expect(bids.length).to.equal(1); expect(bids[0].adId).to.equal('adid-2'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); }); it('should use bidCacheFilterFunction', function() { @@ -989,9 +991,13 @@ describe('targeting tests', function () { expect(bids.length).to.equal(4); expect(bids[0].adId).to.equal('adid-1'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); expect(bids[1].adId).to.equal('adid-4'); + expect(bids[1].latestTargetedAuctionId).to.equal(2); expect(bids[2].adId).to.equal('adid-5'); + expect(bids[2].latestTargetedAuctionId).to.equal(2); expect(bids[3].adId).to.equal('adid-8'); + expect(bids[3].latestTargetedAuctionId).to.equal(2); // Bid Caching Off, No Filter Function useBidCache = false; @@ -1000,9 +1006,13 @@ describe('targeting tests', function () { expect(bids.length).to.equal(4); expect(bids[0].adId).to.equal('adid-2'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); expect(bids[1].adId).to.equal('adid-4'); + expect(bids[1].latestTargetedAuctionId).to.equal(2); expect(bids[2].adId).to.equal('adid-6'); + expect(bids[2].latestTargetedAuctionId).to.equal(2); expect(bids[3].adId).to.equal('adid-8'); + expect(bids[3].latestTargetedAuctionId).to.equal(2); // Bid Caching On AGAIN, No Filter Function (should be same as first time) useBidCache = true; @@ -1011,9 +1021,13 @@ describe('targeting tests', function () { expect(bids.length).to.equal(4); expect(bids[0].adId).to.equal('adid-1'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); expect(bids[1].adId).to.equal('adid-4'); + expect(bids[1].latestTargetedAuctionId).to.equal(2); expect(bids[2].adId).to.equal('adid-5'); + expect(bids[2].latestTargetedAuctionId).to.equal(2); expect(bids[3].adId).to.equal('adid-8'); + expect(bids[3].latestTargetedAuctionId).to.equal(2); // Bid Caching On, with Filter Function to Exclude video useBidCache = true; @@ -1026,9 +1040,13 @@ describe('targeting tests', function () { expect(bids.length).to.equal(4); expect(bids[0].adId).to.equal('adid-1'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); expect(bids[1].adId).to.equal('adid-4'); + expect(bids[1].latestTargetedAuctionId).to.equal(2); expect(bids[2].adId).to.equal('adid-6'); + expect(bids[2].latestTargetedAuctionId).to.equal(2); expect(bids[3].adId).to.equal('adid-8'); + expect(bids[3].latestTargetedAuctionId).to.equal(2); // filter function should have been called for each cached bid (4 times) expect(bcffCalled).to.equal(4); @@ -1044,9 +1062,13 @@ describe('targeting tests', function () { expect(bids.length).to.equal(4); expect(bids[0].adId).to.equal('adid-2'); + expect(bids[0].latestTargetedAuctionId).to.equal(2); expect(bids[1].adId).to.equal('adid-4'); + expect(bids[1].latestTargetedAuctionId).to.equal(2); expect(bids[2].adId).to.equal('adid-6'); + expect(bids[2].latestTargetedAuctionId).to.equal(2); expect(bids[3].adId).to.equal('adid-8'); + expect(bids[3].latestTargetedAuctionId).to.equal(2); // filter function should not have been called expect(bcffCalled).to.equal(0); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index b39c984316a..664f7ebb58f 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -25,6 +25,7 @@ import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {createBid} from '../../../src/bidfactory.js'; import {enrichFPD} from '../../../src/fpd/enrichment.js'; import {mockFpdEnrichments} from '../../helpers/fpd.js'; +import {generateUUID} from '../../../src/utils.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -42,11 +43,12 @@ var adUnits = getAdUnits(); var adUnitCodes = getAdUnits().map(unit => unit.code); var bidsBackHandler = function() {}; const timeout = 2000; +const auctionId = generateUUID(); let auction; function resetAuction() { if (auction == null) { - auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout: timeout}); + auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout: timeout, labels: undefined, auctionId: auctionId}); } $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: false }); auction.getBidRequests = getBidRequests; @@ -3302,16 +3304,20 @@ describe('Unit: Prebid Module', function () { const highestBid = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); expect(highestBid).to.deep.equal(_bidsReceived[2]) }) - }) + }); - describe('getHighestCpm', () => { + describe('getHighestCpmBids', () => { after(() => { resetAuction(); }); it('returns an array containing the highest bid object for the given adUnitCode', function () { - const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids('/19968336/header-bid-tag-0'); + const adUnitcode = '/19968336/header-bid-tag-0'; + targeting.setLatestAuctionForAdUnit(adUnitcode, auctionId) + const highestCpmBids = $$PREBID_GLOBAL$$.getHighestCpmBids(adUnitcode); expect(highestCpmBids.length).to.equal(1); - expect(highestCpmBids[0]).to.deep.equal(auctionManager.getBidsReceived()[1]); + const expectedBid = auctionManager.getBidsReceived()[1]; + expectedBid.latestTargetedAuctionId = auctionId; + expect(highestCpmBids[0]).to.deep.equal(expectedBid); }); it('returns an empty array when the given adUnit is not found', function () { From e86dfcf4148c16a00a9899ff38ffdb387b94863c Mon Sep 17 00:00:00 2001 From: Kevin Park <109548315+kevpark02@users.noreply.github.com> Date: Tue, 10 Oct 2023 12:14:16 -0700 Subject: [PATCH 096/760] Underdogmedia Bid Adapter: Update pbTimeout Assignment (#10466) * Update assigning pbTimeout to our payload * Revert unintended change * Add appropriate unit tests for timeout --- modules/underdogmediaBidAdapter.js | 2 +- test/spec/modules/underdogmediaBidAdapter_spec.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 4c2bdfe175f..54b74c7ccd4 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -71,7 +71,7 @@ export const spec = { let data = { dt: 10, gdpr: {}, - pbTimeout: config.getConfig('bidderTimeout'), + pbTimeout: +config.getConfig('bidderTimeout') || 3001, // KP: convert to number and if NaN we default to 3001. Particular value to let us know that there was a problem in converting pbTimeout pbjsVersion: prebidVersion, placements: [], ref: deepAccess(bidderRequest, 'refererInfo.page') ? bidderRequest.refererInfo.page : undefined, diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 2d7c1f11178..c0e2e8dddce 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -5,6 +5,7 @@ import { spec, resetUserSync } from 'modules/underdogmediaBidAdapter.js'; +import { config } from '../../../src/config'; describe('UnderdogMedia adapter', function () { let bidRequests; @@ -763,6 +764,20 @@ describe('UnderdogMedia adapter', function () { expect(request.data.ref).to.equal(undefined); }); + it('should have pbTimeout to be 3001 if bidder timeout does not exists', function () { + config.setConfig({ bidderTimeout: '' }) + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.pbTimeout).to.equal(3001) + }) + + it('should have pbTimeout to be a numerical value if bidder timeout is in a string', function () { + config.setConfig({ bidderTimeout: '1000' }) + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.pbTimeout).to.equal(1000) + }) + it('should have pubcid if it exists', function () { let bidRequests = [{ adUnitCode: 'div-gpt-ad-1460505748561-0', From 82ef88f69b6b2e79109805994e5bb3f6705edf5b Mon Sep 17 00:00:00 2001 From: carsten1980 <45483737+carsten1980@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:40:44 +0200 Subject: [PATCH 097/760] Adspirit Bid Adapter : initial release (#10494) * Add files via upload * Add files via upload --- modules/adspiritBidAdapter.js | 129 ++++++++++++++++++++++++++++++++++ modules/adspiritBidAdapter.md | 28 ++++++++ 2 files changed, 157 insertions(+) create mode 100644 modules/adspiritBidAdapter.js create mode 100644 modules/adspiritBidAdapter.md diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js new file mode 100644 index 00000000000..da8676f6c9b --- /dev/null +++ b/modules/adspiritBidAdapter.js @@ -0,0 +1,129 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +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 : ['xapadsmedia', 'connectad','twiago'], + supportedMediaTypes: [BANNER, NATIVE], + + isBidRequestValid: function (bid) + { + let host = spec.getBidderHost(bid); + if(!host) + { + return false; + } + if(!bid.params.placementId) + { + return false; + } + return true; + }, + buildRequests : function (validBidRequests, bidderRequest) + { + let requests = []; + let bidRequest; + let reqUrl; + let placementId; + for(let i = 0; i < validBidRequests.length; i++) + { + bidRequest = validBidRequests[i]; + bidRequest.adspiritConId = spec.genAdConId(bidRequest); + reqUrl = spec.getBidderHost(bidRequest); + placementId = utils.getBidIdParameter('placementId', bidRequest.params); + reqUrl = '//' + reqUrl + RTB_URL + '&pid=' + placementId + '&ref=' + encodeURIComponent(bidderRequest.refererInfo.topmostLocation) + '&scx=' + (screen.width) + '&scy=' + (screen.height) + '&wcx=' + ('innerWidth' in window ? window.innerWidth : page.clientWidth) + '&wcy=' + ('innerHeight' in window ? window.innerHeight : page.clientHeight) + '&async=' + bidRequest.adspiritConId + '&t=' + Math.round( + Math.random() * 100000); + requests.push({ + method : 'GET', + url : reqUrl, + 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); + if('mediaTypes' in bidObj && 'native' in bidObj.mediaTypes) + { + const bidResponse = { + requestId : bidObj.bidId, + cpm : cpm, + width : adData.w, + height : adData.h, + creativeId: bidObj.params.placementId, + currency : 'EUR', + netRevenue: true, + ttl : 300, + native : { + title : adData.title, + body : adData.body, + cta : adData.cta, + image : {url: adData.image}, + clickUrl : adData.click, + impressionTrackers: [adData.view] + }, + mediaType : NATIVE + }; + } + else + { + let adm = 'window.inDapIF=false' + '' + adData.adm; + + const bidResponse = { + requestId : bidObj.bidId, + cpm : cpm, + width : adData.w, + height : adData.h, + creativeId: bidObj.params.placementId, + currency : 'EUR', + netRevenue: true, + ttl : 300, + ad : adm, + mediaType : BANNER + }; + } + bidResponses.push(bidResponse); + return bidResponses; + }, + getBidderHost : function (bid) + { + if(bid.bidder === 'adspirit') + { + return utils.getBidIdParameter('host', bid.params); + } + if(bid.bidder === 'connectad') + { + return 'connected-by.connectad.io'; + } + if(bid.bidder === 'xapadsmedia') + { + return 'dsp.xapads.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..688d0814882 --- /dev/null +++ b/modules/adspiritBidAdapter.md @@ -0,0 +1,28 @@ +# Overview + +**Module Name**: AdSpirit Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@adspirit.de + +# Description + +Module that connects to an AdSpirit zone to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'display-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "adspirit", + params: { + placementId: '5', + host: 'n1test.adspirit.de' + } + } + ] + } + ]; +``` From 489e242a8ba0e6e5e316d5ac0cb128048cb65871 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 11 Oct 2023 05:03:22 -0600 Subject: [PATCH 098/760] Revert "Adspirit Bid Adapter : initial release (#10494)" (#10595) This reverts commit 82ef88f69b6b2e79109805994e5bb3f6705edf5b. --- modules/adspiritBidAdapter.js | 129 ---------------------------------- modules/adspiritBidAdapter.md | 28 -------- 2 files changed, 157 deletions(-) delete mode 100644 modules/adspiritBidAdapter.js delete mode 100644 modules/adspiritBidAdapter.md diff --git a/modules/adspiritBidAdapter.js b/modules/adspiritBidAdapter.js deleted file mode 100644 index da8676f6c9b..00000000000 --- a/modules/adspiritBidAdapter.js +++ /dev/null @@ -1,129 +0,0 @@ -import * as utils from '../src/utils'; -import {registerBidder} from '../src/adapters/bidderFactory'; -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 : ['xapadsmedia', 'connectad','twiago'], - supportedMediaTypes: [BANNER, NATIVE], - - isBidRequestValid: function (bid) - { - let host = spec.getBidderHost(bid); - if(!host) - { - return false; - } - if(!bid.params.placementId) - { - return false; - } - return true; - }, - buildRequests : function (validBidRequests, bidderRequest) - { - let requests = []; - let bidRequest; - let reqUrl; - let placementId; - for(let i = 0; i < validBidRequests.length; i++) - { - bidRequest = validBidRequests[i]; - bidRequest.adspiritConId = spec.genAdConId(bidRequest); - reqUrl = spec.getBidderHost(bidRequest); - placementId = utils.getBidIdParameter('placementId', bidRequest.params); - reqUrl = '//' + reqUrl + RTB_URL + '&pid=' + placementId + '&ref=' + encodeURIComponent(bidderRequest.refererInfo.topmostLocation) + '&scx=' + (screen.width) + '&scy=' + (screen.height) + '&wcx=' + ('innerWidth' in window ? window.innerWidth : page.clientWidth) + '&wcy=' + ('innerHeight' in window ? window.innerHeight : page.clientHeight) + '&async=' + bidRequest.adspiritConId + '&t=' + Math.round( - Math.random() * 100000); - requests.push({ - method : 'GET', - url : reqUrl, - 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); - if('mediaTypes' in bidObj && 'native' in bidObj.mediaTypes) - { - const bidResponse = { - requestId : bidObj.bidId, - cpm : cpm, - width : adData.w, - height : adData.h, - creativeId: bidObj.params.placementId, - currency : 'EUR', - netRevenue: true, - ttl : 300, - native : { - title : adData.title, - body : adData.body, - cta : adData.cta, - image : {url: adData.image}, - clickUrl : adData.click, - impressionTrackers: [adData.view] - }, - mediaType : NATIVE - }; - } - else - { - let adm = 'window.inDapIF=false' + '' + adData.adm; - - const bidResponse = { - requestId : bidObj.bidId, - cpm : cpm, - width : adData.w, - height : adData.h, - creativeId: bidObj.params.placementId, - currency : 'EUR', - netRevenue: true, - ttl : 300, - ad : adm, - mediaType : BANNER - }; - } - bidResponses.push(bidResponse); - return bidResponses; - }, - getBidderHost : function (bid) - { - if(bid.bidder === 'adspirit') - { - return utils.getBidIdParameter('host', bid.params); - } - if(bid.bidder === 'connectad') - { - return 'connected-by.connectad.io'; - } - if(bid.bidder === 'xapadsmedia') - { - return 'dsp.xapads.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 deleted file mode 100644 index 688d0814882..00000000000 --- a/modules/adspiritBidAdapter.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview - -**Module Name**: AdSpirit Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: prebid@adspirit.de - -# Description - -Module that connects to an AdSpirit zone to fetch bids. - -# Test Parameters -``` - var adUnits = [ - { - code: 'display-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "adspirit", - params: { - placementId: '5', - host: 'n1test.adspirit.de' - } - } - ] - } - ]; -``` From 695da702ac07772caf6da72294c621d3387311a3 Mon Sep 17 00:00:00 2001 From: ssorleti <100858633+ssorleti@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:43:33 +0200 Subject: [PATCH 099/760] Bucksense bid adapter: add missing gvlid (#10600) --- modules/bucksenseBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 7b6c3911ea1..42edb783d00 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -8,6 +8,7 @@ const URL = 'https://directo.prebidserving.com/prebidjs/'; export const spec = { code: BIDDER_CODE, + gvlid: 235, supportedMediaTypes: [BANNER], /** From de82bae8962742204e438e2a0eac831972e00f90 Mon Sep 17 00:00:00 2001 From: vrtcal-dev <50931150+vrtcal-dev@users.noreply.github.com> Date: Thu, 12 Oct 2023 07:35:07 -0500 Subject: [PATCH 100/760] VRTCAL Bid Adapter : updated user sync support (#10579) * Updated User Sync Support * Simple var to let adjustment --------- Co-authored-by: Ubuntu --- modules/vrtcalBidAdapter.js | 29 ++++++++++++++++++ test/spec/modules/vrtcalBidAdapter_spec.js | 35 ++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/modules/vrtcalBidAdapter.js b/modules/vrtcalBidAdapter.js index de6a9fc1ffe..07dc35525c3 100644 --- a/modules/vrtcalBidAdapter.js +++ b/modules/vrtcalBidAdapter.js @@ -5,6 +5,8 @@ import { config } from '../src/config.js'; import {deepAccess, isFn, isPlainObject} from '../src/utils.js'; const GVLID = 706; +const VRTCAL_USER_SYNC_URL_IFRAME = `https://usync.vrtcal.com/i?ssp=1804&synctype=iframe`; +const VRTCAL_USER_SYNC_URL_REDIRECT = `https://usync.vrtcal.com/i?ssp=1804&synctype=redirect`; export const spec = { code: 'vrtcal', @@ -148,7 +150,34 @@ export const spec = { ); ajax(winUrl, null); return true; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + const syncs = []; + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `&us_privacy=${encodeURIComponent(uspConsent)}`; + const gpp = gppConsent.gppString ? gppConsent.gppString : ''; + const gppSid = Array.isArray(gppConsent.applicableSections) ? gppConsent.applicableSections.join(',') : ''; + let vrtcalSyncURL = '' + + if (syncOptions.iframeEnabled) { + vrtcalSyncURL = `${VRTCAL_USER_SYNC_URL_IFRAME}${usPrivacy}${gdprFlag}${gdprString}&gpp=${gpp}&gpp_sid=${gppSid}&surl=`; + syncs.push({ + type: 'iframe', + url: vrtcalSyncURL + }); + } else { + vrtcalSyncURL = `${VRTCAL_USER_SYNC_URL_REDIRECT}${usPrivacy}${gdprFlag}${gdprString}&gpp=${gpp}&gpp_sid=${gppSid}&surl=`; + syncs.push({ + type: 'image', + url: vrtcalSyncURL + }); + } + + return syncs; } + }; registerBidder(spec); diff --git a/test/spec/modules/vrtcalBidAdapter_spec.js b/test/spec/modules/vrtcalBidAdapter_spec.js index cc4dc0a3882..938934170e9 100644 --- a/test/spec/modules/vrtcalBidAdapter_spec.js +++ b/test/spec/modules/vrtcalBidAdapter_spec.js @@ -134,4 +134,39 @@ describe('vrtcalBidAdapter', function () { ).to.be.true }) }) + + describe('getUserSyncs', function() { + const syncurl_iframe = 'https://usync.vrtcal.com/i?ssp=1804&synctype=iframe'; + const syncurl_redirect = 'https://usync.vrtcal.com/i?ssp=1804&synctype=redirect'; + + it('base iframe sync pper config', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + '&us_privacy=&gdpr=0&gdpr_consent=&gpp=&gpp_sid=&surl=' + }]); + }); + + it('base redirect sync per config', function() { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.deep.equal([{ + type: 'image', url: syncurl_redirect + '&us_privacy=&gdpr=0&gdpr_consent=&gpp=&gpp_sid=&surl=' + }]); + }); + + it('pass with ccpa data', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, 'ccpa_consent_string', undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + '&us_privacy=ccpa_consent_string&gdpr=0&gdpr_consent=&gpp=&gpp_sid=&surl=' + }]); + }); + + it('pass with gdpr data', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: 1, consentString: 'gdpr_consent_string'}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + '&us_privacy=&gdpr=1&gdpr_consent=gdpr_consent_string&gpp=&gpp_sid=&surl=' + }]); + }); + + it('pass with gpp data', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined, {gppString: 'gpp_consent_string', applicableSections: [1, 5]})).to.deep.equal([{ + type: 'iframe', url: syncurl_iframe + '&us_privacy=&gdpr=0&gdpr_consent=&gpp=gpp_consent_string&gpp_sid=1,5&surl=' + }]); + }); + }) }) From 86736ac4d5ee97cdecf772b68b37c36cca96f1e6 Mon Sep 17 00:00:00 2001 From: jkthomas Date: Thu, 12 Oct 2023 17:21:03 +0100 Subject: [PATCH 101/760] Utiq ID submodule: Update submodule name and parameters (#10587) Co-authored-by: Tomasz Januszek --- modules/.submodules.json | 2 +- modules/{utiqSystem.js => utiqIdSystem.js} | 8 +++--- modules/{utiqSystem.md => utiqIdSystem.md} | 7 +---- ...tiqSystem_spec.js => utiqIdSystem_spec.js} | 28 +++++++++---------- 4 files changed, 20 insertions(+), 25 deletions(-) rename modules/{utiqSystem.js => utiqIdSystem.js} (96%) rename modules/{utiqSystem.md => utiqIdSystem.md} (54%) rename test/spec/modules/{utiqSystem_spec.js => utiqIdSystem_spec.js} (86%) diff --git a/modules/.submodules.json b/modules/.submodules.json index fdc79c8b868..9f919724027 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -38,7 +38,7 @@ "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "utiqSystem", + "utiqIdSystem", "uid2IdSystem", "euidIdSystem", "unifiedIdSystem", diff --git a/modules/utiqSystem.js b/modules/utiqIdSystem.js similarity index 96% rename from modules/utiqSystem.js rename to modules/utiqIdSystem.js index 473dc5854a9..8228da3a629 100644 --- a/modules/utiqSystem.js +++ b/modules/utiqIdSystem.js @@ -1,7 +1,7 @@ /** * This module adds Utiq provided by Utiq SA/NV to the User ID module * The {@link module:modules/userId} module is required - * @module modules/utiqSystem + * @module modules/utiqIdSystem * @requires module:modules/userId */ import { logInfo } from '../src/utils.js'; @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -const MODULE_NAME = 'utiq'; +const MODULE_NAME = 'utiqId'; const LOG_PREFIX = 'Utiq module'; export const storage = getStorageManager({ @@ -56,7 +56,7 @@ function getUtiqFromStorage() { } /** @type {Submodule} */ -export const utiqSubmodule = { +export const utiqIdSubmodule = { /** * Used to link submodule with config * @type {string} @@ -135,4 +135,4 @@ export const utiqSubmodule = { } }; -submodule('userId', utiqSubmodule); +submodule('userId', utiqIdSubmodule); diff --git a/modules/utiqSystem.md b/modules/utiqIdSystem.md similarity index 54% rename from modules/utiqSystem.md rename to modules/utiqIdSystem.md index d2c53480383..c7f4f95827f 100644 --- a/modules/utiqSystem.md +++ b/modules/utiqIdSystem.md @@ -5,7 +5,7 @@ Utiq ID Module. First, make sure to add the utiq submodule to your Prebid.js package with: ``` -gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqSystem +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqIdSystem ``` ## Parameter Descriptions @@ -15,8 +15,3 @@ gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,ut | name | String | The name of the module | `"utiq"` | | params | Object | Object with configuration parameters for utiq User Id submodule | - | | params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | -| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] | -| storage | Object | Local storage configuration object | - | -| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | -| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"utiq"` | -| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required. | `1` | diff --git a/test/spec/modules/utiqSystem_spec.js b/test/spec/modules/utiqIdSystem_spec.js similarity index 86% rename from test/spec/modules/utiqSystem_spec.js rename to test/spec/modules/utiqIdSystem_spec.js index afeeea7c3ea..62754d39fa3 100644 --- a/test/spec/modules/utiqSystem_spec.js +++ b/test/spec/modules/utiqIdSystem_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { utiqSubmodule } from 'modules/utiqSystem.js'; -import { storage } from 'modules/utiqSystem.js'; +import { utiqIdSubmodule } from 'modules/utiqIdSystem.js'; +import { storage } from 'modules/utiqIdSystem.js'; -describe('utiqSystem', () => { +describe('utiqIdSystem', () => { const utiqPassKey = 'utiqPass'; const getStorageData = (idGraph) => { @@ -17,7 +17,7 @@ describe('utiqSystem', () => { }; it('should have the correct module name declared', () => { - expect(utiqSubmodule.name).to.equal('utiq'); + expect(utiqIdSubmodule.name).to.equal('utiqId'); }); describe('utiq getId()', () => { @@ -26,13 +26,13 @@ describe('utiqSystem', () => { }); it('it should return object with key callback', () => { - expect(utiqSubmodule.getId()).to.have.property('callback'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); }); it('should return object with key callback with value type - function', () => { storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); - expect(utiqSubmodule.getId()).to.have.property('callback'); - expect(typeof utiqSubmodule.getId().callback).to.be.equal('function'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqIdSubmodule.getId().callback).to.be.equal('function'); }); it('tests if localstorage & JSON works properly ', () => { @@ -50,7 +50,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); @@ -61,7 +61,7 @@ describe('utiqSystem', () => { 'domain': 'test.domain', 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -82,7 +82,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -105,7 +105,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId({params: {maxDelayTime: 200}}); + const response = utiqIdSubmodule.getId({params: {maxDelayTime: 200}}); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -138,7 +138,7 @@ describe('utiqSystem', () => { ]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the utiq for a payload with {utiq: value}', () => { - expect(utiqSubmodule.decode(responseData.payload)).to.deep.equal( + expect(utiqIdSubmodule.decode(responseData.payload)).to.deep.equal( {utiq: responseData.expected} ); }); @@ -146,7 +146,7 @@ describe('utiqSystem', () => { [{}, '', {foo: 'bar'}].forEach((response) => { it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { - expect(utiqSubmodule.decode(response)).to.be.null; + expect(utiqIdSubmodule.decode(response)).to.be.null; }); }); }); @@ -177,7 +177,7 @@ describe('utiqSystem', () => { window.dispatchEvent(new MessageEvent('message', eventData)); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); From e1f446c8550fbd24b201bf6233af7bd9cf066134 Mon Sep 17 00:00:00 2001 From: mickannese Date: Fri, 13 Oct 2023 04:23:36 -0700 Subject: [PATCH 102/760] add rtd module to submodules (#10603) Co-authored-by: Mick --- modules/.submodules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/.submodules.json b/modules/.submodules.json index 9f919724027..8253307c36b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -75,6 +75,7 @@ "oneKeyRtdProvider", "optimeraRtdProvider", "permutiveRtdProvider", + "qortexRtdProvider", "reconciliationRtdProvider", "sirdataRtdProvider", "timeoutRtdProvider", From 2b3426d6cc135bc9f4c903b2270234fcfaf649f1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 13 Oct 2023 09:02:41 -0400 Subject: [PATCH 103/760] Revert "Utiq ID submodule: Update submodule name and parameters (#10587)" (#10606) This reverts commit 86736ac4d5ee97cdecf772b68b37c36cca96f1e6. --- modules/.submodules.json | 2 +- modules/{utiqIdSystem.js => utiqSystem.js} | 8 +++--- modules/{utiqIdSystem.md => utiqSystem.md} | 7 ++++- ...tiqIdSystem_spec.js => utiqSystem_spec.js} | 28 +++++++++---------- 4 files changed, 25 insertions(+), 20 deletions(-) rename modules/{utiqIdSystem.js => utiqSystem.js} (96%) rename modules/{utiqIdSystem.md => utiqSystem.md} (54%) rename test/spec/modules/{utiqIdSystem_spec.js => utiqSystem_spec.js} (86%) diff --git a/modules/.submodules.json b/modules/.submodules.json index 8253307c36b..5699cbfdc87 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -38,7 +38,7 @@ "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "utiqIdSystem", + "utiqSystem", "uid2IdSystem", "euidIdSystem", "unifiedIdSystem", diff --git a/modules/utiqIdSystem.js b/modules/utiqSystem.js similarity index 96% rename from modules/utiqIdSystem.js rename to modules/utiqSystem.js index 8228da3a629..473dc5854a9 100644 --- a/modules/utiqIdSystem.js +++ b/modules/utiqSystem.js @@ -1,7 +1,7 @@ /** * This module adds Utiq provided by Utiq SA/NV to the User ID module * The {@link module:modules/userId} module is required - * @module modules/utiqIdSystem + * @module modules/utiqSystem * @requires module:modules/userId */ import { logInfo } from '../src/utils.js'; @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -const MODULE_NAME = 'utiqId'; +const MODULE_NAME = 'utiq'; const LOG_PREFIX = 'Utiq module'; export const storage = getStorageManager({ @@ -56,7 +56,7 @@ function getUtiqFromStorage() { } /** @type {Submodule} */ -export const utiqIdSubmodule = { +export const utiqSubmodule = { /** * Used to link submodule with config * @type {string} @@ -135,4 +135,4 @@ export const utiqIdSubmodule = { } }; -submodule('userId', utiqIdSubmodule); +submodule('userId', utiqSubmodule); diff --git a/modules/utiqIdSystem.md b/modules/utiqSystem.md similarity index 54% rename from modules/utiqIdSystem.md rename to modules/utiqSystem.md index c7f4f95827f..d2c53480383 100644 --- a/modules/utiqIdSystem.md +++ b/modules/utiqSystem.md @@ -5,7 +5,7 @@ Utiq ID Module. First, make sure to add the utiq submodule to your Prebid.js package with: ``` -gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqIdSystem +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqSystem ``` ## Parameter Descriptions @@ -15,3 +15,8 @@ gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,ut | name | String | The name of the module | `"utiq"` | | params | Object | Object with configuration parameters for utiq User Id submodule | - | | params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | +| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] | +| storage | Object | Local storage configuration object | - | +| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | +| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"utiq"` | +| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required. | `1` | diff --git a/test/spec/modules/utiqIdSystem_spec.js b/test/spec/modules/utiqSystem_spec.js similarity index 86% rename from test/spec/modules/utiqIdSystem_spec.js rename to test/spec/modules/utiqSystem_spec.js index 62754d39fa3..afeeea7c3ea 100644 --- a/test/spec/modules/utiqIdSystem_spec.js +++ b/test/spec/modules/utiqSystem_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { utiqIdSubmodule } from 'modules/utiqIdSystem.js'; -import { storage } from 'modules/utiqIdSystem.js'; +import { utiqSubmodule } from 'modules/utiqSystem.js'; +import { storage } from 'modules/utiqSystem.js'; -describe('utiqIdSystem', () => { +describe('utiqSystem', () => { const utiqPassKey = 'utiqPass'; const getStorageData = (idGraph) => { @@ -17,7 +17,7 @@ describe('utiqIdSystem', () => { }; it('should have the correct module name declared', () => { - expect(utiqIdSubmodule.name).to.equal('utiqId'); + expect(utiqSubmodule.name).to.equal('utiq'); }); describe('utiq getId()', () => { @@ -26,13 +26,13 @@ describe('utiqIdSystem', () => { }); it('it should return object with key callback', () => { - expect(utiqIdSubmodule.getId()).to.have.property('callback'); + expect(utiqSubmodule.getId()).to.have.property('callback'); }); it('should return object with key callback with value type - function', () => { storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); - expect(utiqIdSubmodule.getId()).to.have.property('callback'); - expect(typeof utiqIdSubmodule.getId().callback).to.be.equal('function'); + expect(utiqSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqSubmodule.getId().callback).to.be.equal('function'); }); it('tests if localstorage & JSON works properly ', () => { @@ -50,7 +50,7 @@ describe('utiqIdSystem', () => { 'atid': 'atidValue', }; storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); - const response = utiqIdSubmodule.getId(); + const response = utiqSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); @@ -61,7 +61,7 @@ describe('utiqIdSystem', () => { 'domain': 'test.domain', 'atid': 'atidValue', }; - const response = utiqIdSubmodule.getId(); + const response = utiqSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -82,7 +82,7 @@ describe('utiqIdSystem', () => { 'atid': 'atidValue', }; - const response = utiqIdSubmodule.getId(); + const response = utiqSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -105,7 +105,7 @@ describe('utiqIdSystem', () => { 'atid': 'atidValue', }; - const response = utiqIdSubmodule.getId({params: {maxDelayTime: 200}}); + const response = utiqSubmodule.getId({params: {maxDelayTime: 200}}); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -138,7 +138,7 @@ describe('utiqIdSystem', () => { ]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the utiq for a payload with {utiq: value}', () => { - expect(utiqIdSubmodule.decode(responseData.payload)).to.deep.equal( + expect(utiqSubmodule.decode(responseData.payload)).to.deep.equal( {utiq: responseData.expected} ); }); @@ -146,7 +146,7 @@ describe('utiqIdSystem', () => { [{}, '', {foo: 'bar'}].forEach((response) => { it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { - expect(utiqIdSubmodule.decode(response)).to.be.null; + expect(utiqSubmodule.decode(response)).to.be.null; }); }); }); @@ -177,7 +177,7 @@ describe('utiqIdSystem', () => { window.dispatchEvent(new MessageEvent('message', eventData)); - const response = utiqIdSubmodule.getId(); + const response = utiqSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); From 726633d66a951016220b8378c0b104f6e05483b7 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 13 Oct 2023 16:19:23 +0000 Subject: [PATCH 104/760] Prebid 8.19.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bc544036ad0..bf4d08f39db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.19.0-pre", + "version": "8.19.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 317e23b9a06..d32ff874668 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.19.0-pre", + "version": "8.19.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ba25f22603c196b827476cb7754da8f85c38e9b5 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 13 Oct 2023 16:19:24 +0000 Subject: [PATCH 105/760] Increment version to 8.20.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf4d08f39db..9544014e524 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.19.0", + "version": "8.20.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index d32ff874668..0f6c863d105 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.19.0", + "version": "8.20.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ea3930812bbc4637773772959d70e1d9a5fad0a7 Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Sat, 14 Oct 2023 02:13:29 +0530 Subject: [PATCH 106/760] PubmaticBidAdapter: Read and pass gpid from ortb2imp.ext in API call (#10577) * Pass gpid value from ortb2Imp.ext in translator call * Update pubmaticBidAdapter_spec.js kicking off tests by committing as me * Test * Test * Update pubmaticBidAdapter_spec.js * Commit to retrigger test cases --------- Co-authored-by: Patrick McCann --- modules/pubmaticBidAdapter.js | 3 ++ test/spec/modules/pubmaticBidAdapter_spec.js | 33 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 16d909c2fea..aa953fb295d 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -758,6 +758,9 @@ function _addImpressionFPD(imp, bid) { deepSetValue(imp, `ext.data.${prop}`, ortb2[prop]); } }); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); + gpid && deepSetValue(imp, `ext.gpid`, gpid); } function _addFloorFromFloorModule(impObj, bid) { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 066004bd954..a9f80b6e25a 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -82,6 +82,7 @@ describe('PubMatic adapter', function () { ortb2Imp: { ext: { tid: '92489f71-1bf2-49a0-adf9-000cea934729', + gpid: '/1111/homepage-leftnav' } }, schain: schainConfig @@ -1172,6 +1173,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid expect(data.imp[0].banner.w).to.equal(300); // width expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); @@ -1439,6 +1441,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].banner.w).to.equal(728); // width expect(data.imp[0].banner.h).to.equal(90); // height expect(data.imp[0].banner.format).to.deep.equal([{w: 160, h: 600}]); + expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); expect(data.imp[0].ext.key_val).to.exist.and.to.equal(bidRequests[0].params.dctr); expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); @@ -1663,6 +1666,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid expect(data.imp[0].banner.w).to.equal(300); // width expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid }); @@ -1711,6 +1715,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); expect(data.imp[0].banner.w).to.equal(300); // width expect(data.imp[0].banner.h).to.equal(250); // height expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid @@ -1759,6 +1764,7 @@ describe('PubMatic adapter', function () { expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid expect(data.imp[0].banner.w).to.equal(300); // width expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.gpid).to.equal(bidRequests[0].ortb2Imp.ext.gpid); expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid // second request without USP/CCPA @@ -1909,6 +1915,33 @@ describe('PubMatic adapter', function () { }); describe('ortb2Imp', function() { + describe('ortb2Imp.ext.gpid', function() { + beforeEach(function () { + if (bidRequests[0].hasOwnProperty('ortb2Imp')) { + delete bidRequests[0].ortb2Imp; + } + }); + + it('should send gpid if imp[].ext.gpid is specified', function() { + bidRequests[0].ortb2Imp = { + ext: { + gpid: 'ortb2Imp.ext.gpid' + } + }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.have.property('gpid'); + expect(data.imp[0].ext.gpid).to.equal('ortb2Imp.ext.gpid'); + }); + + it('should not send if imp[].ext.gpid is not specified', function() { + bidRequests[0].ortb2Imp = { ext: { } }; + const request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.imp[0].ext).to.not.have.property('gpid'); + }); + }); + describe('ortb2Imp.ext.data.pbadslot', function() { beforeEach(function () { if (bidRequests[0].hasOwnProperty('ortb2Imp')) { From 5f0e374ae8b9d3fc161c939a2586ffc0211b2edb Mon Sep 17 00:00:00 2001 From: Sajid Mahmood Date: Mon, 16 Oct 2023 11:19:35 -0400 Subject: [PATCH 107/760] IX Bid Adapter: add EUID abd Criteo to EID allowlist (#10609) * IX Bid Adapter: Add EUID abd Criteo to EID allowlist Co-authored-by: Sajid Mahmood * IX Bid Adapter: Add EUID abd Criteo to EID allowlist Co-authored-by: Sajid Mahmood --------- Co-authored-by: Sajid Mahmood --- modules/ixBidAdapter.js | 2 ++ test/spec/modules/ixBidAdapter_spec.js | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 8b6b32e83ec..810fa744b2e 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -76,6 +76,8 @@ const SOURCE_RTI_MAPPING = { 'audigent.com': '', // Hadron ID from Audigent, hadronId 'pubcid.org': '', // SharedID, pubcid 'utiq.com': '', // Utiq + 'criteo.com': '', // Criteo + 'euid.eu': '', // EUID 'intimatemerger.com': '', '33across.com': '', 'liveintent.indexexchange.com': '', diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index eb87f51b6f9..6968eac78fe 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -832,6 +832,8 @@ describe('IndexexchangeAdapter', function () { id5id: { uid: 'testid5id' }, // ID5 imuid: 'testimuid', '33acrossId': { envelope: 'v1.5fs.1000.fjdiosmclds' }, + 'criteoID': { envelope: 'testcriteoID' }, + 'euidID': { envelope: 'testeuid' }, pairId: {envelope: 'testpairId'} }; @@ -891,6 +893,16 @@ describe('IndexexchangeAdapter', function () { uids: [{ id: DEFAULT_USERID_DATA['33acrossId'].envelope }] + }, { + source: 'criteo.com', + uids: [{ + id: DEFAULT_USERID_DATA['criteoID'].envelope + }] + }, { + source: 'euid.eu', + uids: [{ + id: DEFAULT_USERID_DATA['euidID'].envelope + }] }, { source: 'google.com', uids: [{ @@ -1302,7 +1314,7 @@ describe('IndexexchangeAdapter', function () { const payload = extractPayload(request[0]); expect(request).to.be.an('array'); expect(request).to.have.lengthOf.above(0); // should be 1 or more - expect(payload.user.eids).to.have.lengthOf(9); + expect(payload.user.eids).to.have.lengthOf(11); expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); }); }); @@ -1490,7 +1502,7 @@ describe('IndexexchangeAdapter', function () { cloneValidBid[0].userIdAsEids = utils.deepClone(DEFAULT_USERIDASEIDS_DATA); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(9); + expect(payload.user.eids).to.have.lengthOf(11); expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); @@ -1623,7 +1635,7 @@ describe('IndexexchangeAdapter', function () { }) expect(payload.user).to.exist; - expect(payload.user.eids).to.have.lengthOf(11); + expect(payload.user.eids).to.have.lengthOf(13); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); @@ -1665,7 +1677,7 @@ describe('IndexexchangeAdapter', function () { }); const payload = extractPayload(request); - expect(payload.user.eids).to.have.lengthOf(10); + expect(payload.user.eids).to.have.lengthOf(12); expect(payload.user.eids).to.have.deep.members(validUserIdPayload); }); }); From 59cbc9c56f880fb18f6f9ba4330f298943b3cf13 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Mon, 16 Oct 2023 18:25:01 +0300 Subject: [PATCH 108/760] Adman Bid Adapter: refactoring gdpr consent string (#10599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability * сompatible with prebid v5 * add Lotame Panorama ID * update getUserSyncs * fix * fix tests * remove package-lock.json * update sync url * update test * add idx (UserID Module) * update tests * remove traffic param * handle transactionID param * send transactionID param in imp.ext * rename transactionID to transactionId * update tests * additional content handle * rollback content * content handle via hb integration * update gdprConsent * return old package-lock --------- Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman Co-authored-by: SmartyAdman <> --- modules/admanBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 2ee6ecfcb56..5eac02b7420 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -94,7 +94,9 @@ export const spec = { request.ccpa = bidderRequest.uspConsent; } if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; } if (content) { request.content = content; From 665f3567d9776b0e7686e0580027ad672901d8df Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Mon, 16 Oct 2023 23:00:15 -0400 Subject: [PATCH 109/760] Kargo Bid Adapter: Updates to GPID retrieval (#10618) * KargoBidAdapter: GPP Support * kargo adapter to forward schain object (#21) * wrap in if statement (#22) * KRKPD-572: Add spec for schain (#23) * wrap in if statement * update test for schain, file formatting * Adding site to Kargo adapter. * KRKPD-619 Updating Site object. * KRKPD-619 Adding null check for Site object. * Update modules/kargoBidAdapter.js Co-authored-by: Julian Gan * Reducing the size of Site object. * remove white space that is causing linting error * Kargo Bid Adapter: Updates to gpid retrieval --------- Co-authored-by: Julian Gan Co-authored-by: Neil Flynn --- modules/kargoBidAdapter.js | 27 ++--------------------- test/spec/modules/kargoBidAdapter_spec.js | 13 ++++++----- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 01470354755..02e64e42f00 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -469,8 +469,8 @@ function getImpression(bid) { imp.bidderWinCount = bid.bidderWinsCount; } - const gpid = getGPID(bid) - if (gpid != null && gpid != '') { + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { imp.fpd = { gpid: gpid } @@ -493,29 +493,6 @@ function getImpression(bid) { return imp } -function getGPID(bid) { - if (bid.ortb2Imp != null) { - if (bid.ortb2Imp.gpid != null && bid.ortb2Imp.gpid != '') { - return bid.ortb2Imp.gpid; - } - - if (bid.ortb2Imp.ext != null && bid.ortb2Imp.ext.data != null) { - if (bid.ortb2Imp.ext.data.pbAdSlot != null && bid.ortb2Imp.ext.data.pbAdSlot != '') { - return bid.ortb2Imp.ext.data.pbAdSlot; - } - - if (bid.ortb2Imp.ext.data.adServer != null && bid.ortb2Imp.ext.data.adServer.adSlot != null && bid.ortb2Imp.ext.data.adServer.adSlot != '') { - return bid.ortb2Imp.ext.data.adServer.adSlot; - } - } - } - - if (bid.adUnitCode != null && bid.adUnitCode != '') { - return bid.adUnitCode; - } - return ''; -} - export const spec = { gvlid: BIDDER.GVLID, code: BIDDER.CODE, diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index f7010c1886f..7a9d89eb3a1 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -155,9 +155,9 @@ describe('kargo adapter tests', function () { data: { adServer: { name: 'gam', - adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + adslot: '/22558409563,18834096/dfy_mobile_adhesion' }, - pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' }, gpid: '/22558409563,18834096/dfy_mobile_adhesion' } @@ -184,9 +184,9 @@ describe('kargo adapter tests', function () { data: { adServer: { name: 'gam', - adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + adslot: '/22558409563,18834096/dfy_mobile_adhesion' }, - pbAdSlot: '/22558409563,18834096/dfy_mobile_adhesion' + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' } } } @@ -209,9 +209,10 @@ describe('kargo adapter tests', function () { data: { adServer: { name: 'gam', - adSlot: '/22558409563,18834096/dfy_mobile_adhesion' + adslot: '/22558409563,18834096/dfy_mobile_adhesion' } - } + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' } } } From 20bbae29e0bd462d99e0204ff65263fccf832a99 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 17 Oct 2023 05:56:45 -0400 Subject: [PATCH 110/760] Various modules: Received spelling (#10616) * Update qortexRtdProvider.js * Update topicsFpdModule.js * Update priceFloors.js * Update pubmaticAnalyticsAdapter.js * Update qortexRtdProvider_spec.js * Update concertBidAdapter_spec.js * Update qortexRtdProvider.js --- modules/priceFloors.js | 4 ++-- modules/pubmaticAnalyticsAdapter.js | 2 +- modules/qortexRtdProvider.js | 8 ++++---- modules/topicsFpdModule.js | 4 ++-- test/spec/modules/concertBidAdapter_spec.js | 2 +- test/spec/modules/qortexRtdProvider_spec.js | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 07f8fbed45d..a56be71eac0 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -431,7 +431,7 @@ function validateSchemaFields(fields) { if (Array.isArray(fields) && fields.length > 0 && fields.every(field => allowedFields.indexOf(field) !== -1)) { return true; } - logError(`${MODULE_NAME}: Fields recieved do not match allowed fields`); + logError(`${MODULE_NAME}: Fields received do not match allowed fields`); return false; } @@ -616,7 +616,7 @@ function handleFetchError(status) { } /** - * This function handles sending and recieving the AJAX call for a floors fetch + * This function handles sending and receiving the AJAX call for a floors fetch * @param {object} floorsConfig the floors config coming from setConfig */ export function generateAndHandleFetch(floorEndpoint) { diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index a636b7340b0..a8f2b8d20ee 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -93,7 +93,7 @@ function copyRequiredBidDetails(bid) { 'bidderCode', 'adapterCode', 'bidId', - 'status', () => NO_BID, // default a bid to NO_BID until response is recieved or bid is timed out + 'status', () => NO_BID, // default a bid to NO_BID until response is received or bid is timed out 'finalSource as source', 'params', 'floorData', diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index a071436007a..7aa30334756 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -83,7 +83,7 @@ export function getContext () { */ export function addContextToRequests (reqBidsConfig) { if (currentSiteContext === null) { - logWarn('No context data recieved at this time'); + logWarn('No context data received at this time'); } else { const fragment = { site: {content: currentSiteContext} } if (bidderArray?.length > 0) { @@ -122,17 +122,17 @@ export function loadScriptTag(config) { case 'qx-impression': const {uid} = e.detail; if (!uid || impressionIds.has(uid)) { - logWarn(`recieved invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`) + logWarn(`received invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`) return; } else { - logMessage('recieved billable event: qx-impression') + logMessage('received billable event: qx-impression') impressionIds.add(uid) billableEvent.transactionId = e.detail.uid; events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, billableEvent); break; } default: - logWarn(`recieved invalid billable event: ${e.detail?.type}`) + logWarn(`received invalid billable event: ${e.detail?.type}`) } }) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 65c5777acf3..dffb8bc4860 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -160,7 +160,7 @@ export function getCachedTopics() { } /** - * Recieve messages from iframe loaded for bidders to fetch topic + * Receive messages from iframe loaded for bidders to fetch topic * @param {MessageEvent} evt */ export function receiveMessage(evt) { @@ -177,7 +177,7 @@ export function receiveMessage(evt) { } /** -Function to store Topics data recieved from iframe in storage(name: "prebid:topics") +Function to store Topics data received from iframe in storage(name: "prebid:topics") * @param {Topics} topics */ export function storeInLocalStorage(bidder, topics) { diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 40294deb26d..0a76ed3e62d 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -94,7 +94,7 @@ describe('ConcertAdapter', function () { }); describe('spec.isBidRequestValid', function() { - it('should return when it recieved all the required params', function() { + it('should return when it received all the required params', function() { const bid = bidRequests[0]; expect(spec.isBidRequestValid(bid)).to.equal(true); }); diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js index f6cf8798850..9baa526e4cc 100644 --- a/test/spec/modules/qortexRtdProvider_spec.js +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -168,21 +168,21 @@ describe('qortexRtdProvider', () => { dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); expect(billableEvents.length).to.be.equal(1); - expect(logWarnSpy.calledWith('recieved invalid billable event due to duplicate uid: qx-impression')).to.be.ok; + expect(logWarnSpy.calledWith('received invalid billable event due to duplicate uid: qx-impression')).to.be.ok; }) it('will not allow events with missing uid', () => { loadScriptTag(config); dispatchEvent(new CustomEvent('qortex-rtd', missingIdImpressionEvent)); expect(billableEvents.length).to.be.equal(0); - expect(logWarnSpy.calledWith('recieved invalid billable event due to missing uid: qx-impression')).to.be.ok; + expect(logWarnSpy.calledWith('received invalid billable event due to missing uid: qx-impression')).to.be.ok; }) it('will not allow events with unavailable type', () => { loadScriptTag(config); dispatchEvent(new CustomEvent('qortex-rtd', invalidTypeQortexEvent)); expect(billableEvents.length).to.be.equal(0); - expect(logWarnSpy.calledWith('recieved invalid billable event: invalid-type')).to.be.ok; + expect(logWarnSpy.calledWith('received invalid billable event: invalid-type')).to.be.ok; }) }) @@ -281,7 +281,7 @@ describe('qortexRtdProvider', () => { initializeModuleData(validModuleConfig); addContextToRequests(reqBidsConfig); expect(logWarnSpy.calledOnce).to.be.true; - expect(logWarnSpy.calledWith('No context data recieved at this time')).to.be.ok; + expect(logWarnSpy.calledWith('No context data received at this time')).to.be.ok; expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) From 3f6819b8b3ec5616da5c68d006d4f4bb3b9fc826 Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:16:38 +0800 Subject: [PATCH 111/760] FreeWheel Bid Adapter: support passing content object in config (#10588) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * FreeWheel-SSP-Adapter add userIdAsEids support * Freewheel-SSP-Adapter add test for eids * Freewheel SSP Adapter: add prebid version in request * code cleanup * FreeWheel SSP Bid Adapter: support video context and placement * update test * FreeWheel SSP Bid Adapter: add GPP support * Freewheel SSP Bid Adapter: test update * FreeWheel SSP Adapter: update the default value for video placement and context * update test * FreeWheel SSP Adapter: add support for video.plcmt * FreeWheel Adapter: support content object in config * code update --- modules/freewheel-sspBidAdapter.js | 9 +++++++++ test/spec/modules/freewheel-sspBidAdapter_spec.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index cd4785cdc78..1d733052f93 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -382,6 +382,15 @@ export const spec = { requestParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; } + // Add content object + if (typeof config.getConfig('content') === 'object') { + try { + requestParams._fw_prebid_content = JSON.stringify(config.getConfig('content')); + } catch (error) { + logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the content object: ' + error); + } + } + // Add schain object var schain = currentBidRequest.schain; if (schain) { diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index c42c5e2528d..6351d8956f9 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/freewheel-sspBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { createEidsArray } from 'modules/userId/eids.js'; +import { config } from 'src/config.js'; const ENDPOINT = '//ads.stickyadstv.com/www/delivery/swfIndex.php'; const PREBID_VERSION = '$prebid.version$'; @@ -117,6 +118,20 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; + it('should get correct value from content object', () => { + config.setConfig({ + content: { + 'title': 'freewheel', + 'series': 'abc', + 'id': 'iris_5e7' + } + }); + + const request = spec.buildRequests(bidRequests); + const payload = request[0].data; + expect(payload._fw_prebid_content).to.deep.equal('{\"title\":\"freewheel\",\"series\":\"abc\",\"id\":\"iris_5e7\"}'); + }); + it('should get bidfloor value from params if no getFloor method', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; From 4a3fcf5b1fe843f17ba6ba19d9c5549dacf44f2f Mon Sep 17 00:00:00 2001 From: abermanov-zeta <95416296+abermanov-zeta@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:37:22 +0200 Subject: [PATCH 112/760] Zeta Global SSP Bid Adapter: change required parameters. (#10597) --- modules/zeta_global_sspBidAdapter.js | 7 ++++--- .../modules/zeta_global_sspBidAdapter_spec.js | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 687afb6c692..db31791d86f 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -45,7 +45,7 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], /** - * Determines whether or not the given bid request is valid. + * Determines whether the given bid request is valid. * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. @@ -54,7 +54,8 @@ export const spec = { // check for all required bid fields if (!(bid && bid.bidId && - bid.params)) { + bid.params && + bid.params.sid)) { logWarn('Invalid bid request - missing required bid data'); return false; } @@ -162,7 +163,7 @@ export const spec = { } provideEids(validBidRequests[0], payload); - const url = params.shortname ? ENDPOINT_URL.concat('?shortname=', params.shortname) : ENDPOINT_URL; + const url = params.sid ? ENDPOINT_URL.concat('?sid=', params.sid) : ENDPOINT_URL; return { method: 'POST', url: url, diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 601f4546a29..d4fe28eff90 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -1,5 +1,6 @@ import {spec} from '../../../modules/zeta_global_sspBidAdapter.js' import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {deepClone} from '../../../src/utils'; describe('Zeta Ssp Bid Adapter', function () { const eids = [ @@ -50,7 +51,6 @@ describe('Zeta Ssp Bid Adapter', function () { someTag: 444, }, sid: 'publisherId', - shortname: 'test_shortname', tagid: 'test_tag_id', site: { page: 'testPage' @@ -253,11 +253,13 @@ describe('Zeta Ssp Bid Adapter', function () { }; it('Test the bid validation function', function () { - const validBid = spec.isBidRequestValid(bannerRequest[0]); - const invalidBid = spec.isBidRequestValid(null); + const invalidBid = deepClone(bannerRequest[0]); + invalidBid.params = {}; + const isValidBid = spec.isBidRequestValid(bannerRequest[0]); + const isInvalidBid = spec.isBidRequestValid(null); - expect(validBid).to.be.true; - expect(invalidBid).to.be.false; + expect(isValidBid).to.be.true; + expect(isInvalidBid).to.be.false; }); it('Test provide eids', function () { @@ -453,7 +455,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test required params in banner request', function () { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); const payload = JSON.parse(request.data); - expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?sid=publisherId'); expect(payload.ext.sid).to.eql('publisherId'); expect(payload.ext.tags.someTag).to.eql(444); expect(payload.ext.tags.shortname).to.be.undefined; @@ -462,7 +464,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test required params in video request', function () { const request = spec.buildRequests(videoRequest, videoRequest[0]); const payload = JSON.parse(request.data); - expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?sid=publisherId'); expect(payload.ext.sid).to.eql('publisherId'); expect(payload.ext.tags.someTag).to.eql(444); expect(payload.ext.tags.shortname).to.be.undefined; @@ -471,7 +473,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Test multi imp', function () { const request = spec.buildRequests(multiImpRequest, multiImpRequest[0]); const payload = JSON.parse(request.data); - expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?shortname=test_shortname'); + expect(request.url).to.eql('https://ssp.disqus.com/bid/prebid?sid=publisherId'); expect(payload.imp.length).to.eql(2); From 0dc6240716a2827fe976b60be2d82bf82868b49d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 08:43:46 -0700 Subject: [PATCH 113/760] Bump @babel/traverse from 7.20.1 to 7.23.2 (#10625) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.1 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 250 +++++++++++++++++++++++----------------------- 1 file changed, 127 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9544014e524..20819c7919e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.8.0-pre", + "version": "8.20.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -127,11 +127,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -193,12 +194,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz", - "integrity": "sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -310,9 +312,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } @@ -329,23 +331,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -465,28 +467,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -527,12 +529,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -540,9 +542,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz", - "integrity": "sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1612,31 +1614,31 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1645,12 +1647,12 @@ } }, "node_modules/@babel/types": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz", - "integrity": "sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -25310,11 +25312,12 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -25356,12 +25359,13 @@ } }, "@babel/generator": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz", - "integrity": "sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "requires": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -25442,9 +25446,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-explode-assignable-expression": { "version": "7.18.6", @@ -25455,20 +25459,20 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -25555,22 +25559,22 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.18.6", @@ -25599,19 +25603,19 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz", - "integrity": "sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -26303,39 +26307,39 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", - "integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.1", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.1", - "@babel/types": "^7.20.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.0.tgz", - "integrity": "sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, From f0f70f3fcb5757ea091c225d03300eaf10c5e0be Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Wed, 18 Oct 2023 18:51:45 +0300 Subject: [PATCH 114/760] SmartyadsBidAdapter/add_gvlid (#10626) Co-authored-by: vrishko --- modules/smartyadsBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index a7d194e8db4..2409bebbc59 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -6,6 +6,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'smartyads'; +const GVLID = 534; const adUrls = { US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', @@ -52,6 +53,7 @@ function getAdUrlByRegion(bid) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { From 52aeed0a39f3e9b8d2e671ba7078e680db4971ec Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Oct 2023 18:39:39 +0000 Subject: [PATCH 115/760] Prebid 8.20.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 20819c7919e..dcdff3e9267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.20.0-pre", + "version": "8.20.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 0f6c863d105..756f6d01e6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.20.0-pre", + "version": "8.20.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b42e33747eb9ee0c78cfec51484fda6e3f1bbb3d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 19 Oct 2023 18:39:40 +0000 Subject: [PATCH 116/760] Increment version to 8.21.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcdff3e9267..d18ae3dfd62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.20.0", + "version": "8.21.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 756f6d01e6c..a83e321b71b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.20.0", + "version": "8.21.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b4296cd2e8db73350c207a654f3bec955dd608d8 Mon Sep 17 00:00:00 2001 From: kmdevops <126434358+kmdevops@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:40:42 +0200 Subject: [PATCH 117/760] DXKulture Bid Adapter: implementing ORTB conversion library (#10584) --- modules/dxkultureBidAdapter.js | 407 ++------ modules/dxkultureBidAdapter.md | 10 +- test/spec/modules/dxkultureBidAdapter_spec.js | 936 ++++++++++-------- 3 files changed, 614 insertions(+), 739 deletions(-) diff --git a/modules/dxkultureBidAdapter.js b/modules/dxkultureBidAdapter.js index a8f7b4b86ba..9e4768d12bb 100644 --- a/modules/dxkultureBidAdapter.js +++ b/modules/dxkultureBidAdapter.js @@ -1,39 +1,86 @@ import { - deepSetValue, logInfo, - deepAccess, logError, - isFn, - isPlainObject, - isStr, - isNumber, - isArray, logMessage + logMessage, + deepAccess, + deepSetValue, + mergeDeep } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' const BIDDER_CODE = 'dxkulture'; const DEFAULT_BID_TTL = 300; -const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; -const DEFAULT_NETWORK_ID = 1; -const OPENRTB_VIDEO_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'placement', - 'plcmt', - 'protocols', - 'startdelay', - 'skip', - 'skipafter', - 'minbitrate', - 'maxbitrate', - 'delivery', - 'playbackmethod', - 'api', - 'linearity' -]; +const DEFAULT_CURRENCY = 'USD'; +const SYNC_URL = 'https://ads.kulture.media/usync'; + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + if (!imp.bidfloor) { + imp.bidfloor = bidRequest.params.bidfloor || 0; + imp.bidfloorcur = bidRequest.params.currency || DEFAULT_CURRENCY; + } + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + ext: { + hb: 1, + prebidver: '$prebid.version$', + adapterver: '1.0.0', + } + }) + + // Attaching GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(req, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + return req; + }, + bidResponse(buildBidResponse, bid, context) { + let resMediaType; + if (bid.adm?.trim().startsWith(' 0) { - deepSetValue(openrtbRequest, 'user.ext.eids', eids); - } + const data = converter.toORTB({ bidRequests: validBidRequests, bidderRequest, context: {contextMediaType} }); let publisherId = validBidRequests[0].params.publisherId; let placementId = validBidRequests[0].params.placementId; - const networkId = validBidRequests[0].params.networkId || DEFAULT_NETWORK_ID; if (validBidRequests[0].params.e2etest) { - logMessage('E2E test mode enabled'); + logMessage('dxkulture: E2E test mode enabled'); publisherId = 'e2etest' } let baseEndpoint = spec.ENDPOINT + '?pid=' + publisherId; @@ -111,70 +123,46 @@ export const spec = { if (placementId) { baseEndpoint += '&placementId=' + placementId } - if (networkId) { - baseEndpoint += '&nId=' + networkId - } - const payloadString = JSON.stringify(openrtbRequest); return { method: 'POST', url: baseEndpoint, - data: payloadString, + data: data }; }, - interpretResponse: function (serverResponse) { - const bidResponses = []; - const response = (serverResponse || {}).body; - // response is always one seat (exchange) with (optional) bids for each impression - if (response && response.seatbid && response.seatbid.length === 1 && response.seatbid[0].bid && response.seatbid[0].bid.length) { - response.seatbid[0].bid.forEach(bid => { - if (bid.adm && bid.price) { - bidResponses.push(_createBidResponse(bid)); - } - }) - } else { - logInfo('dxkulture.interpretResponse :: no valid responses to interpret'); - } - return bidResponses; + interpretResponse: function (serverResponse, bidRequest) { + const bids = converter.fromORTB({response: serverResponse.body, request: bidRequest.data}).bids; + return bids; }, - getUserSyncs: function (syncOptions, serverResponses) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { logInfo('dxkulture.getUserSyncs', 'syncOptions', syncOptions, 'serverResponses', serverResponses); + let syncs = []; if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { return syncs; } - serverResponses.forEach(resp => { - const userSync = deepAccess(resp, 'body.ext.usersync'); - if (userSync) { - let syncDetails = []; - Object.keys(userSync).forEach(key => { - const value = userSync[key]; - if (value.syncs && value.syncs.length) { - syncDetails = syncDetails.concat(value.syncs); - } - }); - syncDetails.forEach(syncDetails => { - syncs.push({ - type: syncDetails.type === 'iframe' ? 'iframe' : 'image', - url: syncDetails.url - }); - }); - - if (!syncOptions.iframeEnabled) { - syncs = syncs.filter(s => s.type !== 'iframe') - } - if (!syncOptions.pixelEnabled) { - syncs = syncs.filter(s => s.type !== 'image') - } + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let queryParamStrings = []; + let syncUrl = SYNC_URL; + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); } - }); - logInfo('dxkulture.getUserSyncs result=%o', syncs); - return syncs; - }, + + return [{ + type: pixelType, + url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + }]; + } + } }; @@ -182,16 +170,10 @@ export const spec = { * Util Functions *======================================= */ -/** - * @param {BidRequest} bidRequest bid request - */ function hasBannerMediaType(bidRequest) { return !!deepAccess(bidRequest, 'mediaTypes.banner'); } -/** - * @param {BidRequest} bidRequest bid request - */ function hasVideoMediaType(bidRequest) { return !!deepAccess(bidRequest, 'mediaTypes.video'); } @@ -206,12 +188,12 @@ function _validateParams(bidRequest) { } if (!bidRequest.params.publisherId) { - logError('Validation failed: publisherId not declared'); + logError('dxkulture: Validation failed: publisherId not declared'); return false; } if (!bidRequest.params.placementId) { - logError('Validation failed: placementId not declared'); + logError('dxkulture: Validation failed: placementId not declared'); return false; } @@ -266,204 +248,31 @@ function _validateVideo(bidRequest) { }; if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) { - logError('Validation failed: mimes are invalid'); + logError('dxkulture: Validation failed: mimes are invalid'); return false; } if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) { - logError('Validation failed: protocols are invalid'); + logError('dxkulture: Validation failed: protocols are invalid'); return false; } if (!videoParams.context) { - logError('Validation failed: context id not declared'); + logError('dxkulture: Validation failed: context id not declared'); return false; } if (videoParams.context !== 'instream') { - logError('Validation failed: only context instream is supported '); + logError('dxkulture: Validation failed: only context instream is supported '); return false; } if (typeof videoParams.playerSize === 'undefined' || !Array.isArray(videoParams.playerSize) || !Array.isArray(videoParams.playerSize[0])) { - logError('Validation failed: player size not declared or is not in format [[w,h]]'); + logError('dxkulture: Validation failed: player size not declared or is not in format [[w,h]]'); return false; } return true; } -/** - * Prepares video request data. - * - * @param bidRequest - * @param bidderRequest - * @returns openrtbRequest - */ -function buildVideoRequestData(bidRequest, bidderRequest) { - const {params} = bidRequest; - - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); - const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); - - const videoParams = { - ...videoAdUnit, - ...videoBidderParams // Bidder Specific overrides - }; - - if (bidRequest.params && bidRequest.params.e2etest) { - videoParams.playerSize = [[640, 480]] - videoParams.conext = 'instream' - } - - const video = { - w: parseInt(videoParams.playerSize[0][0], 10), - h: parseInt(videoParams.playerSize[0][1], 10), - } - - // Obtain all ORTB params related video from Ad Unit - OPENRTB_VIDEO_PARAMS.forEach((param) => { - if (videoParams.hasOwnProperty(param)) { - video[param] = videoParams[param]; - } - }); - - // - If product is instream (for instream context) then override placement to 1 - if (params.context === 'instream') { - video.startdelay = video.startdelay || 0; - video.placement = 1; - } - - // bid floor - const bidFloorRequest = { - currency: bidRequest.params.cur || 'USD', - mediaType: 'video', - size: '*' - }; - let floorData = bidRequest.params - if (isFn(bidRequest.getFloor)) { - floorData = bidRequest.getFloor(bidFloorRequest); - } else { - if (params.bidfloor) { - floorData = {floor: params.bidfloor, currency: params.currency || 'USD'}; - } - } - - const openrtbRequest = { - id: bidRequest.bidId, - imp: [ - { - id: '1', - video: video, - secure: isSecure() ? 1 : 0, - bidfloor: floorData.floor, - bidfloorcur: floorData.currency - } - ], - site: { - domain: bidderRequest.refererInfo.domain, - page: bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref, - }, - ext: { - hb: 1, - prebidver: '$prebid.version$', - adapterver: spec.VERSION, - }, - }; - - // content - if (videoParams.content && isPlainObject(videoParams.content)) { - openrtbRequest.site.content = {}; - const contentStringKeys = ['id', 'title', 'series', 'season', 'genre', 'contentrating', 'language', 'url']; - const contentNumberkeys = ['episode', 'prodq', 'context', 'livestream', 'len']; - const contentArrayKeys = ['cat']; - const contentObjectKeys = ['ext']; - for (const contentKey in videoBidderParams.content) { - if ( - (contentStringKeys.indexOf(contentKey) > -1 && isStr(videoParams.content[contentKey])) || - (contentNumberkeys.indexOf(contentKey) > -1 && isNumber(videoParams.content[contentKey])) || - (contentObjectKeys.indexOf(contentKey) > -1 && isPlainObject(videoParams.content[contentKey])) || - (contentArrayKeys.indexOf(contentKey) > -1 && isArray(videoParams.content[contentKey]) && - videoParams.content[contentKey].every(catStr => isStr(catStr)))) { - openrtbRequest.site.content[contentKey] = videoParams.content[contentKey]; - } else { - logMessage('DXKulture bid adapter validation error: ', contentKey, ' is either not supported is OpenRTB V2.5 or value is undefined'); - } - } - } - - return openrtbRequest; -} - -/** - * Prepares video request data. - * - * @param bidRequest - * @param bidderRequest - * @returns openrtbRequest - */ -function buildBannerRequestData(bidRequests, bidderRequest) { - const impr = bidRequests.map(bidRequest => ({ - id: bidRequest.bidId, - banner: { - format: bidRequest.mediaTypes.banner.sizes.map(sizeArr => ({ - w: sizeArr[0], - h: sizeArr[1] - })) - }, - ext: { - exchange: { - placementId: bidRequest.params.placementId - } - } - })); - - const openrtbRequest = { - id: bidderRequest.auctionId, - imp: impr, - site: { - domain: bidderRequest.refererInfo?.domain, - page: bidderRequest.refererInfo?.page, - ref: bidderRequest.refererInfo?.ref, - }, - ext: {} - }; - return openrtbRequest; -} - -function _createBidResponse(bid) { - const isADomainPresent = - bid.adomain && bid.adomain.length; - const bidResponse = { - requestId: bid.impid, - bidderCode: spec.code, - 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, - mediaType: deepAccess(bid, 'ext.prebid.type', BANNER) - } - - if (isADomainPresent) { - bidResponse.meta = { - advertiserDomains: bid.adomain - }; - } - - if (bidResponse.mediaType === VIDEO) { - bidResponse.vastXml = bid.adm; - } - - return bidResponse; -} - -function isSecure() { - return document.location.protocol === 'https:'; -} - registerBidder(spec); diff --git a/modules/dxkultureBidAdapter.md b/modules/dxkultureBidAdapter.md index e934aee3301..e31794ef6c6 100644 --- a/modules/dxkultureBidAdapter.md +++ b/modules/dxkultureBidAdapter.md @@ -30,7 +30,8 @@ var adUnits = [ params: { placementId: 'test', publisherId: 'test', - networkId: '123' + bidfloor: 2.7, + bidfloorcur: 'USD' } }] } @@ -43,7 +44,7 @@ We support the following OpenRTB params that can be specified in `mediaTypes.vid - 'mimes', - 'minduration', - 'maxduration', -- 'placement', +- 'plcmt', - 'protocols', - 'startdelay', - 'skip', @@ -74,7 +75,7 @@ We support the following OpenRTB params that can be specified in `mediaTypes.vid delivery: [2], minduration: 10, maxduration: 30, - placement: 1, + plcmt: 1, playbackmethod: [1,5], } }, @@ -84,8 +85,7 @@ We support the following OpenRTB params that can be specified in `mediaTypes.vid params: { bidfloor: 0.5, publisherId: '12345', - placementId: '6789', - networkId" '123' + placementId: '6789' } } ] diff --git a/test/spec/modules/dxkultureBidAdapter_spec.js b/test/spec/modules/dxkultureBidAdapter_spec.js index ec7f6f146a3..d3ae8ec5124 100644 --- a/test/spec/modules/dxkultureBidAdapter_spec.js +++ b/test/spec/modules/dxkultureBidAdapter_spec.js @@ -1,137 +1,198 @@ import {expect} from 'chai'; -import {spec} from 'modules/dxkultureBidAdapter.js'; - -const BANNER_REQUEST = { - 'bidderCode': 'dxkulture', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708', - 'bidderRequestId': 'requestId', - 'bidRequest': [{ - 'bidder': 'dxkulture', - 'params': { - 'placementId': 123456, - }, - 'placementCode': 'div-gpt-dummy-placement-code', - 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, - 'bidId': 'bidId1', - 'bidderRequestId': 'bidderRequestId', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' - }, - { - 'bidder': 'dxkulture', - 'params': { - 'placementId': 123456, - }, - 'placementCode': 'div-gpt-dummy-placement-code', - 'mediaTypes': {'banner': {'sizes': [[300, 250]]}}, - 'bidId': 'bidId2', - 'bidderRequestId': 'bidderRequestId', - 'auctionId': 'auctionId-56a2-4f71-9098-720a68f2f708' - }], - 'start': 1487883186070, - 'auctionStart': 1487883186069, - 'timeout': 3000 +import {spec, SYNC_URL} from 'modules/dxkultureBidAdapter.js'; +import {BANNER, VIDEO} from 'src/mediaTypes.js'; + +const getBannerRequest = () => { + return { + bidderCode: 'dxkulture', + auctionId: 'ba87bfdf-493e-4a88-8e26-17b4cbc9adbd', + bidderRequestId: 'bidderRequestId', + bids: [ + { + bidder: 'dxkulture', + params: { + placementId: 123456, + publisherId: 'publisherId', + bidfloor: 10, + }, + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', + placementCode: 'div-gpt-dummy-placement-code', + mediaTypes: { + banner: { + sizes: [ + [ 300, 250 ], + ] + } + }, + bidId: '2e9f38ea93bb9e', + bidderRequestId: 'bidderRequestId', + } + ], + start: 1487883186070, + auctionStart: 1487883186069, + timeout: 3000 + } }; -const RESPONSE = { - 'headers': null, - 'body': { - 'id': 'responseId', - 'seatbid': [ - { - 'bid': [ - { - 'id': 'bidId1', - 'impid': 'bidId1', - 'price': 0.18, - 'adm': '', - 'adid': '144762342', - 'adomain': [ - 'https://dummydomain.com' - ], - 'iurl': 'iurl', - 'cid': '109', - 'crid': 'creativeId', - 'cat': [], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 334553, - 'auction_id': 514667951122925701, - 'bidder_id': 2, - 'bid_ad_type': 0 +const getVideoRequest = () => { + return { + bidderCode: 'dxkulture', + auctionId: 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + bidderRequestId: '34feaad34lkj2', + bids: [{ + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'dxkulture', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + publisherId: 'km123', + bidfloor: 10, + } + }, { + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]], + } + }, + bidder: 'dxkulture', + sizes: [640, 480], + bidId: '30b3efwfwe2e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + sid: 134, + rewarded: 1, + placement: 1, + plcmt: 1, + hp: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'https://test.com', + referrer: 'http://test.com' + }, + publisherId: 'km123', + bidfloor: 10, + } + }], + auctionStart: 1520001292880, + timeout: 5000, + start: 1520001292884, + doneCbCallCount: 0, + refererInfo: { + numIframes: 1, + reachedTop: true, + referer: 'test.com' + } + }; +}; + +const getBidderResponse = () => { + return { + headers: null, + body: { + id: 'bid-response', + seatbid: [ + { + bid: [ + { + id: '2e9f38ea93bb9e', + impid: '2e9f38ea93bb9e', + price: 0.18, + adm: '', + adid: '144762342', + adomain: [ + 'https://dummydomain.com' + ], + iurl: 'iurl', + cid: '109', + crid: 'creativeId', + cat: [], + w: 300, + h: 250, + ext: { + prebid: { + type: 'banner' + }, + bidder: { + appnexus: { + brand_id: 334553, + auction_id: 514667951122925701, + bidder_id: 2, + bid_ad_type: 0 + } } } } + ], + seat: 'dxkulture' + } + ], + ext: { + usersync: { + sovrn: { + status: 'none', + syncs: [ + { + url: 'urlsovrn', + type: 'iframe' + } + ] }, - { - 'id': 'bidId2', - 'impid': 'bidId2', - 'price': 0.1, - 'adm': '', - 'adid': '144762342', - 'adomain': [ - 'https://dummydomain.com' - ], - 'iurl': 'iurl', - 'cid': '109', - 'crid': 'creativeId', - 'cat': [], - 'w': 300, - 'h': 250, - 'ext': { - 'prebid': { - 'type': 'banner' - }, - 'bidder': { - 'appnexus': { - 'brand_id': 386046, - 'auction_id': 517067951122925501, - 'bidder_id': 2, - 'bid_ad_type': 0 - } + appnexus: { + status: 'none', + syncs: [ + { + url: 'urlappnexus', + type: 'pixel' } - } + ] } - ], - 'seat': 'dxkulture' - } - ], - 'ext': { - 'usersync': { - 'sovrn': { - 'status': 'none', - 'syncs': [ - { - 'url': 'urlsovrn', - 'type': 'iframe' - } - ] }, - 'appnexus': { - 'status': 'none', - 'syncs': [ - { - 'url': 'urlappnexus', - 'type': 'pixel' - } - ] + responsetimemillis: { + appnexus: 127 } - }, - 'responsetimemillis': { - 'appnexus': 127 } } - } -}; - -const DEFAULT_NETWORK_ID = 1; + }; +} -describe('dxkultureBidAdapter:', function () { +describe('dxkultureBidAdapter', function() { let videoBidRequest; const VIDEO_REQUEST = { @@ -183,51 +244,86 @@ describe('dxkultureBidAdapter:', function () { page: 'https://test.com', referrer: 'http://test.com' }, - publisherId: 'km123' + publisherId: 'km123', + bidfloor: 0 } }; }); - describe('isBidRequestValid', function () { - context('basic validation', function () { - beforeEach(function () { - // Basic Valid BidRequest - this.bid = { - bidder: 'dxkulture', - mediaTypes: { - banner: { - sizes: [[250, 300]] - } - }, - params: { - placementId: 'placementId', - publisherId: 'publisherId', - } - }; - }); + describe('isValidRequest', function() { + let bidderRequest; - it('should accept request if placementId and publisherId are passed', function () { - expect(spec.isBidRequestValid(this.bid)).to.be.true; - }); + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); - it('reject requests without params', function () { - this.bid.params = {}; - expect(spec.isBidRequestValid(this.bid)).to.be.false; - }); + it('should accept request if placementId and publisherId are passed', function () { + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.true; + }); - it('returns false when banner mediaType does not exist', function () { - this.bid.mediaTypes = {} - expect(spec.isBidRequestValid(this.bid)).to.be.false; - }); + it('reject requests without params', function () { + bidderRequest.bids[0].params = {}; + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.false; + }); + + it('returns false when banner mediaType does not exist', function () { + bidderRequest.bids[0].mediaTypes = {} + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.false; + }); + }); + + describe('buildRequests', function() { + let bidderRequest; + + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); + + it('should return expected request object', function() { + const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(bidRequest.url).equal('https://ads.kulture.media/pbjs?pid=publisherId&placementId=123456'); + expect(bidRequest.method).equal('POST'); + }); + }); + + context('banner validation', function () { + let bidderRequest; + + beforeEach(function() { + bidderRequest = getBannerRequest(); + }); + + it('returns true when banner sizes are defined', function () { + const bid = { + bidder: 'dxkulture', + mediaTypes: { + banner: { + sizes: [[250, 300]] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + + expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.be.true; }); - context('banner validation', function () { - it('returns true when banner sizes are defined', function () { + it('returns false when banner sizes are invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + + invalidSizes.forEach((sizes) => { const bid = { bidder: 'dxkulture', mediaTypes: { banner: { - sizes: [[250, 300]] + sizes } }, params: { @@ -236,350 +332,281 @@ describe('dxkultureBidAdapter:', function () { } }; - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bid)).to.be.false; }); + }); + }); - it('returns false when banner sizes are invalid', function () { - const invalidSizes = [ - undefined, - '2:1', - 123, - 'test' - ]; - - invalidSizes.forEach((sizes) => { - const bid = { - bidder: 'dxkulture', - mediaTypes: { - banner: { - sizes - } - }, - params: { - placementId: 'placementId', - publisherId: 'publisherId', - } - }; + context('video validation', function () { + beforeEach(function () { + // Basic Valid BidRequest + this.bid = { + bidder: 'dxkulture', + mediaTypes: { + video: { + playerSize: [[300, 50]], + context: 'instream', + mimes: ['foo', 'bar'], + protocols: [1, 2] + } + }, + params: { + placementId: 'placementId', + publisherId: 'publisherId', + } + }; + }); - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); + it('should return true (skip validations) when e2etest = true', function () { + this.bid.params = { + e2etest: true + }; + expect(spec.isBidRequestValid(this.bid)).to.equal(true); }); - context('video validation', function () { - beforeEach(function () { - // Basic Valid BidRequest - this.bid = { - bidder: 'dxkulture', - mediaTypes: { - video: { - playerSize: [[300, 50]], - context: 'instream', - mimes: ['foo', 'bar'], - protocols: [1, 2] - } - }, - params: { - placementId: 'placementId', - publisherId: 'publisherId', - } - }; - }); + it('returns false when video context is not defined', function () { + delete this.bid.mediaTypes.video.context; - it('should return true (skip validations) when e2etest = true', function () { - this.bid.params = { - e2etest: true - }; - expect(spec.isBidRequestValid(this.bid)).to.equal(true); - }); + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); - it('returns false when video context is not defined', function () { - delete this.bid.mediaTypes.video.context; + it('returns false when video playserSize is invalid', function () { + const invalidSizes = [ + undefined, + '2:1', + 123, + 'test' + ]; + invalidSizes.forEach((playerSize) => { + this.bid.mediaTypes.video.playerSize = playerSize; expect(spec.isBidRequestValid(this.bid)).to.be.false; }); + }); - it('returns false when video playserSize is invalid', function () { - const invalidSizes = [ - undefined, - '2:1', - 123, - 'test' - ]; - - invalidSizes.forEach((playerSize) => { - this.bid.mediaTypes.video.playerSize = playerSize; - expect(spec.isBidRequestValid(this.bid)).to.be.false; - }); - }); + it('returns false when video mimes is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] - it('returns false when video mimes is invalid', function () { - const invalidMimes = [ - undefined, - 'test', - 1, - [] - ] - - invalidMimes.forEach((mimes) => { - this.bid.mediaTypes.video.mimes = mimes; - expect(spec.isBidRequestValid(this.bid)).to.be.false; - }) - }); + invalidMimes.forEach((mimes) => { + this.bid.mediaTypes.video.mimes = mimes; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) + }); - it('returns false when video protocols is invalid', function () { - const invalidMimes = [ - undefined, - 'test', - 1, - [] - ] - - invalidMimes.forEach((protocols) => { - this.bid.mediaTypes.video.protocols = protocols; - expect(spec.isBidRequestValid(this.bid)).to.be.false; - }) - }); + it('returns false when video protocols is invalid', function () { + const invalidMimes = [ + undefined, + 'test', + 1, + [] + ] + + invalidMimes.forEach((protocols) => { + this.bid.mediaTypes.video.protocols = protocols; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }) }); }); describe('buildRequests', function () { + let bidderBannerRequest; + let bidRequestsWithMediaTypes; + let mockBidderRequest; + + beforeEach(function() { + bidderBannerRequest = getBannerRequest(); + + mockBidderRequest = {refererInfo: {}}; + + bidRequestsWithMediaTypes = [{ + bidder: 'dxkulture', + params: { + publisherId: 'km123', + }, + adUnitCode: '/adunit-code/test-path', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + ortb2Imp: { + ext: { + ae: 2 + } + } + }, { + bidder: 'dxkulture', + params: { + publisherId: 'km123', + }, + adUnitCode: 'adunit-code', + mediaTypes: { + video: { + playerSize: [640, 480], + placement: 1, + plcmt: 1, + } + }, + bidId: 'test-bid-id-2', + bidderRequestId: 'test-bid-request-2', + auctionId: 'test-auction-2', + transactionId: 'test-transactionId-2' + }]; + }); + context('when mediaType is banner', function () { it('creates request data', function () { - let request = spec.buildRequests(BANNER_REQUEST.bidRequest, BANNER_REQUEST); + let request = spec.buildRequests(bidderBannerRequest.bids, bidderBannerRequest) expect(request).to.exist.and.to.be.a('object'); - const payload = JSON.parse(request.data); - expect(payload.imp[0]).to.have.property('id', BANNER_REQUEST.bidRequest[0].bidId); - expect(payload.imp[1]).to.have.property('id', BANNER_REQUEST.bidRequest[1].bidId); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', bidderBannerRequest.bids[0].bidId); }); it('has gdpr data if applicable', function () { - const req = Object.assign({}, BANNER_REQUEST, { + const req = Object.assign({}, getBannerRequest(), { gdprConsent: { consentString: 'consentString', gdprApplies: true, } }); - let request = spec.buildRequests(BANNER_REQUEST.bidRequest, req); + let request = spec.buildRequests(bidderBannerRequest.bids, req); - const payload = JSON.parse(request.data); + const payload = request.data; expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); expect(payload.regs.ext).to.have.property('gdpr', 1); }); + }); - it('should properly forward eids parameters', function () { - const req = Object.assign({}, BANNER_REQUEST); - req.bidRequest[0].userIdAsEids = [ - { - source: 'dummy.com', - uids: [ - { - id: 'd6d0a86c-20c6-4410-a47b-5cba383a698a', - atype: 1 - } - ] - }]; - let request = spec.buildRequests(req.bidRequest, req); + if (FEATURES.VIDEO) { + context('video', function () { + it('should create a POST request for every bid', function () { + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(requests.method).to.equal('POST'); + expect(requests.url.trim()).to.equal(spec.ENDPOINT + '?pid=' + videoBidRequest.params.publisherId); + }); - const payload = JSON.parse(request.data); - expect(payload.user.ext.eids[0].source).to.equal('dummy.com'); - expect(payload.user.ext.eids[0].uids[0].id).to.equal('d6d0a86c-20c6-4410-a47b-5cba383a698a'); - expect(payload.user.ext.eids[0].uids[0].atype).to.equal(1); - }); - }); + it('should attach request data', function () { + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const data = requests.data; + const [width, height] = videoBidRequest.sizes; + const VERSION = '1.0.0'; + + expect(data.imp[1].video.w).to.equal(width); + expect(data.imp[1].video.h).to.equal(height); + expect(data.imp[1].bidfloor).to.equal(videoBidRequest.params.bidfloor); + expect(data.imp[1]['video']['placement']).to.equal(videoBidRequest.params.video['placement']); + expect(data.imp[1]['video']['plcmt']).to.equal(videoBidRequest.params.video['plcmt']); + expect(data.ext.prebidver).to.equal('$prebid.version$'); + expect(data.ext.adapterver).to.equal(spec.VERSION); + }); - context('when mediaType is video', function () { - it('should create a POST request for every bid', function () { - const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); - expect(requests.method).to.equal('POST'); - expect(requests.url.trim()).to.equal(spec.ENDPOINT + '?pid=' + videoBidRequest.params.publisherId + '&nId=' + DEFAULT_NETWORK_ID); - }); + it('should set pubId to e2etest when bid.params.e2etest = true', function () { + bidRequestsWithMediaTypes[0].params.e2etest = true; + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(requests.method).to.equal('POST'); + expect(requests.url).to.equal(spec.ENDPOINT + '?pid=e2etest'); + }); - it('should attach request data', function () { - const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); - const data = JSON.parse(requests.data); - const [width, height] = videoBidRequest.sizes; - const VERSION = '1.0.0'; - expect(data.imp[0].video.w).to.equal(width); - expect(data.imp[0].video.h).to.equal(height); - expect(data.imp[0].bidfloor).to.equal(videoBidRequest.params.bidfloor); - expect(data.imp[0]['video']['placement']).to.equal(videoBidRequest.params.video['placement']); - expect(data.imp[0]['video']['plcmt']).to.equal(videoBidRequest.params.video['plcmt']); - expect(data.ext.prebidver).to.equal('$prebid.version$'); - expect(data.ext.adapterver).to.equal(spec.VERSION); + it('should attach End 2 End test data', function () { + bidRequestsWithMediaTypes[1].params.e2etest = true; + const requests = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + const data = requests.data; + expect(data.imp[1].bidfloor).to.equal(0); + expect(data.imp[1].video.w).to.equal(640); + expect(data.imp[1].video.h).to.equal(480); + }); }); + } + }); - it('should set pubId to e2etest when bid.params.e2etest = true', function () { - videoBidRequest.params.e2etest = true; - const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); - expect(requests.method).to.equal('POST'); - expect(requests.url).to.equal(spec.ENDPOINT + '?pid=e2etest&nId=' + DEFAULT_NETWORK_ID); + describe('interpretResponse', function() { + context('when mediaType is banner', function() { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getBannerRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); }); - it('should attach End 2 End test data', function () { - videoBidRequest.params.e2etest = true; - const requests = spec.buildRequests([videoBidRequest], VIDEO_REQUEST); - const data = JSON.parse(requests.data); - expect(data.imp[0].bidfloor).to.not.exist; - expect(data.imp[0].video.w).to.equal(640); - expect(data.imp[0].video.h).to.equal(480); + it('handles empty response', function () { + const EMPTY_RESP = Object.assign({}, bidderResponse, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); + + expect(bids).to.be.empty; }); - }); - }); - describe('interpretResponse', function () { - context('when mediaType is banner', function () { it('have bids', function () { - let bids = spec.interpretResponse(RESPONSE, BANNER_REQUEST); + let bids = spec.interpretResponse(bidderResponse, bidRequest); expect(bids).to.be.an('array').that.is.not.empty; validateBidOnIndex(0); - validateBidOnIndex(1); function validateBidOnIndex(index) { expect(bids[index]).to.have.property('currency', 'USD'); - expect(bids[index]).to.have.property('requestId', RESPONSE.body.seatbid[0].bid[index].impid); - expect(bids[index]).to.have.property('cpm', RESPONSE.body.seatbid[0].bid[index].price); - expect(bids[index]).to.have.property('width', RESPONSE.body.seatbid[0].bid[index].w); - expect(bids[index]).to.have.property('height', RESPONSE.body.seatbid[0].bid[index].h); - expect(bids[index]).to.have.property('ad', RESPONSE.body.seatbid[0].bid[index].adm); - expect(bids[index]).to.have.property('creativeId', RESPONSE.body.seatbid[0].bid[index].crid); - expect(bids[index].meta).to.have.property('advertiserDomains', RESPONSE.body.seatbid[0].bid[index].adomain); + expect(bids[index]).to.have.property('requestId', getBidderResponse().body.seatbid[0].bid[index].impid); + expect(bids[index]).to.have.property('cpm', getBidderResponse().body.seatbid[0].bid[index].price); + expect(bids[index]).to.have.property('width', getBidderResponse().body.seatbid[0].bid[index].w); + expect(bids[index]).to.have.property('height', getBidderResponse().body.seatbid[0].bid[index].h); + expect(bids[index]).to.have.property('ad', getBidderResponse().body.seatbid[0].bid[index].adm); + expect(bids[index]).to.have.property('creativeId', getBidderResponse().body.seatbid[0].bid[index].crid); + expect(bids[index].meta).to.have.property('advertiserDomains'); expect(bids[index]).to.have.property('ttl', 300); expect(bids[index]).to.have.property('netRevenue', true); } }); + }); + + context('when mediaType is video', function () { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getVideoRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); it('handles empty response', function () { - const EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {}}); - const bids = spec.interpretResponse(EMPTY_RESP, BANNER_REQUEST); + const EMPTY_RESP = Object.assign({}, bidderResponse, {'body': {}}); + const bids = spec.interpretResponse(EMPTY_RESP, bidRequest); expect(bids).to.be.empty; }); - }); - - context('when mediaType is video', function () { - it('should return no bids if the response is not valid', function () { - const bidResponse = spec.interpretResponse({ - body: null - }, { - videoBidRequest - }); - expect(bidResponse.length).to.equal(0); - }); it('should return no bids if the response "nurl" and "adm" are missing', function () { - const serverResponse = { + const SERVER_RESP = Object.assign({}, bidderResponse, {'body': { seatbid: [{ bid: [{ price: 6.01 }] }] - }; - const bidResponse = spec.interpretResponse({ - body: serverResponse - }, { - videoBidRequest - }); - expect(bidResponse.length).to.equal(0); + }}); + const bids = spec.interpretResponse(SERVER_RESP, bidRequest); + expect(bids.length).to.equal(0); }); it('should return no bids if the response "price" is missing', function () { - const serverResponse = { + const SERVER_RESP = Object.assign({}, bidderResponse, {'body': { seatbid: [{ bid: [{ adm: '' }] }] - }; - const bidResponse = spec.interpretResponse({ - body: serverResponse - }, { - videoBidRequest - }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return a valid video bid response with just "adm"', function () { - const serverResponse = { - id: '123', - seatbid: [{ - bid: [{ - id: 1, - adid: 123, - impid: 456, - crid: 2, - price: 6.01, - adm: '', - adomain: [ - 'dxkulture.com' - ], - w: 640, - h: 480, - ext: { - prebid: { - type: 'video' - }, - } - }] - }], - cur: 'USD' - }; - const bidResponse = spec.interpretResponse({ - body: serverResponse - }, { - videoBidRequest - }); - let o = { - requestId: serverResponse.seatbid[0].bid[0].impid, - ad: '', - bidderCode: spec.code, - cpm: serverResponse.seatbid[0].bid[0].price, - creativeId: serverResponse.seatbid[0].bid[0].crid, - vastXml: serverResponse.seatbid[0].bid[0].adm, - width: 640, - height: 480, - mediaType: 'video', - currency: 'USD', - ttl: 300, - netRevenue: true, - meta: { - advertiserDomains: ['dxkulture.com'] - } - }; - expect(bidResponse[0]).to.deep.equal(o); - }); - - it('should default ttl to 300', function () { - const serverResponse = { - seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], - cur: 'USD' - }; - const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); - expect(bidResponse[0].ttl).to.equal(300); - }); - it('should not allow ttl above 3601, default to 300', function () { - videoBidRequest.params.video.ttl = 3601; - const serverResponse = { - seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], - cur: 'USD' - }; - const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); - expect(bidResponse[0].ttl).to.equal(300); - }); - it('should not allow ttl below 1, default to 300', function () { - videoBidRequest.params.video.ttl = 0; - const serverResponse = { - seatbid: [{bid: [{id: 1, adid: 123, crid: 2, price: 6.01, adm: ''}]}], - cur: 'USD' - }; - const bidResponse = spec.interpretResponse({body: serverResponse}, {videoBidRequest}); - expect(bidResponse[0].ttl).to.equal(300); + }}); + const bids = spec.interpretResponse(SERVER_RESP, bidRequest); + expect(bids.length).to.equal(0); }); }); }); - describe('getUserSyncs', function () { + describe('user sync', function () { it('handles no parameters', function () { let opts = spec.getUserSyncs({}); expect(opts).to.be.an('array').that.is.empty; @@ -590,27 +617,66 @@ describe('dxkultureBidAdapter:', function () { expect(opts).to.be.an('array').that.is.empty; }); - it('iframe sync enabled should return results', function () { - let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [RESPONSE]); + describe('when gdpr applies', function () { + let gdprConsent; + let gdprPixelUrl; + const consentString = 'gdpr-pixel-consent'; + const gdprApplies = '1'; + beforeEach(() => { + gdprConsent = { + consentString, + gdprApplies: true + }; - 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); - }); + gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; + }); - it('pixel sync enabled should return results', function () { - let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [RESPONSE]); + it('when there is a response, it should have the gdpr query params', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + gdprConsent + ); - 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); + expect(url).to.have.string(`gdpr_consent=${consentString}`); + expect(url).to.have.string(`gdpr=${gdprApplies}`); + }); + + it('should not send signals if no consent object is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('gdpr_consent='); + expect(url).to.not.have.string('gdpr='); + }); }); - it('all sync enabled should return all results', function () { - let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [RESPONSE]); + describe('when ccpa applies', function () { + let usPrivacyConsent; + let uspPixelUrl; + const privacyString = 'TEST'; + beforeEach(() => { + usPrivacyConsent = 'TEST'; + uspPixelUrl = `${SYNC_URL}&us_privacy=${privacyString}` + }); + it('should send the us privacy string, ', () => { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + undefined, + usPrivacyConsent + ); + expect(url).to.have.string(`us_privacy=${privacyString}`); + }); - expect(opts.length).to.equal(2); + it('should not send signals if no consent string is available', function () { + let [{url}] = spec.getUserSyncs( + {iframeEnabled: true, pixelEnabled: true}, + [], + ); + expect(url).to.not.have.string('us_privacy='); + }); }); }); -}) -; +}); From 6f778dad892821b1fc25daf0dd07307096773ed3 Mon Sep 17 00:00:00 2001 From: Mediabrama <145676355+Mediabrama@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:54:42 +0300 Subject: [PATCH 118/760] Add MediaBrama apater (#10518) Co-authored-by: Oleh Naimushyn --- modules/mediabramaBidAdapter.js | 155 +++++++++++ modules/mediabramaBidAdapter.md | 33 +++ .../spec/modules/mediabramaBidAdapter_spec.js | 256 ++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 modules/mediabramaBidAdapter.js create mode 100644 modules/mediabramaBidAdapter.md create mode 100644 test/spec/modules/mediabramaBidAdapter_spec.js diff --git a/modules/mediabramaBidAdapter.js b/modules/mediabramaBidAdapter.js new file mode 100644 index 00000000000..caf6854fe03 --- /dev/null +++ b/modules/mediabramaBidAdapter.js @@ -0,0 +1,155 @@ +import { + isFn, + isStr, + deepAccess, + getWindowTop, + triggerPixel +} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'mediabrama'; +const AD_URL = 'https://prebid.mediabrama.com/pbjs'; +const SYNC_URL = 'https://prebid.mediabrama.com/sync'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || + !bid.ttl || !bid.currency || !bid.meta) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + default: + return false; + } +} + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (_) { + return 0 + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && bid.params.placementId); + }, + + buildRequests: (validBidRequests = [], bidderRequest) => { + const winTop = getWindowTop(); + const location = winTop.location; + const placements = []; + + const request = { + deviceWidth: winTop.screen.width, + deviceHeight: winTop.screen.height, + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + host: location.host, + page: location.pathname, + placements: placements + }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + request.gdpr = bidderRequest.gdprConsent; + } + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + const placement = { + placementId: bid.params.placementId, + bidId: bid.bidId, + schain: bid.schain || {}, + bidfloor: getBidFloor(bid) + }; + + if (typeof bid.userId !== 'undefined') { + placement.userId = bid.userId; + } + + const mediaType = bid.mediaTypes; + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.adFormat = BANNER; + } + + placements.push(placement); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, + + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + + return [{ + type: syncType, + url: syncUrl + }]; + }, + + onBidWon: (bid) => { + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); diff --git a/modules/mediabramaBidAdapter.md b/modules/mediabramaBidAdapter.md new file mode 100644 index 00000000000..fde0a399852 --- /dev/null +++ b/modules/mediabramaBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: MediaBrama Bidder Adapter +Module Type: MediaBrama Bidder Adapter +Maintainer: support@mediabrama.com +``` + +# Description + +Module that connects to mediabrama demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'div-prebid', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mediabrama', + params: { + placementId: '24428' //test, please replace after test + } + } + ] + }, + ]; +``` diff --git a/test/spec/modules/mediabramaBidAdapter_spec.js b/test/spec/modules/mediabramaBidAdapter_spec.js new file mode 100644 index 00000000000..d7341e02f17 --- /dev/null +++ b/test/spec/modules/mediabramaBidAdapter_spec.js @@ -0,0 +1,256 @@ +import {expect} from 'chai'; +import {spec} from '../../../modules/mediabramaBidAdapter.js'; +import { BANNER } from '../../../src/mediaTypes.js'; +import * as utils from '../../../src/utils.js'; + +describe('MediaBramaBidAdapter', function () { + const bid = { + bidId: '23dc19818e5293', + bidder: 'mediabrama', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 24428, + } + }; + + const bidderRequest = { + refererInfo: { + referer: 'test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid], bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://prebid.mediabrama.com/pbjs'); + }); + 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', 'host', 'page', 'placements'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.gdpr).to.not.exist; + expect(data.ccpa).to.not.exist; + let placement = data['placements'][0]; + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'schain', 'bidfloor'); + expect(placement.placementId).to.equal(24428); + expect(placement.bidId).to.equal('23dc19818e5293'); + expect(placement.adFormat).to.equal(BANNER); + expect(placement.schain).to.be.an('object'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + bidderRequest.gdprConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = 'test'; + serverRequest = spec.buildRequests([bid], bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([]); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: {} + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23dc19818e5293'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.width).to.equal(300); + expect(dataItem.height).to.equal(250); + expect(dataItem.ad).to.equal('Test'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23dc19818e5293', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('getUserSyncs', function () { + it('should do nothing on getUserSyncs', function () { + const syncData = spec.getUserSyncs({}, {}, { + consentString: 'ALL', + gdprApplies: true + }, {}); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://prebid.mediabrama.com/sync/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + }); + }); + + describe('on bidWon', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('should replace nurl for banner', function () { + const nurl = 'nurl/?ap=${' + 'AUCTION_PRICE}'; + const bid = { + 'bidderCode': 'mediabrama', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '5691dd18ba6ab6', + 'requestId': '23dc19818e5293', + 'transactionId': '948c716b-bf64-4303-bcf4-395c2f6a9770', + 'auctionId': 'a6b7c61f-15a9-481b-8f64-e859787e9c07', + 'mediaType': 'banner', + 'source': 'client', + 'ad': "
\n", + 'cpm': 0.61, + 'nurl': nurl, + 'creativeId': 'test', + 'currency': 'USD', + 'dealId': '', + 'meta': { + 'advertiserDomains': [], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'mediabrama' + } + ] + } + }, + 'netRevenue': true, + 'ttl': 185, + 'metrics': {}, + 'adapterCode': 'mediabrama', + 'originalCpm': 0.61, + 'originalCurrency': 'USD', + 'responseTimestamp': 1668162732297, + 'requestTimestamp': 1668162732292, + 'bidder': 'mediabrama', + 'adUnitCode': 'div-prebid', + 'timeToRespond': 5, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.61', + 'pbAg': '0.61', + 'pbDg': '0.61', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'mediabrama', + 'hb_adid': '5691dd18ba6ab6', + 'hb_pb': '0.61', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 24428 + } + ] + }; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl/?ap=0.61'); + }); + }); +}); From db9d236dfeb5a56cf0ae316498bc891d2918ce01 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Fri, 20 Oct 2023 17:57:24 +0200 Subject: [PATCH 119/760] LiveIntent UserId module: update LiveConnect dependency; fix caching bug (#10631) * Bump LiveConnect * Bump live-connect-js --- package-lock.json | 66 +++++++++++------------------------------------ package.json | 2 +- 2 files changed, 16 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index d18ae3dfd62..3a96e5a4c1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.0.1" + "live-connect-js": "^6.2.4" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -15471,14 +15471,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", - "engines": { - "node": ">=14" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -16242,32 +16234,19 @@ "dev": true }, "node_modules/live-connect-common": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.0.tgz", - "integrity": "sha512-pa1SuzCg8ovsB6OziAQZpDid/OT8k37VgWFQkE8OUmG52Kf9PUtJM8wqaGdMXd/rNAe/NH8m+Kxx9MZuOvn5zg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.2.tgz", + "integrity": "sha512-K3LNKd9CpREDJbXGdwKqPojjQaxd4G6c7OAD6Yzp3wsCWTH2hV8xNAbUksSOpOcVyyOT9ilteEFXIJQJrbODxQ==", "engines": { "node": ">=18" } }, - "node_modules/live-connect-handlers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/live-connect-handlers/-/live-connect-handlers-2.1.0.tgz", - "integrity": "sha512-uABe9D6yRp7HRgO6vhdIM5j88l17/ROzYGIOHc2Rv1TacLFH6IJ8sbmunY5mIJ9L6ArOVmL4WHY+QgOIkabhxg==", - "dependencies": { - "js-cookie": "^3.0.5", - "live-connect-common": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/live-connect-js": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.1.tgz", - "integrity": "sha512-+TwM7cjgyutqaMNlTQKNY9nJFDPpSWfoazSHmlWxOPlimp10PSZGABIbtulNGGpYbR/Zxgc+C/uW5OxqcNEPXg==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.2.4.tgz", + "integrity": "sha512-fjNzKxbtmHSCsPT27UANOf66fVizJkhgSzAj5Ej9X6sYRiox+NClM2VdRSf4eL54BabJznPLEWzWT/s4ZVVWsQ==", "dependencies": { - "live-connect-common": "^3.0.0", - "live-connect-handlers": "^2.1.0", + "live-connect-common": "^v3.0.2", "tiny-hashes": "1.0.1" }, "engines": { @@ -37287,11 +37266,6 @@ } } }, - "js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==" - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -37913,26 +37887,16 @@ "dev": true }, "live-connect-common": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.0.tgz", - "integrity": "sha512-pa1SuzCg8ovsB6OziAQZpDid/OT8k37VgWFQkE8OUmG52Kf9PUtJM8wqaGdMXd/rNAe/NH8m+Kxx9MZuOvn5zg==" - }, - "live-connect-handlers": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/live-connect-handlers/-/live-connect-handlers-2.1.0.tgz", - "integrity": "sha512-uABe9D6yRp7HRgO6vhdIM5j88l17/ROzYGIOHc2Rv1TacLFH6IJ8sbmunY5mIJ9L6ArOVmL4WHY+QgOIkabhxg==", - "requires": { - "js-cookie": "^3.0.5", - "live-connect-common": "^3.0.0" - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.2.tgz", + "integrity": "sha512-K3LNKd9CpREDJbXGdwKqPojjQaxd4G6c7OAD6Yzp3wsCWTH2hV8xNAbUksSOpOcVyyOT9ilteEFXIJQJrbODxQ==" }, "live-connect-js": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.0.1.tgz", - "integrity": "sha512-+TwM7cjgyutqaMNlTQKNY9nJFDPpSWfoazSHmlWxOPlimp10PSZGABIbtulNGGpYbR/Zxgc+C/uW5OxqcNEPXg==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.2.4.tgz", + "integrity": "sha512-fjNzKxbtmHSCsPT27UANOf66fVizJkhgSzAj5Ej9X6sYRiox+NClM2VdRSf4eL54BabJznPLEWzWT/s4ZVVWsQ==", "requires": { - "live-connect-common": "^3.0.0", - "live-connect-handlers": "^2.1.0", + "live-connect-common": "^v3.0.2", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index a83e321b71b..bb777eeb7a1 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.0.1" + "live-connect-js": "^6.2.4" }, "optionalDependencies": { "fsevents": "^2.3.2" From d0e981b15c5595685504819414ffa0481f32bcc2 Mon Sep 17 00:00:00 2001 From: jingyi-gao-ttd Date: Mon, 23 Oct 2023 12:59:55 +1100 Subject: [PATCH 120/760] UID2: support Client-side token generation and build tag (#10510) * Add CSTG to common module * Add generateToken calls * Fix bug * Use async function and clean up code * Update xhrrequest to ajax * Add try catch * refactor * add tests * Fix tests after change to ajax * Add new test cases for stored token expired * Address feedbacks * Add new test cases * Using ramdom token instead * Add tests for cstg-derived and non-cstg-derived token * Store hashed identity instead of original DII * Remove X-UID2-Client-Version * kick off integration tests * kick off tests * Add console log to debug integration tests * Test action delay * Mock window.crypto.subtle.digest * Fix window.crypto.subtle.digest in firefox * Decouple cstg * Move uid2Cstg out * Wrap cstg with feature flag * Fix formatting * Remove export * Add cstg enabled check * Fix tests * Remove clientId * wrap test with feature flag * cleanup * cleanup * cleanup example * add refresh token * change back to const * Fix tests * restore pacakge lock * Restore test files * Add more tests * Fix normalize email bug * remove redundant condition --------- Co-authored-by: Chris Huie --- features.json | 3 +- modules/uid2IdSystem.js | 23 +- modules/uid2IdSystem_shared.js | 599 ++++++++++++++++++++-- test/spec/modules/euidIdSystem_spec.js | 6 +- test/spec/modules/uid2IdSystem_helpers.js | 4 +- test/spec/modules/uid2IdSystem_spec.js | 339 +++++++++++- 6 files changed, 904 insertions(+), 70 deletions(-) diff --git a/features.json b/features.json index ccb2166a05f..4d8377cda7d 100644 --- a/features.json +++ b/features.json @@ -1,4 +1,5 @@ [ "NATIVE", - "VIDEO" + "VIDEO", + "UID2_CSTG" ] diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index d3b35a5a1aa..b9b3dfa2380 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -7,7 +7,7 @@ */ import { logInfo, logWarn } from '../src/utils.js'; -import {submodule} from '../src/hook.js'; +import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; @@ -33,6 +33,19 @@ function createLogger(logger, prefix) { logger(prefix + ' ', ...strings); } } + +function extractIdentityFromParams(params) { + const keysToCheck = ['emailHash', 'phoneHash', 'email', 'phone']; + + for (let key of keysToCheck) { + if (params.hasOwnProperty(key)) { + return { [key]: params[key] }; + } + } + + return {}; +} + const _logInfo = createLogger(logInfo, LOG_PRE_FIX); const _logWarn = createLogger(logWarn, LOG_PRE_FIX); @@ -79,6 +92,14 @@ export const uid2IdSubmodule = { clientId: UID2_CLIENT_ID, internalStorage: ADVERTISING_COOKIE } + + if (FEATURES.UID2_CSTG) { + mappedConfig.cstg = { + serverPublicKey: config?.params?.serverPublicKey, + subscriptionId: config?.params?.subscriptionId, + ...extractIdentityFromParams(config?.params ?? {}) + } + } _logInfo(`UID2 configuration loaded and mapped.`, mappedConfig); const result = Uid2GetId(mappedConfig, storage, _logInfo, _logWarn); _logInfo(`UID2 getId returned`, result); diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 0f6894a9d3e..29837c5f012 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -1,4 +1,7 @@ /* eslint-disable no-console */ +import { ajax } from '../src/ajax.js'; +import { cyrb53Hash } from '../src/utils.js'; + export const Uid2CodeVersion = '1.1'; function isValidIdentity(identity) { @@ -13,6 +16,7 @@ export class Uid2ApiClient { this._logInfo = logInfo; this._logWarn = logWarn; } + createArrayBuffer(text) { const arrayBuffer = new Uint8Array(text.length); for (let i = 0; i < text.length; i++) { @@ -36,49 +40,58 @@ export class Uid2ApiClient { } callRefreshApi(refreshDetails) { const url = this._baseUrl + '/v2/token/refresh'; - const req = new XMLHttpRequest(); - req.overrideMimeType('text/plain'); - req.open('POST', url, true); - req.setRequestHeader('X-UID2-Client-Version', this._clientVersion); let resolvePromise; let rejectPromise; const promise = new Promise((resolve, reject) => { resolvePromise = resolve; rejectPromise = reject; }); - req.onreadystatechange = () => { - if (req.readyState !== req.DONE) { return; } - try { - if (!refreshDetails.refresh_response_key || req.status !== 200) { - this._logInfo('Error status OR no response decryption key available, assuming unencrypted JSON'); - const response = JSON.parse(req.responseText); + this._logInfo('Sending refresh request', refreshDetails); + ajax(url, { + success: (responseText) => { + try { + if (!refreshDetails.refresh_response_key) { + this._logInfo('No response decryption key available, assuming unencrypted JSON'); + const response = JSON.parse(responseText); + const result = this.ResponseToRefreshResult(response); + if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + } else { + this._logInfo('Decrypting refresh API response'); + const encodeResp = this.createArrayBuffer(atob(responseText)); + window.crypto.subtle.importKey('raw', this.createArrayBuffer(atob(refreshDetails.refresh_response_key)), { name: 'AES-GCM' }, false, ['decrypt']).then((key) => { + this._logInfo('Imported decryption key') + // returns the symmetric key + window.crypto.subtle.decrypt({ + name: 'AES-GCM', + iv: encodeResp.slice(0, 12), + tagLength: 128, // The tagLength you used to encrypt (if any) + }, key, encodeResp.slice(12)).then((decrypted) => { + const decryptedResponse = String.fromCharCode(...new Uint8Array(decrypted)); + this._logInfo('Decrypted to:', decryptedResponse); + const response = JSON.parse(decryptedResponse); + const result = this.ResponseToRefreshResult(response); + if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); + }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); + } + } catch (_err) { + rejectPromise(responseText); + } + }, + error: (error, xhr) => { + try { + this._logInfo('Error status, assuming unencrypted JSON'); + const response = JSON.parse(xhr.responseText); const result = this.ResponseToRefreshResult(response); if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } - } else { - this._logInfo('Decrypting refresh API response'); - const encodeResp = this.createArrayBuffer(atob(req.responseText)); - window.crypto.subtle.importKey('raw', this.createArrayBuffer(atob(refreshDetails.refresh_response_key)), { name: 'AES-GCM' }, false, ['decrypt']).then((key) => { - this._logInfo('Imported decryption key') - // returns the symmetric key - window.crypto.subtle.decrypt({ - name: 'AES-GCM', - iv: encodeResp.slice(0, 12), - tagLength: 128, // The tagLength you used to encrypt (if any) - }, key, encodeResp.slice(12)).then((decrypted) => { - const decryptedResponse = String.fromCharCode(...new Uint8Array(decrypted)); - this._logInfo('Decrypted to:', decryptedResponse); - const response = JSON.parse(decryptedResponse); - const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); + } catch (_e) { + rejectPromise(error) } - } catch (err) { - rejectPromise(err); } - }; - this._logInfo('Sending refresh request', refreshDetails); - req.send(refreshDetails.refresh_token); + }, refreshDetails.refresh_token, { method: 'POST', + customHeaders: { + 'X-UID2-Client-Version': this._clientVersion + } }); return promise; } } @@ -160,18 +173,507 @@ function refreshTokenAndStore(baseUrl, token, clientId, storageManager, _logInfo originalToken: token, latestToken: response.identity, }; + let storedTokens = storageManager.getStoredValueWithFallback(); + if (storedTokens?.originalIdentity) tokens.originalIdentity = storedTokens.originalIdentity; storageManager.storeValue(tokens); return tokens; }); } +let clientSideTokenGenerator; +if (FEATURES.UID2_CSTG) { + const SERVER_PUBLIC_KEY_PREFIX_LENGTH = 9; + + clientSideTokenGenerator = { + isCSTGOptionsValid(maybeOpts, _logWarn) { + if (typeof maybeOpts !== 'object' || maybeOpts === null) { + _logWarn('CSTG opts must be an object'); + return false; + } + + const opts = maybeOpts; + if (typeof opts.serverPublicKey !== 'string') { + _logWarn('CSTG opts.serverPublicKey must be a string'); + return false; + } + const serverPublicKeyPrefix = /^UID2-X-[A-Z]-.+/; + if (!serverPublicKeyPrefix.test(opts.serverPublicKey)) { + _logWarn( + `CSTG opts.serverPublicKey must match the regular expression ${serverPublicKeyPrefix}` + ); + return false; + } + // We don't do any further validation of the public key, as we will find out + // later if it's valid by using importKey. + + if (typeof opts.subscriptionId !== 'string') { + _logWarn('CSTG opts.subscriptionId must be a string'); + return false; + } + if (opts.subscriptionId.length === 0) { + _logWarn('CSTG opts.subscriptionId is empty'); + return false; + } + return true; + }, + + getValidIdentity(opts, _logWarn) { + if (opts.emailHash) { + if (!UID2DiiNormalization.isBase64Hash(opts.emailHash)) { + _logWarn('CSTG opts.emailHash is invalid'); + return; + } + return { email_hash: opts.emailHash }; + } + + if (opts.phoneHash) { + if (!UID2DiiNormalization.isBase64Hash(opts.phoneHash)) { + _logWarn('CSTG opts.phoneHash is invalid'); + return; + } + return { phone_hash: opts.phoneHash }; + } + + if (opts.email) { + const normalizedEmail = UID2DiiNormalization.normalizeEmail(opts.email); + if (normalizedEmail === undefined) { + _logWarn('CSTG opts.email is invalid'); + return; + } + return { email: normalizedEmail }; + } + + if (opts.phone) { + if (!UID2DiiNormalization.isNormalizedPhone(opts.phone)) { + _logWarn('CSTG opts.phone is invalid'); + return; + } + return { phone: opts.phone }; + } + }, + + isStoredTokenInvalid(cstgIdentity, storedTokens, _logInfo, _logWarn) { + if (storedTokens) { + const identity = Object.values(cstgIdentity)[0]; + if (!this.isStoredTokenFromSameIdentity(storedTokens, identity)) { + _logInfo( + 'CSTG supplied new identity - ignoring stored value.', + storedTokens.originalIdentity, + cstgIdentity + ); + // Stored token wasn't originally sourced from the provided identity - ignore the stored value. A new user has logged in? + return true; + } + } + return false; + }, + + async generateTokenAndStore( + baseUrl, + cstgOpts, + cstgIdentity, + storageManager, + _logInfo, + _logWarn + ) { + _logInfo('UID2 cstg opts provided: ', JSON.stringify(cstgOpts)); + const client = new UID2CstgApiClient( + { baseUrl, cstg: cstgOpts }, + _logInfo, + _logWarn + ); + const response = await client.generateToken(cstgIdentity); + _logInfo('CSTG endpoint responded with:', response); + const tokens = { + originalIdentity: this.encodeOriginalIdentity(cstgIdentity), + latestToken: response.identity, + }; + storageManager.storeValue(tokens); + return tokens; + }, + + isStoredTokenFromSameIdentity(storedTokens, identity) { + if (!storedTokens.originalIdentity) return false; + return ( + cyrb53Hash(identity, storedTokens.originalIdentity.salt) === + storedTokens.originalIdentity.identity + ); + }, + + encodeOriginalIdentity(identity) { + const identityValue = Object.values(identity)[0]; + const salt = Math.floor(Math.random() * Math.pow(2, 32)); + return { + identity: cyrb53Hash(identityValue, salt), + salt, + }; + }, + }; + + class UID2DiiNormalization { + static EMAIL_EXTENSION_SYMBOL = '+'; + static EMAIL_DOT = '.'; + static GMAIL_DOMAIN = 'gmail.com'; + + static isBase64Hash(value) { + if (!(value && value.length === 44)) { + return false; + } + + try { + return btoa(atob(value)) === value; + } catch (err) { + return false; + } + } + + static isNormalizedPhone(phone) { + return /^\+[0-9]{10,15}$/.test(phone); + } + + static normalizeEmail(email) { + if (!email || !email.length) return; + + const parsedEmail = email.trim().toLowerCase(); + if (parsedEmail.indexOf(' ') > 0) return; + + const emailParts = this.splitEmailIntoAddressAndDomain(parsedEmail); + if (!emailParts) return; + + const { address, domain } = emailParts; + + const emailIsGmail = this.isGmail(domain); + const parsedAddress = this.normalizeAddressPart( + address, + emailIsGmail, + emailIsGmail + ); + + return parsedAddress ? `${parsedAddress}@${domain}` : undefined; + } + + static splitEmailIntoAddressAndDomain(email) { + const parts = email.split('@'); + if ( + parts.length !== 2 || + parts.some((part) => part === '') + ) { return; } + + return { + address: parts[0], + domain: parts[1], + }; + } + + static isGmail(domain) { + return domain === this.GMAIL_DOMAIN; + } + + static dropExtension(address, extensionSymbol = this.EMAIL_EXTENSION_SYMBOL) { + return address.split(extensionSymbol)[0]; + } + + static normalizeAddressPart(address, shouldRemoveDot, shouldDropExtension) { + let parsedAddress = address; + if (shouldRemoveDot) { parsedAddress = parsedAddress.replaceAll(this.EMAIL_DOT, ''); } + if (shouldDropExtension) parsedAddress = this.dropExtension(parsedAddress); + return parsedAddress; + } + } + + class UID2CstgApiClient { + constructor(opts, logInfo, logWarn) { + this._baseUrl = opts.baseUrl; + this._serverPublicKey = opts.cstg.serverPublicKey; + this._subscriptionId = opts.cstg.subscriptionId; + this._logInfo = logInfo; + this._logWarn = logWarn; + } + + hasStatusResponse(response) { + return typeof response === 'object' && response && response.status; + } + + isCstgApiSuccessResponse(response) { + return ( + this.hasStatusResponse(response) && + response.status === 'success' && + isValidIdentity(response.body) + ); + } + + isCstgApiClientErrorResponse(response) { + return ( + this.hasStatusResponse(response) && + response.status === 'client_error' && + typeof response.message === 'string' + ); + } + + isCstgApiForbiddenResponse(response) { + return ( + this.hasStatusResponse(response) && + response.status === 'invalid_http_origin' && + typeof response.message === 'string' + ); + } + + stripPublicKeyPrefix(serverPublicKey) { + return serverPublicKey.substring(SERVER_PUBLIC_KEY_PREFIX_LENGTH); + } + + async generateCstgRequest(cstgIdentity) { + if ('email_hash' in cstgIdentity || 'phone_hash' in cstgIdentity) { + return cstgIdentity; + } + if ('email' in cstgIdentity) { + const emailHash = await UID2CstgCrypto.hash(cstgIdentity.email); + return { email_hash: emailHash }; + } + if ('phone' in cstgIdentity) { + const phoneHash = await UID2CstgCrypto.hash(cstgIdentity.phone); + return { phone_hash: phoneHash }; + } + } + + async generateToken(cstgIdentity) { + const request = await this.generateCstgRequest(cstgIdentity); + this._logInfo('Building CSTG request for', request); + const box = await UID2CstgBox.build( + this.stripPublicKeyPrefix(this._serverPublicKey) + ); + const encoder = new TextEncoder(); + const now = Date.now(); + const { iv, ciphertext } = await box.encrypt( + encoder.encode(JSON.stringify(request)), + encoder.encode(JSON.stringify([now])) + ); + + const exportedPublicKey = await UID2CstgCrypto.exportPublicKey( + box.clientPublicKey + ); + const requestBody = { + payload: UID2CstgCrypto.bytesToBase64(new Uint8Array(ciphertext)), + iv: UID2CstgCrypto.bytesToBase64(new Uint8Array(iv)), + public_key: UID2CstgCrypto.bytesToBase64( + new Uint8Array(exportedPublicKey) + ), + timestamp: now, + subscription_id: this._subscriptionId, + }; + return this.callCstgApi(requestBody, box); + } + + async callCstgApi(requestBody, box) { + const url = this._baseUrl + '/v2/token/client-generate'; + let resolvePromise; + let rejectPromise; + const promise = new Promise((resolve, reject) => { + resolvePromise = resolve; + rejectPromise = reject; + }); + + this._logInfo('Sending CSTG request', requestBody); + ajax( + url, + { + success: async (responseText, xhr) => { + try { + const encodedResp = UID2CstgCrypto.base64ToBytes(responseText); + const decrypted = await box.decrypt( + encodedResp.slice(0, 12), + encodedResp.slice(12) + ); + const decryptedResponse = new TextDecoder().decode(decrypted); + const response = JSON.parse(decryptedResponse); + if (this.isCstgApiSuccessResponse(response)) { + resolvePromise({ + status: 'success', + identity: response.body, + }); + } else { + // A 200 should always be a success response. + // Something has gone wrong. + rejectPromise( + `API error: Response body was invalid for HTTP status 200: ${decryptedResponse}` + ); + } + } catch (err) { + rejectPromise(err); + } + }, + error: (error, xhr) => { + try { + if (xhr.status === 400) { + const response = JSON.parse(xhr.responseText); + if (this.isCstgApiClientErrorResponse(response)) { + rejectPromise(`Client error: ${response.message}`); + } else { + // A 400 should always be a client error. + // Something has gone wrong. + rejectPromise( + `API error: Response body was invalid for HTTP status 400: ${xhr.responseText}` + ); + } + } else if (xhr.status === 403) { + const response = JSON.parse(xhr.responseText); + if (this.isCstgApiForbiddenResponse(xhr)) { + rejectPromise(`Forbidden: ${response.message}`); + } else { + // A 403 should always be a forbidden response. + // Something has gone wrong. + rejectPromise( + `API error: Response body was invalid for HTTP status 403: ${xhr.responseText}` + ); + } + } else { + rejectPromise( + `API error: Unexpected HTTP status ${xhr.status}: ${error}` + ); + } + } catch (_e) { + rejectPromise(error); + } + }, + }, + JSON.stringify(requestBody), + { method: 'POST' } + ); + return promise; + } + } + + class UID2CstgBox { + static _namedCurve = 'P-256'; + constructor(clientPublicKey, sharedKey) { + this._clientPublicKey = clientPublicKey; + this._sharedKey = sharedKey; + } + + static async build(serverPublicKey) { + const clientKeyPair = await UID2CstgCrypto.generateKeyPair( + UID2CstgBox._namedCurve + ); + const importedServerPublicKey = await UID2CstgCrypto.importPublicKey( + serverPublicKey, + this._namedCurve + ); + const sharedKey = await UID2CstgCrypto.deriveKey( + importedServerPublicKey, + clientKeyPair.privateKey + ); + return new UID2CstgBox(clientKeyPair.publicKey, sharedKey); + } + + async encrypt(plaintext, additionalData) { + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = await window.crypto.subtle.encrypt( + { + name: 'AES-GCM', + iv, + additionalData, + }, + this._sharedKey, + plaintext + ); + return { iv, ciphertext }; + } + + async decrypt(iv, ciphertext) { + return window.crypto.subtle.decrypt( + { + name: 'AES-GCM', + iv, + }, + this._sharedKey, + ciphertext + ); + } + + get clientPublicKey() { + return this._clientPublicKey; + } + } + + class UID2CstgCrypto { + static base64ToBytes(base64) { + const binString = atob(base64); + return Uint8Array.from(binString, (m) => m.codePointAt(0)); + } + + static bytesToBase64(bytes) { + const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join( + '' + ); + return btoa(binString); + } + + static async generateKeyPair(namedCurve) { + const params = { + name: 'ECDH', + namedCurve: namedCurve, + }; + return window.crypto.subtle.generateKey(params, false, ['deriveKey']); + } + + static async importPublicKey(publicKey, namedCurve) { + const params = { + name: 'ECDH', + namedCurve: namedCurve, + }; + return window.crypto.subtle.importKey( + 'spki', + this.base64ToBytes(publicKey), + params, + false, + [] + ); + } + + static exportPublicKey(publicKey) { + return window.crypto.subtle.exportKey('spki', publicKey); + } + + static async deriveKey(serverPublicKey, clientPrivateKey) { + return window.crypto.subtle.deriveKey( + { + name: 'ECDH', + public: serverPublicKey, + }, + clientPrivateKey, + { + name: 'AES-GCM', + length: 256, + }, + false, + ['encrypt', 'decrypt'] + ); + } + + static async hash(value) { + const hash = await window.crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(value) + ); + return this.bytesToBase64(new Uint8Array(hash)); + } + } +} + export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { let suppliedToken = null; const preferLocalStorage = (config.storage !== 'cookie'); const storageManager = new Uid2StorageManager(prebidStorageManager, preferLocalStorage, config.internalStorage, _logInfo); _logInfo(`Module is using ${preferLocalStorage ? 'local storage' : 'cookies'} for internal storage.`); - if (config.paramToken) { + const isCstgEnabled = + clientSideTokenGenerator && + clientSideTokenGenerator.isCSTGOptionsValid(config.cstg, _logWarn); + if (isCstgEnabled) { + _logInfo(`Module is using client-side token generation.`); + // Ignores config.paramToken and config.serverCookieName if any is provided + suppliedToken = null; + } else if (config.paramToken) { suppliedToken = config.paramToken; _logInfo('Read token from params', suppliedToken); } else if (config.serverCookieName) { @@ -184,7 +686,7 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { if (storedTokens && typeof storedTokens === 'string') { // Stored value is a plain token - if no token is supplied, just use the stored value. - if (!suppliedToken) { + if (!suppliedToken && !isCstgEnabled) { _logInfo('Returning legacy cookie value.'); return { id: storedTokens }; } @@ -200,11 +702,31 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { storedTokens = null; } } - // At this point, any legacy values or superseded stored tokens have been nulled out. + + if (FEATURES.UID2_CSTG && isCstgEnabled) { + const cstgIdentity = clientSideTokenGenerator.getValidIdentity(config.cstg, _logWarn); + if (cstgIdentity) { + if (storedTokens && clientSideTokenGenerator.isStoredTokenInvalid(cstgIdentity, storedTokens, _logInfo, _logWarn)) { + storedTokens = null; + } + + if (!storedTokens || Date.now() > storedTokens.latestToken.refresh_expires) { + const promise = clientSideTokenGenerator.generateTokenAndStore(config.apiBaseUrl, config.cstg, cstgIdentity, storageManager, _logInfo, _logWarn); + _logInfo('Generate token using CSTG'); + return { callback: (cb) => { + promise.then((result) => { + _logInfo('Token generation responded, passing the new token on.', result); + cb(result); + }); + } }; + } + } + } + const useSuppliedToken = !(storedTokens?.latestToken) || (suppliedToken && suppliedToken.identity_expires > storedTokens.latestToken.identity_expires); const newestAvailableToken = useSuppliedToken ? suppliedToken : storedTokens.latestToken; _logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); - if (!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires) { + if ((!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires)) { _logInfo('Newest available token is expired and not refreshable.'); return { id: null }; } @@ -227,6 +749,9 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { originalToken: suppliedToken ?? storedTokens?.originalToken, latestToken: newestAvailableToken, }; + if (FEATURES.UID2_CSTG && isCstgEnabled) { + tokens.originalIdentity = storedTokens?.originalIdentity; + } storageManager.storeValue(tokens); return { id: tokens }; } diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index 4f6bacebe6a..9e4598bb5f5 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -6,6 +6,7 @@ import 'src/prebid.js'; import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {server} from 'test/mocks/xhr'; let expect = require('chai').expect; @@ -32,8 +33,7 @@ const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(ma const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); describe('EUID module', function() { - let suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; - let server; + let suiteSandbox, restoreSubtleToUndefined = false; const configureEuidResponse = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); @@ -53,12 +53,10 @@ describe('EUID module', function() { if (restoreSubtleToUndefined) window.crypto.subtle = undefined; }); beforeEach(function() { - server = sinon.createFakeServer(); init(config); setSubmoduleRegistry([euidIdSubmodule]); }); afterEach(function() { - server.restore(); $$PREBID_GLOBAL$$.requestBids.removeAll(); config.resetConfig(); cookieHelpers.clearCookies(moduleCookieName, publisherCookieName); diff --git a/test/spec/modules/uid2IdSystem_helpers.js b/test/spec/modules/uid2IdSystem_helpers.js index 5006a50dedd..e0bef047acb 100644 --- a/test/spec/modules/uid2IdSystem_helpers.js +++ b/test/spec/modules/uid2IdSystem_helpers.js @@ -26,12 +26,12 @@ export const runAuction = async () => { } export const apiHelpers = { - makeTokenResponse: (token, shouldRefresh = false, expired = false) => ({ + makeTokenResponse: (token, shouldRefresh = false, expired = false, refreshExpired = false) => ({ advertising_token: token, refresh_token: 'fake-refresh-token', identity_expires: expired ? Date.now() - 1000 : Date.now() + 60 * 60 * 1000, refresh_from: shouldRefresh ? Date.now() - 1000 : Date.now() + 60 * 1000, - refresh_expires: Date.now() + 24 * 60 * 60 * 1000, // 24 hours + refresh_expires: refreshExpired ? Date.now() - 1000 : Date.now() + 24 * 60 * 60 * 1000, // 24 hours refresh_response_key: 'wR5t6HKMfJ2r4J7fEGX9Gw==', // Fake data }), respondAfterDelay: (delay, srv = server) => new Promise((resolve) => setTimeout(() => { diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index f33060869df..8e3728704c7 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -1,6 +1,6 @@ /* eslint-disable no-console */ -import {coreStorage, init, setSubmoduleRegistry, requestBidsHook} from 'modules/userId/index.js'; +import {coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; @@ -11,6 +11,7 @@ import { configureTimerInterceptors } from 'test/mocks/timers.js'; import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {server} from 'test/mocks/xhr'; let expect = require('chai').expect; @@ -23,16 +24,22 @@ const auctionDelayMs = 10; const initialToken = `initial-advertising-token`; const legacyToken = 'legacy-advertising-token'; const refreshedToken = 'refreshed-advertising-token'; +const clientSideGeneratedToken = 'client-side-generated-advertising-token'; const legacyConfigParams = {storage: null}; const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName }; const newServerCookieConfigParams = { uid2Cookie: publisherCookieName }; +const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); let useLocalStorage = false; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings }); +const makeOriginalIdentity = (identity, salt = 1) => ({ + identity: utils.cyrb53Hash(identity, salt), + salt +}) const getFromAppropriateStorage = () => { if (useLocalStorage) return coreStorage.getDataFromLocalStorage(moduleCookieName); @@ -46,15 +53,18 @@ const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.d const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makeUid2IdentityContainer(legacyToken)); const expectModuleStorageEmptyOrMissing = () => expect(getFromAppropriateStorage()).to.be.null; -const expectModuleStorageToContain = (initialIdentity, latestIdentity) => { +const expectModuleStorageToContain = (originalAdvertisingToken, latestAdvertisingToken, originalIdentity) => { const cookie = JSON.parse(getFromAppropriateStorage()); - if (initialIdentity) expect(cookie.originalToken.advertising_token).to.equal(initialIdentity); - if (latestIdentity) expect(cookie.latestToken.advertising_token).to.equal(latestIdentity); + if (originalAdvertisingToken) expect(cookie.originalToken.advertising_token).to.equal(originalAdvertisingToken); + if (latestAdvertisingToken) expect(cookie.latestToken.advertising_token).to.equal(latestAdvertisingToken); + if (originalIdentity) expect(cookie.originalIdentity).to.eql(makeOriginalIdentity(Object.values(originalIdentity)[0], cookie.originalIdentity.salt)); } -const apiUrl = 'https://prod.uidapi.com/v2/token/refresh'; +const apiUrl = 'https://prod.uidapi.com/v2/token' +const refreshApiUrl = `${apiUrl}/refresh`; const headers = { 'Content-Type': 'application/json' }; -const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: refreshedToken } })); +const makeSuccessResponseBody = (responseToken) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: responseToken } })); +const cstgApiUrl = `${apiUrl}/client-generate`; const testCookieAndLocalStorage = (description, test, only = false) => { const describeFn = only ? describe.only : describe; @@ -76,7 +86,7 @@ const testCookieAndLocalStorage = (description, test, only = false) => { }; describe(`UID2 module`, function () { - let server, suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; + let suiteSandbox, testSandbox, timerSpy, fullTestTitle, restoreSubtleToUndefined = false; before(function () { timerSpy = configureTimerInterceptors(debugOutput); hook.ready(); @@ -87,10 +97,18 @@ describe(`UID2 module`, function () { // I've confirmed it's available in Firefox since v34 (it seems to be unavailable on BrowserStack in Firefox v106). if (typeof window.crypto.subtle === 'undefined') { restoreSubtleToUndefined = true; - window.crypto.subtle = { importKey: () => {}, decrypt: () => {} }; + window.crypto.subtle = { importKey: () => {}, digest: () => {}, decrypt: () => {}, deriveKey: () => {}, encrypt: () => {}, generateKey: () => {}, exportKey: () => {} }; } suiteSandbox.stub(window.crypto.subtle, 'importKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'digest').callsFake(() => Promise.resolve('hashed_value')); suiteSandbox.stub(window.crypto.subtle, 'decrypt').callsFake((settings, key, data) => Promise.resolve(new Uint8Array([...settings.iv, ...data]))); + suiteSandbox.stub(window.crypto.subtle, 'deriveKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'exportKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'encrypt').callsFake(() => Promise.resolve(new ArrayBuffer())); + suiteSandbox.stub(window.crypto.subtle, 'generateKey').callsFake(() => Promise.resolve({ + privateKey: {}, + publicKey: {} + })); }); after(function () { @@ -99,18 +117,18 @@ describe(`UID2 module`, function () { if (restoreSubtleToUndefined) window.crypto.subtle = undefined; }); - const configureUid2Response = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); - const configureUid2ApiSuccessResponse = () => configureUid2Response(200, makeSuccessResponseBody()); - const configureUid2ApiFailResponse = () => configureUid2Response(500, 'Error'); + const configureUid2Response = (apiUrl, httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); + const configureUid2ApiSuccessResponse = (apiUrl, responseToken) => configureUid2Response(apiUrl, 200, makeSuccessResponseBody(responseToken)); + const configureUid2ApiFailResponse = (apiUrl) => configureUid2Response(apiUrl, 500, 'Error'); // Runs the provided test twice - once with a successful API mock, once with one which returns a server error - const testApiSuccessAndFailure = (act, testDescription, failTestDescription, only = false) => { + const testApiSuccessAndFailure = (act, apiUrl, testDescription, failTestDescription, only = false, responseToken = refreshedToken) => { const testFn = only ? it.only : it; testFn(`API responds successfully: ${testDescription}`, async function() { - configureUid2ApiSuccessResponse(); + configureUid2ApiSuccessResponse(apiUrl, responseToken); await act(true); }); testFn(`API responds with an error: ${failTestDescription ?? testDescription}`, async function() { - configureUid2ApiFailResponse(); + configureUid2ApiFailResponse(apiUrl); await act(false); }); } @@ -123,8 +141,6 @@ describe(`UID2 module`, function () { debugOutput(fullTestTitle); testSandbox = sinon.sandbox.create(); testSandbox.stub(utils, 'logWarn'); - server = sinon.createFakeServer(); - init(config); setSubmoduleRegistry([uid2IdSubmodule]); }); @@ -151,13 +167,13 @@ describe(`UID2 module`, function () { it('When no baseUrl is provided in config, the module calls the production endpoint', function() { const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token})); - expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/'); + expect(server.requests[0]?.url).to.have.string('https://prod.uidapi.com/v2/token/refresh'); }); it('When a baseUrl is provided in config, the module calls the provided endpoint', function() { const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); config.setConfig(makePrebidConfig({uid2Token, uid2ApiBase: 'https://operator-integ.uidapi.com'})); - expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/'); + expect(server.requests[0]?.url).to.have.string('https://operator-integ.uidapi.com/v2/token/refresh'); }); }); @@ -238,7 +254,7 @@ describe(`UID2 module`, function () { cookieHelpers.setPublisherCookie(publisherCookieName, token); config.setConfig(makePrebidConfig(serverCookieConfigParams, extraConfig)); }, - } + }, ] scenarios.forEach(function(scenario) { @@ -252,7 +268,7 @@ describe(`UID2 module`, function () { if (apiSucceeds) expectToken(bid, refreshedToken); else expectNoIdentity(bid); - }, 'it should be used in the auction', 'the auction should have no uid2'); + }, refreshApiUrl, 'it should be used in the auction', 'the auction should have no uid2'); testApiSuccessAndFailure(async function(apiSucceeds) { scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); @@ -264,14 +280,14 @@ describe(`UID2 module`, function () { } else { expectModuleStorageEmptyOrMissing(); } - }, 'the refreshed token should be stored in the module storage', 'the module storage should not be set'); + }, refreshApiUrl, 'the refreshed token should be stored in the module storage', 'the module storage should not be set'); }); describe(`when the response doesn't arrive before the auction timer`, function() { testApiSuccessAndFailure(async function() { scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); const bid = await runAuction(); expectNoIdentity(bid); - }, 'it should run the auction'); + }, refreshApiUrl, 'it should run the auction'); testApiSuccessAndFailure(async function(apiSucceeds) { scenario.setConfig(apiHelpers.makeTokenResponse(initialToken, true, true)); @@ -283,7 +299,7 @@ describe(`UID2 module`, function () { await promise; if (apiSucceeds) expectGlobalToHaveToken(refreshedToken); else expectGlobalToHaveNoUid2(); - }, 'it should update the userId after the auction', 'there should be no global identity'); + }, refreshApiUrl, 'it should update the userId after the auction', 'there should be no global identity'); }) describe('and there is a refreshed token in the module cookie', function() { it('the refreshed value from the cookie is used', async function() { @@ -322,7 +338,7 @@ describe(`UID2 module`, function () { apiHelpers.respondAfterDelay(10, server); const bid = await runAuction(); expectToken(bid, initialToken); - }, 'it should not be refreshed before the auction runs'); + }, refreshApiUrl, 'it should not be refreshed before the auction runs'); testApiSuccessAndFailure(async function(success) { const promise = apiHelpers.respondAfterDelay(1, server); @@ -333,7 +349,7 @@ describe(`UID2 module`, function () { } else { expectModuleStorageToContain(initialToken, initialToken); } - }, 'the refreshed token should be stored in the module cookie after the auction runs', 'the module cookie should only have the original token'); + }, refreshApiUrl, 'the refreshed token should be stored in the module cookie after the auction runs', 'the module cookie should only have the original token'); it('it should use the current token in the auction', async function() { const bid = await runAuction(); @@ -342,4 +358,277 @@ describe(`UID2 module`, function () { }); }); }); + + if (FEATURES.UID2_CSTG) { + describe('When CSTG is enabled provided', function () { + const scenarios = [ + { + name: 'email provided in config', + identity: { email: 'test@example.com' }, + setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, ...this.identity }, extraConfig)) }, + setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test . test@gmail.com' }, extraConfig)) + }, + { + name: 'phone provided in config', + identity: { phone: '+12345678910' }, + setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, ...this.identity }, extraConfig)) }, + setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, phone: 'test123' }, extraConfig)) + }, + { + name: 'email hash provided in config', + identity: { email_hash: 'lz3+Rj7IV4X1+Vr1ujkG7tstkxwk5pgkqJ6mXbpOgTs=' }, + setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, emailHash: this.identity.email_hash }, extraConfig)) }, + setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, emailHash: 'test@example.com' }, extraConfig)) + }, + { + name: 'phone hash provided in config', + identity: { phone_hash: 'kVJ+4ilhrqm3HZDDnCQy4niZknvCoM4MkoVzZrQSdJw=' }, + setConfig: function (extraConfig) { config.setConfig(makePrebidConfig({ ...cstgConfigParams, phoneHash: this.identity.phone_hash }, extraConfig)) }, + setInvalidConfig: (extraConfig) => config.setConfig(makePrebidConfig({ ...cstgConfigParams, phoneHash: '614332222111' }, extraConfig)) + }, + ] + scenarios.forEach(function(scenario) { + describe(`And ${scenario.name}`, function() { + describe(`When invalid identity is provided`, function() { + it('the auction should have no uid2', async function () { + scenario.setInvalidConfig() + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + expectModuleStorageEmptyOrMissing(); + }) + }); + + describe('When valid identity is provided, and the auction is set to run immediately', function() { + it('it should ignores token provided in config, and the auction should have no uid2', async function() { + scenario.setConfig({ uid2Token: apiHelpers.makeTokenResponse(initialToken), auctionDelay: 0, syncDelay: 1 }); + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + expectModuleStorageEmptyOrMissing(); + }) + + it('it should ignores token provided in server-set cookie', async function() { + cookieHelpers.setPublisherCookie(publisherCookieName, initialToken); + scenario.setConfig({ ...newServerCookieConfigParams, auctionDelay: 0, syncDelay: 1 }) + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + expectModuleStorageEmptyOrMissing(); + }) + + describe('When the token generated in time', function() { + testApiSuccessAndFailure(async function(apiSucceeds) { + scenario.setConfig(); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + const bid = await runAuction(); + + if (apiSucceeds) expectToken(bid, clientSideGeneratedToken); + else expectNoIdentity(bid); + }, cstgApiUrl, 'it should be used in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken); + + testApiSuccessAndFailure(async function(apiSucceeds) { + scenario.setConfig(); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + + await runAuction(); + if (apiSucceeds) { + expectModuleStorageToContain(undefined, clientSideGeneratedToken, scenario.identity); + } else { + expectModuleStorageEmptyOrMissing(); + } + }, cstgApiUrl, 'the generated token should be stored in the module storage', 'the module storage should not be set', false, clientSideGeneratedToken); + }); + }); + }); + }); + describe(`when the response doesn't arrive before the auction timer`, function() { + testApiSuccessAndFailure(async function() { + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + const bid = await runAuction(); + expectNoIdentity(bid); + }, cstgApiUrl, 'it should run the auction', undefined, false, clientSideGeneratedToken); + + testApiSuccessAndFailure(async function(apiSucceeds) { + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + const promise = apiHelpers.respondAfterDelay(auctionDelayMs * 2, server); + + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + await promise; + if (apiSucceeds) expectGlobalToHaveToken(clientSideGeneratedToken); + else expectGlobalToHaveNoUid2(); + }, cstgApiUrl, 'it should update the userId after the auction', 'there should be no global identity', false, clientSideGeneratedToken); + }) + + describe('when there is a token in the module cookie', function() { + describe('when originalIdentity matches', function() { + describe('When the storedToken is valid', function() { + it('it should use the stored token in the auction', async function() { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken); + const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com', auctionDelay: 0, syncDelay: 1 })); + const bid = await runAuction(); + expectToken(bid, refreshedToken); + }); + }) + + describe('When the storedToken is expired and can be refreshed ', function() { + it('it should calls refresh API', function() { + testApiSuccessAndFailure(async function(apiSucceeds) { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true); + const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + + const bid = await runAuction(); + + if (apiSucceeds) expectToken(bid, refreshedToken); + else expectNoIdentity(bid); + }, refreshApiUrl, 'it should use refreshed token in the auction', 'the auction should have no uid2'); + }); + }) + + describe('When the storedToken is expired for refresh', function() { + it('it should calls CSTG API and not use the stored token', function() { + testApiSuccessAndFailure(async function(apiSucceeds) { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true, true); + const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + + const bid = await runAuction(); + + if (apiSucceeds) expectToken(bid, clientSideGeneratedToken); + else expectNoIdentity(bid); + }, cstgApiUrl, 'it should use generated token in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken); + }); + }) + }) + + it('when originalIdentity not match, the auction should has no uid2', async function() { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken); + const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + const bid = await runAuction(); + expectNoIdentity(bid); + }); + }) + }); + describe('When invalid CSTG configuration is provided', function () { + const invalidConfigs = [ + { + name: 'CSTG option is not a object', + cstgOptions: '' + }, + { + name: 'CSTG option is null', + cstgOptions: '' + }, + { + name: 'serverPublicKey is not a string', + cstgOptions: { subscriptionId: cstgConfigParams.subscriptionId, serverPublicKey: {} } + }, + { + name: 'serverPublicKey not match regular expression', + cstgOptions: { subscriptionId: cstgConfigParams.subscriptionId, serverPublicKey: 'serverPublicKey' } + }, + { + name: 'subscriptionId is not a string', + cstgOptions: { subscriptionId: {}, serverPublicKey: cstgConfigParams.serverPublicKey } + }, + { + name: 'subscriptionId is empty', + cstgOptions: { subscriptionId: '', serverPublicKey: cstgConfigParams.serverPublicKey } + }, + ] + invalidConfigs.forEach(function(scenario) { + describe(`When ${scenario.name}`, function() { + it('should not generate token using identity', async () => { + config.setConfig(makePrebidConfig({ ...scenario.cstgOptions, email: 'test@email.com' })); + const bid = await runAuction(); + expectNoIdentity(bid); + expectGlobalToHaveNoUid2(); + expectModuleStorageEmptyOrMissing(); + }); + }); + }); + }); + describe('When email is provided in different format', function () { + const testCases = [ + { originalEmail: 'TEst.TEST@Test.com ', normalizedEmail: 'test.test@test.com' }, + { originalEmail: 'test+test@test.com', normalizedEmail: 'test+test@test.com' }, + { originalEmail: ' testtest@test.com ', normalizedEmail: 'testtest@test.com' }, + { originalEmail: 'TEst.TEst+123@GMail.Com', normalizedEmail: 'testtest@gmail.com' } + ]; + testCases.forEach((testCase) => { + describe('it should normalize the email and generate token on normalized email', async () => { + testApiSuccessAndFailure(async function(apiSucceeds) { + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: testCase.originalEmail })); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + + await runAuction(); + if (apiSucceeds) { + expectModuleStorageToContain(undefined, clientSideGeneratedToken, { email: testCase.normalizedEmail }); + } else { + expectModuleStorageEmptyOrMissing(); + } + }, cstgApiUrl, 'the generated token should be stored in the module storage', 'the module storage should not be set', false, clientSideGeneratedToken); + }); + }); + }); + } + + describe('When neither token nor CSTG config provided', function () { + describe('when there is a non-cstg-derived token in the module cookie', function () { + it('the auction use stored token if it is valid', async function () { + const originalIdentity = apiHelpers.makeTokenResponse(initialToken); + const moduleCookie = {originalToken: originalIdentity, latestToken: originalIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({})); + const bid = await runAuction(); + expectToken(bid, initialToken); + }) + + it('the auction should has no uid2 if stored token is invalid', async function () { + const originalIdentity = apiHelpers.makeTokenResponse(initialToken, true, true, true); + const moduleCookie = {originalToken: originalIdentity, latestToken: originalIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({})); + const bid = await runAuction(); + expectNoIdentity(bid); + }) + }) + + describe('when there is a cstg-derived token in the module cookie', function () { + it('the auction use stored token if it is valid', async function () { + const originalIdentity = apiHelpers.makeTokenResponse(initialToken); + const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), originalToken: originalIdentity, latestToken: originalIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({})); + const bid = await runAuction(); + expectToken(bid, initialToken); + }) + + it('the auction should has no uid2 if stored token is invalid', async function () { + const originalIdentity = apiHelpers.makeTokenResponse(initialToken, true, true, true); + const moduleCookie = {originalIdentity: makeOriginalIdentity('123@test.com'), originalToken: originalIdentity, latestToken: originalIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({})); + const bid = await runAuction(); + expectNoIdentity(bid); + }) + }) + + it('the auction should has no uid2', async function () { + config.setConfig(makePrebidConfig({})); + const bid = await runAuction(); + expectNoIdentity(bid); + }) + }) }); From 6f51f686df87046c0635abdfbc7c60d9cdae944f Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Mon, 23 Oct 2023 19:44:54 +0800 Subject: [PATCH 121/760] Jixie Bid Adapter : support ids extraction based on config object and misc. changes (#10551) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * WIP * cleaned up * correcting formatting errors from circleci * sending floor to our backend for each bid, when available, changed one of the 1st party cookies that we want to send to backend * fixed spacing issues in code * 1/ provide the possibility of using the jixie section of the config object to determine what ids to read from cookie and to send 2/ removed ontimeout handling 3/ bidwon just ping the trackingUrl, if any 4/ misc: sending aid (from jixie config if any), prebid version etc * corrected formatting mistakes --- modules/jixieBidAdapter.js | 103 +++++-------- test/spec/modules/jixieBidAdapter_spec.js | 176 +++++++--------------- 2 files changed, 92 insertions(+), 187 deletions(-) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 103c925a2f9..75268e9d168 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject} from '../src/utils.js'; +import {deepAccess, getDNT, isArray, logWarn, isFn, isPlainObject, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -7,9 +7,11 @@ import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {Renderer} from '../src/Renderer.js'; +const ADAPTER_VERSION = '2.1.0'; +const PREBID_VERSION = '$prebid.version$'; + const BIDDER_CODE = 'jixie'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); -const EVENTS_URL = 'https://hbtra.jixie.io/sync/hb?'; const JX_OUTSTREAM_RENDERER_URL = 'https://scripts.jixie.media/jxhbrenderer.1.1.min.js'; const REQUESTS_URL = 'https://hb.jixie.io/v2/hbpost'; const sidTTLMins_ = 30; @@ -59,7 +61,18 @@ function setIds_(clientId, sessionId) { } catch (error) {} } -function fetchIds_() { +/** + * fetch some ids from cookie, LS. + * @returns + */ +const defaultGenIds_ = [ + { id: '_jxtoko' }, + { id: '_jxifo' }, + { id: '_jxtdid' }, + { id: '_jxcomp' } +]; + +function fetchIds_(cfg) { let ret = { client_id_c: '', client_id_ls: '', @@ -77,9 +90,11 @@ function fetchIds_() { if (tmp) ret.client_id_ls = tmp; tmp = storage.getDataFromLocalStorage('_jxxs'); if (tmp) ret.session_id_ls = tmp; - ['_jxtoko', '_jxifo', '_jxtdid', '_jxcomp'].forEach(function(n) { - tmp = storage.getCookie(n); - if (tmp) ret.jxeids[n] = tmp; + + let arr = cfg.genids ? cfg.genids : defaultGenIds_; + arr.forEach(function(o) { + tmp = storage.getCookie(o.ck ? o.ck : o.id); + if (tmp) ret.jxeids[o.id] = tmp; }); } catch (error) {} return ret; @@ -97,14 +112,6 @@ function getDevice_() { return device; } -function pingTracking_(endpointOverride, qpobj) { - internal.ajax((endpointOverride || EVENTS_URL), null, qpobj, { - withCredentials: true, - method: 'GET', - crossOrigin: true - }); -} - function jxOutstreamRender_(bidAd) { bidAd.renderer.push(() => { window.JixieOutstreamVideo.init({ @@ -166,7 +173,6 @@ export const internal = { export const spec = { code: BIDDER_CODE, - EVENTS_URL: EVENTS_URL, supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { @@ -198,29 +204,23 @@ export const spec = { } bids.push(tmp); }); - let jixieCfgBlob = config.getConfig('jixie'); - if (!jixieCfgBlob) { - jixieCfgBlob = {}; - } + let jxCfg = config.getConfig('jixie') || {}; - let ids = fetchIds_(); + let ids = fetchIds_(jxCfg); let eids = []; let miscDims = internal.getMiscDims(); let schain = deepAccess(validBidRequests[0], 'schain'); - let eids1 = validBidRequests[0].userIdAsEids + let eids1 = validBidRequests[0].userIdAsEids; // all available user ids are sent to our backend in the standard array layout: if (eids1 && eids1.length) { eids = eids1; } // we want to send this blob of info to our backend: - let pg = config.getConfig('priceGranularity'); - if (!pg) { - pg = {}; - } let transformedParams = Object.assign({}, { // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionid: bidderRequest.auctionId, + auctionid: bidderRequest.auctionId || '', + aid: jxCfg.aid || '', timeout: bidderRequest.timeout, currency: currency, timestamp: (new Date()).getTime(), @@ -231,8 +231,10 @@ export const spec = { bids: bids, eids: eids, schain: schain, - pricegranularity: pg, - cfg: jixieCfgBlob + pricegranularity: (config.getConfig('priceGranularity') || {}), + ver: ADAPTER_VERSION, + pbjsver: PREBID_VERSION, + cfg: jxCfg }, ids); return Object.assign({}, { method: 'POST', @@ -243,48 +245,20 @@ export const spec = { }, onTimeout: function(timeoutData) { - let jxCfgBlob = config.getConfig('jixie'); - if (jxCfgBlob && jxCfgBlob.onTimeout == 'off') { - return; - } - let url = null;// default - if (jxCfgBlob && jxCfgBlob.onTimeoutUrl && typeof jxCfgBlob.onTimeoutUrl == 'string') { - url = jxCfgBlob.onTimeoutUrl; - } - let miscDims = internal.getMiscDims(); - pingTracking_(url, // no overriding ping URL . just use default - { - action: 'hbtimeout', - device: miscDims.device, - pageurl: encodeURIComponent(miscDims.pageurl), - domain: encodeURIComponent(miscDims.domain), - auctionid: deepAccess(timeoutData, '0.auctionId'), - timeout: deepAccess(timeoutData, '0.timeout'), - count: timeoutData.length - }); + logError('jixie adapter timed out for the auction.', timeoutData); }, onBidWon: function(bid) { - if (bid.notrack) { - return; - } if (bid.trackingUrl) { - pingTracking_(bid.trackingUrl, {}); - } else { - let miscDims = internal.getMiscDims(); - pingTracking_((bid.trackingUrlBase ? bid.trackingUrlBase : null), { - action: 'hbbidwon', - device: miscDims.device, - pageurl: encodeURIComponent(miscDims.pageurl), - domain: encodeURIComponent(miscDims.domain), - cid: bid.cid, - cpid: bid.cpid, - jxbidid: bid.jxBidId, - auctionid: bid.auctionId, - cpm: bid.cpm, - requestid: bid.requestId + internal.ajax(bid.trackingUrl, null, {}, { + withCredentials: true, + method: 'GET', + crossOrigin: true }); } + logInfo( + `jixie adapter won the auction. Bid id: ${bid.bidId}, Ad Unit Id: ${bid.adUnitId}` + ); }, interpretResponse: function(response, bidRequest) { @@ -292,7 +266,6 @@ export const spec = { const bidResponses = []; response.body.bids.forEach(function(oneBid) { let bnd = {}; - Object.assign(bnd, oneBid); if (oneBid.osplayer) { bnd.adResponse = { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index fd0d7e8a033..fa7618814f8 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -76,8 +76,11 @@ describe('jixie Adapter', function () { const jxifoTest1_ = 'fffffbbbbbcccccaaaaae890606aaaaa'; const jxtdidTest1_ = '222223d1-1111-2222-3333-b9f129299999'; const jxcompTest1_ = 'AAAAABBBBBCCCCCDDDDDEEEEEUkkZPQfifpkPnnlJhtsa4o+gf4nfqgN5qHiTVX73ymTSbLT9jz1nf+Q7QdxNh9nTad9UaN5pzfHMt/rs1woQw72c1ip+8heZXPfKGZtZP7ldJesYhlo3/0FVcL/wl9ZlAo1jYOEfHo7Y9zFzNXABbbbbb=='; - + const ckname1Val_ = 'ckckname1'; + const ckname2Val_ = 'ckckname2'; const refJxEids_ = { + 'pubid1': ckname1Val_, + 'pubid2': ckname2Val_, '_jxtoko': jxtokoTest1_, '_jxifo': jxifoTest1_, '_jxtdid': jxtdidTest1_, @@ -206,6 +209,17 @@ describe('jixie Adapter', function () { } ]; + const testJixieCfg_ = { + genids: [ + { id: 'pubid1', ck: 'ckname1' }, + { id: 'pubid2', ck: 'ckname2' }, + { id: '_jxtoko' }, + { id: '_jxifo' }, + { id: '_jxtdid' }, + { id: '_jxcomp' } + ] + }; + it('should attach valid params to the adserver endpoint (1)', function () { // this one we do not intercept the cookie stuff so really don't know // what will be in there. so we do not check here (using expect) @@ -216,7 +230,6 @@ describe('jixie Adapter', function () { }) expect(request.data).to.be.an('string'); const payload = JSON.parse(request.data); - expect(payload).to.have.property('auctionid', auctionId_); expect(payload).to.have.property('timeout', timeout_); expect(payload).to.have.property('currency', currency_); expect(payload).to.have.property('bids').that.deep.equals(refBids_); @@ -226,8 +239,25 @@ describe('jixie Adapter', function () { // similar to above test case but here we force some clientid sessionid values // and domain, pageurl // get the interceptors ready: + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'jixie') { + return testJixieCfg_; + } + return null; + }); + let getCookieStub = sinon.stub(storage, 'getCookie'); let getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getCookieStub + .withArgs('ckname1') + .returns(ckname1Val_); + getCookieStub + .withArgs('ckname2') + .returns(ckname2Val_); + getCookieStub + .withArgs('_jxtoko') + .returns(jxtokoTest1_); getCookieStub .withArgs('_jxtoko') .returns(jxtokoTest1_); @@ -265,7 +295,6 @@ describe('jixie Adapter', function () { expect(request.data).to.be.an('string'); const payload = JSON.parse(request.data); - expect(payload).to.have.property('auctionid', auctionId_); expect(payload).to.have.property('client_id_c', clientIdTest1_); expect(payload).to.have.property('client_id_ls', clientIdTest1_); expect(payload).to.have.property('session_id_c', sessionIdTest1_); @@ -282,6 +311,7 @@ describe('jixie Adapter', function () { // unwire interceptors getCookieStub.restore(); getLocalStorageStub.restore(); + getConfigStub.restore(); miscDimsStub.restore(); });// it @@ -362,6 +392,27 @@ describe('jixie Adapter', function () { expect(payload.bids[0].bidFloor).to.exist.and.to.equal(2.1); }); + it('it should populate the aid field when available', function () { + let oneSpecialBidReq = deepClone(bidRequests_[0]); + // 1 aid is not set in the jixie config + let request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + let payload = JSON.parse(request.data); + expect(payload.aid).to.eql(''); + + // 2 aid is set in the jixie config + let getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'jixie') { + return { aid: '11223344556677889900' }; + } + return null; + }); + request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + payload = JSON.parse(request.data); + expect(payload.aid).to.exist.and.to.equal('11223344556677889900'); + getConfigStub.restore(); + }); + it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { userIdAsEids: [ @@ -425,7 +476,6 @@ describe('jixie Adapter', function () { 'bids': [ // video (vast tag url) returned here { - 'trackingUrlBase': 'https://traid.jixie.io/sync/ad?', 'jxBidId': '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', 'requestId': '62847e4c696edcb', 'cpm': 2.19, @@ -458,7 +508,6 @@ describe('jixie Adapter', function () { // display ad returned here: This one there is advertiserDomains // in the response . Will be checked in the unit tests below { - 'trackingUrlBase': 'https://traid.jixie.io/sync/ad?', 'jxBidId': '600c9ae6fda1acb-028d5dee-2c83-44e3-bed1-b75002475cdf', 'requestId': '600c9ae6fda1acb', 'cpm': 1.999, @@ -495,7 +544,6 @@ describe('jixie Adapter', function () { }, // outstream, jx non-default renderer specified: { - 'trackingUrlBase': 'https://traid.jixie.io/sync/ad?', 'jxBidId': '99bc539c81b00ce-028d5dee-2c83-44e3-bed1-b75002475cdf', 'requestId': '99bc539c81b00ce', 'cpm': 2.99, @@ -514,7 +562,6 @@ describe('jixie Adapter', function () { }, // outstream, jx default renderer: { - 'trackingUrlBase': 'https://traid.jixie.io/sync/ad?', 'jxBidId': '61bc539c81b00ce-028d5dee-2c83-44e3-bed1-b75002475cdf', 'requestId': '61bc539c81b00ce', 'cpm': 1.99, @@ -585,7 +632,6 @@ describe('jixie Adapter', function () { expect(result[0].netRevenue).to.equal(true) expect(result[0].ttl).to.equal(300) expect(result[0].vastUrl).to.include('https://ad.jixie.io/v1/video?creativeid=') - expect(result[0].trackingUrlBase).to.include('sync') // We will always make sure the meta->advertiserDomains property is there // If no info it is an empty array. expect(result[0].meta.advertiserDomains.length).to.equal(0) @@ -601,7 +647,6 @@ describe('jixie Adapter', function () { expect(result[1].ttl).to.equal(300) expect(result[1].ad).to.include('jxoutstream') expect(result[1].meta.advertiserDomains.length).to.equal(3) - expect(result[1].trackingUrlBase).to.include('sync') // should pick up about using alternative outstream renderer expect(result[2].requestId).to.equal('99bc539c81b00ce') @@ -613,7 +658,6 @@ describe('jixie Adapter', function () { expect(result[2].netRevenue).to.equal(true) expect(result[2].ttl).to.equal(300) expect(result[2].vastXml).to.include('') - expect(result[2].trackingUrlBase).to.include('sync'); expect(result[2].renderer.id).to.equal('demoslot4-div') expect(result[2].meta.advertiserDomains.length).to.equal(0) expect(result[2].renderer.url).to.equal(JX_OTHER_OUTSTREAM_RENDERER_URL); @@ -628,7 +672,6 @@ describe('jixie Adapter', function () { expect(result[3].netRevenue).to.equal(true) expect(result[3].ttl).to.equal(300) expect(result[3].vastXml).to.include('') - expect(result[3].trackingUrlBase).to.include('sync'); expect(result[3].renderer.id).to.equal('demoslot2-div') expect(result[3].meta.advertiserDomains.length).to.equal(0) expect(result[3].renderer.url).to.equal(JX_OUTSTREAM_RENDERER_URL) @@ -663,116 +706,5 @@ describe('jixie Adapter', function () { spec.onBidWon({ trackingUrl: TRACKINGURL_ }) expect(jixieaux.ajax.calledWith(TRACKINGURL_)).to.equal(true); }) - - it('Should not fire if the adserver response indicates no firing', function() { - let called = false; - ajaxStub.callsFake(function fakeFn() { - called = true; - }); - spec.onBidWon({ notrack: 1 }) - expect(called).to.equal(false); - }); - - // A reference to check again: - const QPARAMS_ = { - action: 'hbbidwon', - device: device_, - pageurl: encodeURIComponent(pageurl_), - domain: encodeURIComponent(domain_), - cid: 121, - cpid: 99, - jxbidid: '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', - auctionid: '028d5dee-2c83-44e3-bed1-b75002475cdf', - cpm: 1.11, - requestid: '62847e4c696edcb' - }; - - it('check it is sending the correct ajax url and qparameters', function() { - spec.onBidWon({ - trackingUrlBase: 'https://mytracker.com/sync?', - cid: 121, - cpid: 99, - jxBidId: '62847e4c696edcb-028d5dee-2c83-44e3-bed1-b75002475cdf', - auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', - cpm: 1.11, - requestId: '62847e4c696edcb' - }) - expect(jixieaux.ajax.calledWith('https://mytracker.com/sync?', null, QPARAMS_)).to.equal(true); - }); }); // describe - - /** - * onTimeout - */ - describe('onTimeout', function() { - let ajaxStub; - let miscDimsStub; - beforeEach(function() { - ajaxStub = sinon.stub(jixieaux, 'ajax'); - miscDimsStub = sinon.stub(jixieaux, 'getMiscDims'); - miscDimsStub - .returns({ device: device_, pageurl: pageurl_, domain: domain_, mkeywords: keywords_ }); - }) - - afterEach(function() { - miscDimsStub.restore(); - ajaxStub.restore(); - }) - - // reference to check against: - const QPARAMS_ = { - action: 'hbtimeout', - device: device_, - pageurl: encodeURIComponent(pageurl_), - domain: encodeURIComponent(domain_), - auctionid: '028d5dee-2c83-44e3-bed1-b75002475cdf', - timeout: 1000, - count: 2 - }; - - it('check it is sending the correct ajax url and qparameters', function() { - spec.onTimeout([ - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} - ]) - expect(jixieaux.ajax.calledWith(spec.EVENTS_URL, null, QPARAMS_)).to.equal(true); - }) - - it('if turned off via config then dont do onTimeout sending of event', function() { - let getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.callsFake(function fakeFn(prop) { - if (prop == 'jixie') { - return { onTimeout: 'off' }; - } - return null; - }); - let called = false; - ajaxStub.callsFake(function fakeFn() { - called = true; - }); - spec.onTimeout([ - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} - ]) - expect(called).to.equal(false); - getConfigStub.restore(); - }) - - const otherUrl_ = 'https://other.azurewebsites.net/sync/evt?'; - it('if config specifies a different endpoint then should send there instead', function() { - let getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.callsFake(function fakeFn(prop) { - if (prop == 'jixie') { - return { onTimeoutUrl: otherUrl_ }; - } - return null; - }); - spec.onTimeout([ - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000}, - {auctionId: '028d5dee-2c83-44e3-bed1-b75002475cdf', timeout: 1000} - ]) - expect(jixieaux.ajax.calledWith(otherUrl_, null, QPARAMS_)).to.equal(true); - getConfigStub.restore(); - }) - });// describe }); From 0a2187c77cef0b006f597879c28604c293959899 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:14:10 -0400 Subject: [PATCH 122/760] Bump crypto-js from 3.3.0 to 4.2.0 (#10645) Bumps [crypto-js](https://github.com/brix/crypto-js) from 3.3.0 to 4.2.0. - [Commits](https://github.com/brix/crypto-js/compare/3.3.0...4.2.0) --- updated-dependencies: - dependency-name: crypto-js dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a96e5a4c1c..2a4198915e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.20.0-pre", + "version": "8.21.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -16,7 +16,7 @@ "core-js": "^3.13.0", "core-js-pure": "^3.13.0", "criteo-direct-rsa-validate": "^1.1.0", - "crypto-js": "^3.3.0", + "crypto-js": "^4.2.0", "dlv": "1.1.3", "dset": "3.1.2", "express": "^4.15.4", @@ -7840,9 +7840,9 @@ } }, "node_modules/crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/css": { "version": "3.0.0", @@ -31282,9 +31282,9 @@ } }, "crypto-js": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", - "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "css": { "version": "3.0.0", diff --git a/package.json b/package.json index bb777eeb7a1..c3ff9a85604 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "core-js": "^3.13.0", "core-js-pure": "^3.13.0", "criteo-direct-rsa-validate": "^1.1.0", - "crypto-js": "^3.3.0", + "crypto-js": "^4.2.0", "dlv": "1.1.3", "dset": "3.1.2", "express": "^4.15.4", From 559206ef959f5d5920e5460688f1848f96a09b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Fri, 27 Oct 2023 15:17:17 +0200 Subject: [PATCH 123/760] Update criteo id system to comply with storage.type (#10630) --- modules/criteoIdSystem.js | 53 +++++++++++------- test/spec/modules/criteoIdSystem_spec.js | 69 ++++++++++++++++-------- 2 files changed, 81 insertions(+), 41 deletions(-) diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index ee343d9b16a..6a09ce2c973 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -22,6 +22,9 @@ const bundleStorageKey = 'cto_bundle'; const dnaBundleStorageKey = 'cto_dna_bundle'; const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; +const STORAGE_TYPE_LOCALSTORAGE = 'html5'; +const STORAGE_TYPE_COOKIES = 'cookie'; + const pastDateString = new Date(0).toString(); const expirationString = new Date(timestamp() + cookiesMaxAge).toString(); @@ -32,14 +35,26 @@ function extractProtocolHost(url, returnOnlyHost = false) { : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; } -function getFromAllStorages(key) { +function getFromStorage(submoduleConfig, key) { + if (submoduleConfig?.storage?.type === STORAGE_TYPE_LOCALSTORAGE) { + return storage.getDataFromLocalStorage(key); + } else if (submoduleConfig?.storage?.type === STORAGE_TYPE_COOKIES) { + return storage.getCookie(key); + } + return storage.getCookie(key) || storage.getDataFromLocalStorage(key); } -function saveOnAllStorages(key, value, hostname) { +function saveOnStorage(submoduleConfig, key, value, hostname) { if (key && value) { - storage.setDataInLocalStorage(key, value); - setCookieOnAllDomains(key, value, expirationString, hostname, true); + if (submoduleConfig?.storage?.type === STORAGE_TYPE_LOCALSTORAGE) { + storage.setDataInLocalStorage(key, value); + } else if (submoduleConfig?.storage?.type === STORAGE_TYPE_COOKIES) { + setCookieOnAllDomains(key, value, expirationString, hostname, true); + } else { + storage.setDataInLocalStorage(key, value); + setCookieOnAllDomains(key, value, expirationString, hostname, true); + } } } @@ -70,11 +85,11 @@ function deleteFromAllStorages(key, hostname) { storage.removeDataFromLocalStorage(key); } -function getCriteoDataFromAllStorages() { +function getCriteoDataFromStorage(submoduleConfig) { return { - bundle: getFromAllStorages(bundleStorageKey), - dnaBundle: getFromAllStorages(dnaBundleStorageKey), - bidId: getFromAllStorages(bididStorageKey), + bundle: getFromStorage(submoduleConfig, bundleStorageKey), + dnaBundle: getFromStorage(submoduleConfig, dnaBundleStorageKey), + bidId: getFromStorage(submoduleConfig, bididStorageKey), } } @@ -108,7 +123,7 @@ function buildCriteoUsersyncUrl(topUrl, domain, bundle, dnaBundle, areCookiesWri return url; } -function callSyncPixel(domain, pixel) { +function callSyncPixel(submoduleConfig, domain, pixel) { if (pixel.writeBundleInStorage && pixel.bundlePropertyName && pixel.storageKeyName) { ajax( pixel.pixelUrl, @@ -117,7 +132,7 @@ function callSyncPixel(domain, pixel) { if (response) { const jsonResponse = JSON.parse(response); if (jsonResponse && jsonResponse[pixel.bundlePropertyName]) { - saveOnAllStorages(pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); + saveOnStorage(submoduleConfig, pixel.storageKeyName, jsonResponse[pixel.bundlePropertyName], domain); } } }, @@ -133,9 +148,9 @@ function callSyncPixel(domain, pixel) { } } -function callCriteoUserSync(parsedCriteoData, callback) { - const cw = storage.cookiesAreEnabled(); - const lsw = storage.localStorageIsEnabled(); +function callCriteoUserSync(submoduleConfig, parsedCriteoData, callback) { + const cw = (submoduleConfig?.storage?.type === undefined || submoduleConfig?.storage?.type === STORAGE_TYPE_COOKIES) && storage.cookiesAreEnabled(); + const lsw = (submoduleConfig?.storage?.type === undefined || submoduleConfig?.storage?.type === STORAGE_TYPE_LOCALSTORAGE) && storage.localStorageIsEnabled(); const topUrl = extractProtocolHost(getRefererInfo().page); // TODO: should domain really be extracted from the current frame? const domain = extractProtocolHost(document.location.href, true); @@ -156,18 +171,18 @@ function callCriteoUserSync(parsedCriteoData, callback) { const jsonResponse = JSON.parse(response); if (jsonResponse.pixels) { - jsonResponse.pixels.forEach(pixel => callSyncPixel(domain, pixel)); + jsonResponse.pixels.forEach(pixel => callSyncPixel(submoduleConfig, domain, pixel)); } if (jsonResponse.acwsUrl) { const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; urlsToCall.forEach(url => triggerPixel(url)); } else if (jsonResponse.bundle) { - saveOnAllStorages(bundleStorageKey, jsonResponse.bundle, domain); + saveOnStorage(submoduleConfig, bundleStorageKey, jsonResponse.bundle, domain); } if (jsonResponse.bidId) { - saveOnAllStorages(bididStorageKey, jsonResponse.bidId, domain); + saveOnStorage(submoduleConfig, bididStorageKey, jsonResponse.bidId, domain); const criteoId = { criteoId: jsonResponse.bidId }; callback(criteoId); } else { @@ -207,10 +222,10 @@ export const criteoIdSubmodule = { * @param {ConsentData} [consentData] * @returns {{id: {criteoId: string} | undefined}}} */ - getId() { - let localData = getCriteoDataFromAllStorages(); + getId(submoduleConfig) { + let localData = getCriteoDataFromStorage(submoduleConfig); - const result = (callback) => callCriteoUserSync(localData, callback); + const result = (callback) => callCriteoUserSync(submoduleConfig, localData, callback); return { id: localData.bidId ? { criteoId: localData.bidId } : undefined, diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index aaf63873d93..975271738e5 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -52,17 +52,21 @@ describe('CriteoId module', function () { }); const storageTestCases = [ - { cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId' }, - { cookie: 'bidId', localStorage: undefined, expected: 'bidId' }, - { cookie: undefined, localStorage: 'bidId', expected: 'bidId' }, - { cookie: undefined, localStorage: undefined, expected: undefined }, + { submoduleConfig: undefined, cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId' }, + { submoduleConfig: undefined, cookie: 'bidId', localStorage: undefined, expected: 'bidId' }, + { submoduleConfig: undefined, cookie: undefined, localStorage: 'bidId', expected: 'bidId' }, + { submoduleConfig: undefined, cookie: undefined, localStorage: undefined, expected: undefined }, + { submoduleConfig: { storage: { type: 'cookie' } }, cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId' }, + { submoduleConfig: { storage: { type: 'cookie' } }, cookie: undefined, localStorage: 'bidId2', expected: undefined }, + { submoduleConfig: { storage: { type: 'html5' } }, cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId2' }, + { submoduleConfig: { storage: { type: 'html5' } }, cookie: 'bidId', localStorage: undefined, expected: undefined }, ] - storageTestCases.forEach(testCase => it('getId() should return the bidId when it exists in local storages', function () { + storageTestCases.forEach(testCase => it('getId() should return the user id depending on the storage type enabled and the data available', function () { getCookieStub.withArgs('cto_bidid').returns(testCase.cookie); getLocalStorageStub.withArgs('cto_bidid').returns(testCase.localStorage); - const result = criteoIdSubmodule.getId(); + const result = criteoIdSubmodule.getId(testCase.submoduleConfig); expect(result.id).to.be.deep.equal(testCase.expected ? { criteoId: testCase.expected } : undefined); expect(result.callback).to.be.a('function'); })) @@ -95,22 +99,24 @@ describe('CriteoId module', function () { }); const responses = [ - { bundle: 'bundle', bidId: 'bidId', acwsUrl: 'acwsUrl' }, - { bundle: 'bundle', bidId: undefined, acwsUrl: 'acwsUrl' }, - { bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, - { bundle: undefined, bidId: 'bidId', acwsUrl: 'acwsUrl' }, - { bundle: 'bundle', bidId: undefined, acwsUrl: undefined }, - { bundle: undefined, bidId: 'bidId', acwsUrl: undefined }, - { bundle: undefined, bidId: undefined, acwsUrl: 'acwsUrl' }, - { bundle: undefined, bidId: undefined, acwsUrl: ['acwsUrl', 'acwsUrl2'] }, - { bundle: undefined, bidId: undefined, acwsUrl: undefined }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: 'bundle', bidId: 'bidId', acwsUrl: 'acwsUrl' }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: 'bundle', bidId: undefined, acwsUrl: 'acwsUrl' }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: undefined, bidId: 'bidId', acwsUrl: 'acwsUrl' }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: 'bundle', bidId: undefined, acwsUrl: undefined }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: undefined, bidId: 'bidId', acwsUrl: undefined }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: undefined, bidId: undefined, acwsUrl: 'acwsUrl' }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: undefined, bidId: undefined, acwsUrl: ['acwsUrl', 'acwsUrl2'] }, + { submoduleConfig: undefined, shouldWriteCookie: true, shouldWriteLocalStorage: true, bundle: undefined, bidId: undefined, acwsUrl: undefined }, + { submoduleConfig: { storage: { type: 'cookie' } }, shouldWriteCookie: true, shouldWriteLocalStorage: false, bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, + { submoduleConfig: { storage: { type: 'html5' } }, shouldWriteCookie: false, shouldWriteLocalStorage: true, bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, ] responses.forEach(response => describe('test user sync response behavior', function () { const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); it('should save bidId if it exists', function () { - const result = criteoIdSubmodule.getId(); + const result = criteoIdSubmodule.getId(response.submoduleConfig); result.callback((id) => { expect(id).to.be.deep.equal(response.bidId ? { criteoId: response.bidId } : undefined); }); @@ -127,16 +133,35 @@ describe('CriteoId module', function () { expect(setCookieStub.calledWith('cto_bundle')).to.be.false; expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; } else if (response.bundle) { - expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.true; - expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.true; - expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; + if (response.shouldWriteCookie) { + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.true; + } else { + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.com')).to.be.false; + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs, null, '.testdev.com')).to.be.false; + } + + if (response.shouldWriteLocalStorage) { + expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; + } else { + expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.false; + } expect(triggerPixelStub.called).to.be.false; } if (response.bidId) { - expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.true; - expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.true; - expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; + if (response.shouldWriteCookie) { + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.true; + } else { + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.com')).to.be.false; + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs, null, '.testdev.com')).to.be.false; + } + if (response.shouldWriteLocalStorage) { + expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; + } else { + expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.false; + } } else { expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.com')).to.be.true; expect(setCookieStub.calledWith('cto_bidid', '', pastDateString, null, '.testdev.com')).to.be.true; From 34070eb841bfc055fea3d9eca8ef82923382642d Mon Sep 17 00:00:00 2001 From: jingyi-gao-ttd Date: Sat, 28 Oct 2023 00:18:02 +1100 Subject: [PATCH 124/760] UID2 module: Add documentation for cstg (#10629) * Add documentation for cstg * Add params to the table * add table to explain the usage of each param * Mark cstg as alpha * Address feedback * Address feedback * change wording * Add documentation for cstg * Add params to the table * add table to explain the usage of each param * Mark cstg as alpha * Address feedback * Address feedback * change wording * trigger tests * reset commit and trigger tests * change wording * update wording --- modules/uid2IdSystem.md | 131 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md index a795d9b1aa1..e546f6eafe1 100644 --- a/modules/uid2IdSystem.md +++ b/modules/uid2IdSystem.md @@ -1,11 +1,63 @@ ## UID2 User ID Submodule -UID2 requires initial tokens to be generated server-side. The UID2 module handles storing, providing, and optionally refreshing them. The module can operate in one of two different modes: *Client Refresh* mode or *Server Only* mode. +The UID2 module handles storing, providing, and optionally refreshing tokens. While initial tokens traditionally required server-side generation, the introduction of the *Client-Side Token Generation (CSTG)* mode offers publishers the flexibility to generate UID2 tokens directly from the module, eliminating this need. Publishers can choose to operate the module in one of three distinct modes: *Client Refresh* mode, *Server Only* mode and *Client-Side Token Generation* mode. *Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. +*Client-Side Token Generation* mode is included in UID2 module by default. However, it's important to note that this mode is created and made available recently. For publishers who do not intend to use it, you have the option to instruct the build to exclude the code related to this feature: + +``` + $ gulp build --modules=uid2IdSystem --disable UID2_CSTG +``` +If you do plan to use Client-Side Token Generation (CSTG) mode, please consult the UID2 Team first as they will provide required configuration values for you to use (see the Client-Side Token Generation (CSTG) mode section below for details) + **Important information:** UID2 is not designed to be used where GDPR applies. The module checks the passed-in consent data and will not operate if the `gdprApplies` flag is true. +## Client-Side Token Generation (CSTG) mode + +**This mode is created and made available recently. Please consult UID2 Team first as they will provide required configuration values for you to use.** + +For publishers seeking a purely client-side integration without the complexities of server-side involvement, the CSTG mode is highly recommended. This mode requires the provision of a public key, subscription ID and [directly identifying information (DII)](https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii) - either emails or phone numbers. In the CSTG mode, the module takes on the responsibility of encrypting the DII, generating the UID2 token, and handling token refreshes when necessary. + +To configure the module to use this mode, you must: +1. Set `parmas.serverPublicKey` and `params.subscriptionId` (please reach out to the UID2 team to obtain these values) +2. Provide **ONLY ONE DII** by setting **ONLY ONE** of `params.email`/`params.phone`/`params.emailHash`/`params.phoneHash` + +Below is a table that provides guidance on when to use each directly identifying information (DII) parameter, along with information on whether normalization and hashing are required by the publisher for each parameter. + +| DII param | When to use it | Normalization required by publisher? | Hashing required by publisher? | +|------------------|-------------------------------------------------------|--------------------------------------|--------------------------------| +| params.email | When you have users' email address | No | No | +| params.phone | When you have user's phone number | Yes | No | +| params.emailHash | When you have user's hashed, normalized email address | Yes | Yes | +| params.phoneHash | When you have user's hashed, normalized phone number | Yes | Yes | + + +*Note that setting params.email will normalize email addresses, but params.phone requires phone numbers to be normalized.* + +Refer to [Normalization and Encoding](#normalization-and-encoding) for details on email address normalization, SHA-256 hashing and Base64 encoding. + +### CSTG example + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'uid2', + params: { + serverPublicKey: '...server public key...', + subscriptionId: '...subcription id...', + email: 'user@email.com', + //phone: '+0000000', + //emailHash: '...email hash...', + //phoneHash: '...phone hash ...' + } + }] + } +}); +``` + ## Client Refresh mode This is the recommended mode for most scenarios. In this mode, the full response body from the UID2 Token Generate or Token Refresh endpoint must be provided to the module. As long as the refresh token remains valid, the module will refresh the advertising token as needed. @@ -133,3 +185,80 @@ The below parameters apply only to the UID2 User ID Module integration. | params.uid2Cookie | Optional, Client refresh | String | The name of a cookie which holds the initial UID2 token, set by the server. The cookie should contain JSON in the same format as the uid2Token param. **If uid2Token is supplied, this param is ignored.** | See the sample token above. | | params.uid2ApiBase | Optional, Client refresh | String | Overrides the default UID2 API endpoint. | `"https://prod.uidapi.com"` _(default)_| | params.storage | Optional, Client refresh | String | Specify whether to use `cookie` or `localStorage` for module-internal storage. It is recommended to not provide this and allow the module to use the default. | `localStorage` _(default)_ | +| params.serverPublicKey | Optional, Client-side token generation | String | A public key for encrypting the DII payload for the Operator's CSTG endpoint. **This is required for client-side token generation.** | - | +| params.subscriptionId | Optional, Client-side token generation | String | A publisher Identifier. **This is required for client-side token generation.** | - | +| params.email | Optional, Client-side token generation | String | The user's email address. Provide this parameter if using email as the DII. | `"test@example.com"` | +| params.emailHash | Optional, Client-side token generation | String | A hashed, normalized representation of the user's email. Provide this parameter if using emailHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | +| params.phone | Optional, Client-side token generation | String | The user's phone number. Provide this parameter if using phone as the DII. | `"+15555555555"` | +| params.phoneHash | Optional, Client-side token generation | String | A hashed, normalized representation of the user's phone number. Provide this parameter if using phoneHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | + +# Normalization and Encoding + +This section provides information about normalizing and encoding [directly Identifying information (DII)](https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii). It's important that, in working with UID2, normalizing and encoding are performed correctly. + +## Introduction +When you're taking user information such as an email address, and following the steps to create a raw UID2 and/or a UID2 advertising token, it's very important that you follow all the required steps. Whether you normalize the information or not, whether you hash it or not, follow the steps exactly. By doing so, you can ensure that the UID2 value you create can be securely and anonymously matched up with other instances of online behavior by the same user. + +>Note: Raw UID2s, and their associated UID2 tokens, are case sensitive. When working with UID2, it's important to pass all IDs and tokens without changing the case. Mismatched IDs can cause ID parsing or token decryption errors. + +## Types of Directly Identifying Information +UID2 supports the following types of directly identifying information (DII): +- Email address +- Phone number + +## Email Address Normalization + +If you send unhashed email addresses to the UID2 Operator Service, the service normalizes the email addresses and then hashes them. If you want to hash the email addresses yourself before sending them, you must normalize them before you hash them. + +> IMPORTANT: Normalizing before hashing ensures that the generated UID2 value will always be the same, so that the data can be matched. If you do not normalize before hashing, this might result in a different UID2, reducing the effectiveness of targeted advertising. + +To normalize an email address, complete the following steps: + +1. Remove leading and trailing spaces. +2. Convert all ASCII characters to lowercase. +3. In `gmail.com` email addresses, remove the following characters from the username part of the email address: + 1. The period (`.` (ASCII code 46)).
For example, normalize `jane.doe@gmail.com` to `janedoe@gmail.com`. + 2. The plus sign (`+` (ASCII code 43)) and all subsequent characters.
For example, normalize `janedoe+home@gmail.com` to `janedoe@gmail.com`. + +## Email Address Hash Encoding + +An email hash is a Base64-encoded SHA-256 hash of a normalized email address. The email address is first normalized, then hashed using the SHA-256 hashing algorithm, and then the resulting bytes of the hash value are encoded using Base64 encoding. Note that the bytes of the hash value are encoded, not the hex-encoded string representation. + +| Type | Example | Comments and Usage | +| :--- | :--- | :--- | +| Normalized email address | `user@example.com` | Normalization is always the first step. | +| SHA-256 hash of normalized email address | `b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514` | This 64-character string is a hex-encoded representation of the 32-byte SHA-256.| +| Hex to Base64 SHA-256 encoding of normalized email address | `tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=` | This 44-character string is a Base64-encoded representation of the 32-byte SHA-256.
WARNING: The SHA-256 hash string in the example above is a hex-encoded representation of the hash value. You must Base64-encode the raw bytes of the hash or use a Base64 encoder that takes a hex-encoded value as input.
Use this encoding for `email_hash` values sent in the request body. | + +>WARNING: When applying Base64 encoding, be sure to Base64-encode the raw bytes of the hash or use a Base64 encoder that takes a hex-encoded value as input. + +## Phone Number Normalization + +If you send unhashed phone numbers to the UID2 Operator Service, the service normalizes the phone numbers and then hashes them. If you want to hash the phone numbers yourself before sending them, you must normalize them before you hash them. + +> IMPORTANT: Normalization before hashing ensures that the generated UID2 value will always be the same, so that the data can be matched. If you do not normalize before hashing, this might result in a different UID2, reducing the effectiveness of targeted advertising. + +Here's what you need to know about phone number normalization rules: + +- The UID2 Operator accepts phone numbers in the [E.164](https://en.wikipedia.org/wiki/E.164) format, which is the international phone number format that ensures global uniqueness. +- E.164 phone numbers can have a maximum of 15 digits. +- Normalized E.164 phone numbers use the following syntax, with no spaces, hyphens, parentheses, or other special characters:
+ `[+] [country code] [subscriber number including area code]` + Examples: + - US: `1 (123) 456-7890` is normalized to `+11234567890`. + - Singapore: `65 1243 5678` is normalized to `+6512345678`. + - Sydney, Australia: `(02) 1234 5678` is normalized to drop the leading zero for the city plus include the country code: `+61212345678`. + +## Phone Number Hash Encoding + +A phone number hash is a Base64-encoded SHA-256 hash of a normalized phone number. The phone number is first normalized, then hashed using the SHA-256 hashing algorithm, and the resulting hex value is encoded using Base64 encoding. + +The example below shows a simple input phone number, and the result as each step is applied to arrive at a secure, opaque, URL-safe value. + +| Type | Example | Comments and Usage | +| :--- | :--- | :--- | +| Normalized phone number | `+12345678901` | Normalization is always the first step. | +| SHA-256 hash of normalized phone number | `10e6f0b47054a83359477dcb35231db6de5c69fb1816e1a6b98e192de9e5b9ee` |This 64-character string is a hex-encoded representation of the 32-byte SHA-256. | +| Hex to Base64 SHA-256 encoding of normalized and hashed phone number | `EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=` | This 44-character string is a Base64-encoded representation of the 32-byte SHA-256.
NOTE: The SHA-256 hash is a hexadecimal value. You must use a Base64 encoder that takes a hex value as input. Use this encoding for `phone_hash` values sent in the request body. | + +>WARNING: When applying Base64 encoding, be sure to use a function that takes a hex value as input. If you use a function that takes text as input, the result is a longer string which is invalid for the purposes of UID2. From 24a90d38e61c7b82db83781462f4dbd92fb37c28 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Fri, 27 Oct 2023 07:18:47 -0600 Subject: [PATCH 125/760] PBjs Core : Update RTDs not in .submodules (#10608) * add/delete rtd in .submodules * fix quotes to show on line * fix comma --- modules/.submodules.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 5699cbfdc87..e7488b9ddb2 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -57,30 +57,41 @@ "1plusXRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", + "adlooxRtdProvider", + "adnuntiusRtdProvider", "airgridRtdProvider", "akamaiDapRtdProvider", "arcspanRtdProvider", "blueconicRtdProvider", + "brandmetricsRtdProvider", "browsiRtdProvider", - "captifyRtdProvider", + "cleanioRtdProvider", "confiantRtdProvider", "dgkeywordRtdProvider", + "experianRtdProvider", "geoedgeRtdProvider", + "geolocationRtdProvider", + "greenbidsRtdProvider", + "growthCodeRtdProvider", "hadronRtdProvider", - "haloRtdProvider", "iasRtdProvider", + "idWardRtdProvider", + "imRtdProvider", + "intersectionRtdProvider", "jwplayerRtdProvider", "medianetRtdProvider", "mgidRtdProvider", + "neuwoRtdProvider", "oneKeyRtdProvider", "optimeraRtdProvider", + "oxxionRtdProvider", "permutiveRtdProvider", "qortexRtdProvider", "reconciliationRtdProvider", + "relevadRtdProvider", "sirdataRtdProvider", "timeoutRtdProvider", - "weboramaRtdProvider", - "zeusPrimeRtdProvider" + "weboramaRtdProvider" ], "fpdModule": [ "validationFpdModule", From f40a32498c42f5780a534b94fc0de9f8a6f7a74e Mon Sep 17 00:00:00 2001 From: radubarbos Date: Fri, 27 Oct 2023 16:24:25 +0300 Subject: [PATCH 126/760] Yahoo ConnectId - backend sync updates. (#10590) Co-authored-by: dumitrubarbos --- modules/connectIdSystem.js | 36 ++++++++++- test/spec/modules/connectIdSystem_spec.js | 74 +++++++++++++++++++++++ 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index e1c5b427264..35a77a9d72d 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -10,7 +10,7 @@ import {submodule} from '../src/hook.js'; import {includes} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; -import {formatQS, isPlainObject, logError, parseUrl} from '../src/utils.js'; +import {formatQS, isNumber, isPlainObject, logError, parseUrl} from '../src/utils.js'; import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; @@ -26,6 +26,16 @@ const PLACEHOLDER = '__PIXEL_ID__'; const UPS_ENDPOINT = `https://ups.analytics.yahoo.com/ups/${PLACEHOLDER}/fed`; const OVERRIDE_OPT_OUT_KEY = 'connectIdOptOut'; const INPUT_PARAM_KEYS = ['pixelId', 'he', 'puid']; +const O_AND_O_DOMAINS = [ + 'yahoo.com', + 'aol.com', + 'aol.ca', + 'aol.de', + 'aol.co.uk', + 'engadget.com', + 'techcrunch.com', + 'autoblog.com', +]; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** @@ -104,9 +114,11 @@ function syncLocalStorageToCookie() { } function isStale(storedIdData) { - if (isPlainObject(storedIdData) && storedIdData.lastSynced && - (storedIdData.lastSynced + VALID_ID_DURATION) <= Date.now()) { + if (isOAndOTraffic()) { return true; + } else if (isPlainObject(storedIdData) && storedIdData.lastSynced) { + const validTTL = storedIdData.ttl || VALID_ID_DURATION; + return storedIdData.lastSynced + validTTL <= Date.now(); } return false; } @@ -127,6 +139,17 @@ function getSiteHostname() { return pageInfo.hostname; } +function isOAndOTraffic() { + let referer = getRefererInfo().ref; + + if (referer) { + referer = parseUrl(referer).hostname; + const subDomains = referer.split('.'); + referer = subDomains.slice(subDomains.length - 2, subDomains.length).join('.'); + } + return O_AND_O_DOMAINS.indexOf(referer) >= 0; +} + /** @type {Submodule} */ export const connectIdSubmodule = { /** @@ -238,6 +261,13 @@ export const connectIdSubmodule = { responseObj.puid = params.puid || responseObj.puid; responseObj.lastSynced = Date.now(); responseObj.lastUsed = Date.now(); + if (isNumber(responseObj.ttl)) { + let validTTLMiliseconds = responseObj.ttl * 60 * 60 * 1000; + if (validTTLMiliseconds > VALID_ID_DURATION) { + validTTLMiliseconds = VALID_ID_DURATION; + } + responseObj.ttl = validTTLMiliseconds; + } storeObject(responseObj); } else { logError(`${MODULE_NAME} module: UPS response returned an invalid payload ${response}`); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 5376ba60886..686c3d63a63 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -3,6 +3,7 @@ import {connectIdSubmodule, storage} from 'modules/connectIdSystem.js'; import {server} from '../../mocks/xhr'; import {parseQS, parseUrl} from 'src/utils.js'; import {uspDataHandler, gppDataHandler} from 'src/adapterManager.js'; +import * as refererDetection from '../../../src/refererDetection'; const TEST_SERVER_URL = 'http://localhost:9876/'; @@ -288,6 +289,79 @@ describe('Yahoo ConnectID Submodule', () => { expect(setCookieStub.firstCall.args[2]).to.equal(expiryDelta.toUTCString()); }); + it('returns an object with the stored ID from cookies and syncs because of expired TTL', () => { + const last2Days = Date.now() - (60 * 60 * 24 * 1000 * 2); + const last21Days = Date.now() - (60 * 60 * 24 * 1000 * 21); + const ttl = 10000; + const cookieData = {connectId: 'foo', he: 'email', lastSynced: last2Days, puid: '9', lastUsed: last21Days, ttl}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(result.id).to.deep.equal(cookieData); + expect(typeof result.callback).to.equal('function'); + }); + + it('returns an object with the stored ID from cookies and not syncs because of valid TTL', () => { + const last2Days = Date.now() - (60 * 60 * 24 * 1000 * 2); + const last21Days = Date.now() - (60 * 60 * 24 * 1000 * 21); + const ttl = 60 * 60 * 24 * 1000 * 3; + const cookieData = {connectId: 'foo', he: HASHED_EMAIL, lastSynced: last2Days, puid: '9', lastUsed: last21Days, ttl}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id'); + cookieData.lastUsed = result.id.lastUsed; + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from cookies and not syncs because of valid TTL with provided puid', () => { + const last2Days = Date.now() - (60 * 60 * 24 * 1000 * 2); + const last21Days = Date.now() - (60 * 60 * 24 * 1000 * 21); + const ttl = 60 * 60 * 24 * 1000 * 3; + const cookieData = {connectId: 'foo', he: HASHED_EMAIL, lastSynced: last2Days, puid: '9', lastUsed: last21Days, ttl}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID, + puid: '9' + }, consentData); + + expect(result).to.be.an('object').that.has.all.keys('id'); + cookieData.lastUsed = result.id.lastUsed; + expect(result.id).to.deep.equal(cookieData); + }); + + it('returns an object with the stored ID from cookies and syncs because is O&O traffic', () => { + const last2Days = Date.now() - (60 * 60 * 24 * 1000 * 2); + const last21Days = Date.now() - (60 * 60 * 24 * 1000 * 21); + const ttl = 60 * 60 * 24 * 1000 * 3; + const cookieData = {connectId: 'foo', he: HASHED_EMAIL, lastSynced: last2Days, puid: '9', lastUsed: last21Days, ttl}; + getCookieStub.withArgs(STORAGE_KEY).returns(JSON.stringify(cookieData)); + const getRefererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + getRefererInfoStub.returns({ + ref: 'https://dev.fc.yahoo.com?test' + }); + let result = invokeGetIdAPI({ + he: HASHED_EMAIL, + pixelId: PIXEL_ID + }, consentData); + getRefererInfoStub.restore(); + + expect(result).to.be.an('object').that.has.all.keys('id', 'callback'); + expect(result.id).to.deep.equal(cookieData); + expect(typeof result.callback).to.equal('function'); + }); + it('Makes an ajax GET request to the production API endpoint with stored puid when id is stale', () => { const last15Days = Date.now() - (60 * 60 * 24 * 1000 * 15); const last29Days = Date.now() - (60 * 60 * 24 * 1000 * 29); From 54e5cb1d5a56895547f83b3645e43d7861c72945 Mon Sep 17 00:00:00 2001 From: Mike Lei Date: Fri, 27 Oct 2023 06:55:15 -0700 Subject: [PATCH 127/760] Flipp Bid Adapter: add additional check for userKey value, update docs (#10628) * Flipp Bid Adapter: initial release * Added flippBidAdapter * OFF-372 Support DTX/Hero in flippBidAdapter (#2) * support creativeType * OFF-422 flippBidAdapter handle AdTypes --------- Co-authored-by: Jairo Panduro * OFF-465 Add getUserKey logic to prebid.js adapter (#3) * Support cookie sync and uid * address pr feedback * remove redundant check * OFF-500 Support "startCompact" param for Prebid.JS #4 * set startCompact default value (#5) * fix docs * use client bidding endpoint * update unit testing endpoint * OFF-876 [Prebid Adapter] Check userKey for empty string (#6) * add more checks to userKey * update document * add uuid format statement * modify docs --------- Co-authored-by: Jairo Panduro --- modules/flippBidAdapter.js | 15 +++++++++------ modules/flippBidAdapter.md | 5 +++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index dfe8141170d..480e414992d 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -25,13 +25,16 @@ export function getUserKey(options = {}) { } // If the partner provides the user key use it, otherwise fallback to cookies - if (options.userKey && isValidUserKey(options.userKey)) { - userKey = options.userKey; - return options.userKey; + if ('userKey' in options && options.userKey) { + if (isValidUserKey(options.userKey)) { + userKey = options.userKey; + return options.userKey; + } } + // Grab from Cookie - const foundUserKey = storage.cookiesAreEnabled() && storage.getCookie(FLIPP_USER_KEY); - if (foundUserKey) { + const foundUserKey = storage.cookiesAreEnabled(null) && storage.getCookie(FLIPP_USER_KEY, null); + if (foundUserKey && isValidUserKey(foundUserKey)) { return foundUserKey; } @@ -47,7 +50,7 @@ export function getUserKey(options = {}) { } function isValidUserKey(userKey) { - return !userKey.startsWith('#'); + return typeof userKey === 'string' && !userKey.startsWith('#') && userKey.length > 0; } const generateUUID = () => { diff --git a/modules/flippBidAdapter.md b/modules/flippBidAdapter.md index 810b883e3f9..e823432a60f 100644 --- a/modules/flippBidAdapter.md +++ b/modules/flippBidAdapter.md @@ -32,9 +32,10 @@ var adUnits = [ publisherNameIdentifier: 'wishabi-test-publisher', // Required siteId: 1192075, // Required zoneIds: [260678], // Optional - userKey: "", // Optional + userKey: ``, // Optional, but recommended for better user experience. Can be a cookie, session id or any other user identifier options: { - startCompact: true // Optional, default to true + startCompact: true, // Optional. Height of the experience will be reduced. Default to true + dwellExpand: true // Optional. Auto expand the experience after a certain time passes. Default to true } } } From 838b9475f0443688d776121bec1070ccefa1c6c2 Mon Sep 17 00:00:00 2001 From: Snigel <108489367+snigelweb@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:06:03 +0200 Subject: [PATCH 128/760] include query string in page parameter (#10644) --- modules/snigelBidAdapter.js | 2 +- test/spec/modules/snigelBidAdapter_spec.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 489d0bcdc9e..6d32d8f97a2 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -105,7 +105,7 @@ registerBidder(spec); function getPage(bidderRequest) { return ( - getConfig(`${BIDDER_CODE}.page`) || deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || window.location.href + getConfig(`${BIDDER_CODE}.page`) || deepAccess(bidderRequest, 'refererInfo.page') || window.location.href ); } diff --git a/test/spec/modules/snigelBidAdapter_spec.js b/test/spec/modules/snigelBidAdapter_spec.js index 7fe2387ca6c..3ba84228872 100644 --- a/test/spec/modules/snigelBidAdapter_spec.js +++ b/test/spec/modules/snigelBidAdapter_spec.js @@ -23,6 +23,7 @@ const BASE_BIDDER_REQUEST = { auctionId: 'test', bidderRequestId: 'test', refererInfo: { + page: 'https://localhost', canonicalUrl: 'https://localhost', }, }; From f9df72a3b7160588b3c56134a98df98c0d5e3320 Mon Sep 17 00:00:00 2001 From: Filip Stamenkovic Date: Fri, 27 Oct 2023 16:09:51 +0200 Subject: [PATCH 129/760] use gvlid for showheroes bidder (#10640) --- modules/showheroes-bsBidAdapter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index a1e7df49d18..bd2706a21d5 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -28,8 +28,11 @@ function getEnvURLs(isStage) { } } +const GVLID = 111; + export const spec = { code: BIDDER_CODE, + gvlid: GVLID, aliases: ['showheroesBs'], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function(bid) { From 63214ee241d143880f3bae5ef14b9b9e468dca6e Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Fri, 27 Oct 2023 08:13:21 -0600 Subject: [PATCH 130/760] remove coppa getConfig (#10651) --- PR_REVIEW.md | 1 - 1 file changed, 1 deletion(-) diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 45ca30a7a3d..9deac9963fb 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -55,7 +55,6 @@ Follow steps above for general review process. In addition, please verify the fo - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloor()` function. - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain. - The bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter. - - If they're getting the COPPA flag, it must come from config.getConfig('coppa'); - Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd): - bcat, battr, badv From ee63e1be8bc2ec0bdb259ddbf2b5d00afff8ac2e Mon Sep 17 00:00:00 2001 From: Remi Henriot Date: Fri, 27 Oct 2023 16:19:07 +0200 Subject: [PATCH 131/760] Adagio: adapt video outstream implementation details (#10633) --- modules/adagioBidAdapter.js | 5 +++-- modules/adagioBidAdapter.md | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 3de584a1195..c775e8223b4 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -54,8 +54,9 @@ const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0k const ADAGIO_PUBKEY_E = 65537; const CURRENCY = 'USD'; -// This provide a whitelist and a basic validation of OpenRTB 2.6 options used by the Adagio SSP. -// https://iabtechlab.com/wp-content/uploads/2022/04/OpenRTB-2-6_FINAL.pdf +// This provide a whitelist and a basic validation of OpenRTB 2.5 options used by the Adagio SSP. +// Accept all options but 'protocol', 'companionad', 'companiontype', 'ext' +// https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf export const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), 'minduration': (value) => isInteger(value), diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index 45f39fc6f2d..19673571982 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -107,10 +107,11 @@ var adUnits = [ cpm: 3.00 // default to 1.00 }, video: { - api: [2, 7], // Required - Your video player must at least support the value 2 and/or 7. + api: [2], // Required - Your video player must at least support the value 2 playbackMethod: [6], // Highly recommended skip: 0 - // OpenRTB video options defined here override ones defined in mediaTypes. + // OpenRTB 2.5 video options defined here override ones defined in mediaTypes. + // Not supported: 'protocol', 'companionad', 'companiontype', 'ext' }, native: { // Optional OpenRTB Native 1.2 request object. Only `context`, `plcmttype` fields are supported. @@ -193,6 +194,8 @@ If the FPD value is an array, the 1st value of this array will be used. placement: 'in_article', adUnitElementId: 'article_outstream', video: { + api: [2], + playbackMethod: [6], skip: 0 }, debug: { From 0a18cdc071826cf1f875489756db2e73cf294bd2 Mon Sep 17 00:00:00 2001 From: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:23:58 +0200 Subject: [PATCH 132/760] Sparteo Bid Adapter: initial release (#10582) * Add: sparteo bidder * Add: sparteo md * Ad:d sparteo tests * Mod: sparteo buildRequest params * Fix: ttl to 60 --- modules/sparteoBidAdapter.js | 126 ++++++ modules/sparteoBidAdapter.md | 35 ++ test/spec/modules/sparteoBidAdapter_spec.js | 408 ++++++++++++++++++++ 3 files changed, 569 insertions(+) create mode 100644 modules/sparteoBidAdapter.js create mode 100644 modules/sparteoBidAdapter.md create mode 100644 test/spec/modules/sparteoBidAdapter_spec.js diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js new file mode 100644 index 00000000000..7d4f87c24f3 --- /dev/null +++ b/modules/sparteoBidAdapter.js @@ -0,0 +1,126 @@ +import { deepAccess, deepSetValue, logError, parseSizesInput, triggerPixel } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +const BIDDER_CODE = 'sparteo'; +const GVLID = 1028; +const TTL = 60; +const HTTP_METHOD = 'POST'; +const REQUEST_URL = 'https://bid.sparteo.com/auction'; + +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: TTL // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + if (bidderRequest.bids[0].params.networkId) { + deepSetValue(request, 'site.publisher.ext.params.networkId', bidderRequest.bids[0].params.networkId); + } + + if (bidderRequest.bids[0].params.publisherId) { + deepSetValue(request, 'site.publisher.ext.params.publisherId', bidderRequest.bids[0].params.publisherId); + } + + return request; + }, + bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.prebid.type'); + + return buildBidResponse(bid, context) + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + let bannerParams = deepAccess(bid, 'mediaTypes.banner'); + let videoParams = deepAccess(bid, 'mediaTypes.video'); + + if (!bid.params) { + logError('The bid params are missing'); + return false; + } + + if (!bid.params.networkId && !bid.params.publisherId) { + logError('The networkId or publisherId is required'); + return false; + } + + if (!bannerParams && !videoParams) { + logError('The placement must be of banner or video type'); + return false; + } + + /** + * BANNER checks + */ + + if (bannerParams) { + let sizes = bannerParams.sizes; + + if (!sizes || parseSizesInput(sizes).length == 0) { + logError('mediaTypes.banner.sizes must be set for banner placement at the right format.'); + return false; + } + } + + /** + * VIDEO checks + */ + + if (videoParams) { + if (parseSizesInput(videoParams.playerSize).length == 0) { + logError('mediaTypes.video.playerSize must be set for video placement at the right format.'); + return false; + } + } + + return true; + }, + + buildRequests: function(bidRequests, bidderRequest) { + const payload = converter.toORTB({bidRequests, bidderRequest}) + + return { + method: HTTP_METHOD, + url: bidRequests[0].params.endpoint ? bidRequests[0].params.endpoint : REQUEST_URL, + data: payload + }; + }, + + interpretResponse: function(serverResponse, requests) { + const bids = converter.fromORTB({response: serverResponse.body, request: requests.data}).bids; + + return bids; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {}, + + onTimeout: function(timeoutData) {}, + + onBidWon: function(bid) { + if (bid && bid.nurl && bid.nurl.length > 0) { + bid.nurl.forEach(function(winUrl) { + triggerPixel(winUrl, null); + }); + } + }, + + onSetTargeting: function(bid) {} +}; + +registerBidder(spec); diff --git a/modules/sparteoBidAdapter.md b/modules/sparteoBidAdapter.md new file mode 100644 index 00000000000..774d9211d9d --- /dev/null +++ b/modules/sparteoBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: Sparteo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@sparteo.com +``` + +# Description + +Module that connects to Sparteo's demand sources + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [ + [1, 1] + ] + } + }, + bids: [ + { + bidder: 'sparteo', + params: { + networkId: '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js new file mode 100644 index 00000000000..e82f23a1d4e --- /dev/null +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -0,0 +1,408 @@ +import {expect} from 'chai'; +import { deepClone, mergeDeep } from 'src/utils'; +import {spec as adapter} from 'modules/sparteoBidAdapter'; + +const CURRENCY = 'EUR'; +const TTL = 60; +const HTTP_METHOD = 'POST'; +const REQUEST_URL = 'https://bid.sparteo.com/auction'; + +const VALID_BID_BANNER = { + bidder: 'sparteo', + bidId: '1a2b3c4d', + adUnitCode: 'id-1234', + params: { + networkId: '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + }, + mediaTypes: { + banner: { + sizes: [ + [1, 1] + ] + } + } +}; + +const VALID_BID_VIDEO = { + bidder: 'sparteo', + bidId: '5e6f7g8h', + adUnitCode: 'id-5678', + params: { + networkId: '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + }, + mediaTypes: { + video: { + playerSize: [640, 360], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + api: [1, 2], + mimes: ['video/mp4'], + skip: 1, + startdelay: 0, + placement: 1, + linearity: 1, + minduration: 5, + maxduration: 30, + context: 'instream' + } + }, + ortb2Imp: { + ext: { + pbadslot: 'video' + } + } +}; + +const VALID_REQUEST_BANNER = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + 'imp': [{ + 'id': '1a2b3c4d', + 'banner': { + 'format': [{ + 'h': 1, + 'w': 1 + }], + 'topframe': 0 + } + }], + 'site': { + 'publisher': { + 'ext': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } + } + }, + 'test': 0 + } +}; + +const VALID_REQUEST_VIDEO = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + 'imp': [{ + 'id': '5e6f7g8h', + 'video': { + 'w': 640, + 'h': 360, + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'api': [1, 2], + 'mimes': ['video/mp4'], + 'skip': 1, + 'startdelay': 0, + 'placement': 1, + 'linearity': 1, + 'minduration': 5, + 'maxduration': 30, + }, + 'ext': { + 'pbadslot': 'video' + } + }], + 'site': { + 'publisher': { + 'ext': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } + } + }, + 'test': 0 + } +}; + +const VALID_REQUEST = { + method: HTTP_METHOD, + url: REQUEST_URL, + data: { + 'imp': [{ + 'id': '1a2b3c4d', + 'banner': { + 'format': [{ + 'h': 1, + 'w': 1 + }], + 'topframe': 0 + } + }, { + 'id': '5e6f7g8h', + 'video': { + 'w': 640, + 'h': 360, + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'api': [1, 2], + 'mimes': ['video/mp4'], + 'skip': 1, + 'startdelay': 0, + 'placement': 1, + 'linearity': 1, + 'minduration': 5, + 'maxduration': 30, + }, + 'ext': { + 'pbadslot': 'video' + } + }], + 'site': { + 'publisher': { + 'ext': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } + } + }, + 'test': 0 + } +}; + +const BIDDER_REQUEST = { + bids: [VALID_BID_BANNER, VALID_BID_VIDEO] +} + +const BIDDER_REQUEST_BANNER = { + bids: [VALID_BID_BANNER] +} + +const BIDDER_REQUEST_VIDEO = { + bids: [VALID_BID_VIDEO] +} + +describe('SparteoAdapter', function () { + describe('isBidRequestValid', function () { + describe('Check method return', function () { + it('should return true', function () { + expect(adapter.isBidRequestValid(VALID_BID_BANNER)).to.equal(true); + expect(adapter.isBidRequestValid(VALID_BID_VIDEO)).to.equal(true); + }); + + it('should return false because the networkId is missing', function () { + let wrongBid = deepClone(VALID_BID_BANNER); + delete wrongBid.params.networkId; + + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + + it('should return false because the banner size is missing', function () { + let wrongBid = deepClone(VALID_BID_BANNER); + + wrongBid.mediaTypes.banner.sizes = '123456'; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + + delete wrongBid.mediaTypes.banner.sizes; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + + it('should return false because the video player size paramater is missing', function () { + let wrongBid = deepClone(VALID_BID_VIDEO); + + wrongBid.mediaTypes.video.playerSize = '123456'; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + + delete wrongBid.mediaTypes.video.playerSize; + expect(adapter.isBidRequestValid(wrongBid)).to.equal(false); + }); + }); + }); + + describe('buildRequests', function () { + describe('Check method return', function () { + if (FEATURES.VIDEO) { + it('should return the right formatted requests', function() { + const request = adapter.buildRequests([VALID_BID_BANNER, VALID_BID_VIDEO], BIDDER_REQUEST); + delete request.data.id; + + expect(request).to.deep.equal(VALID_REQUEST); + }); + } + + it('should return the right formatted banner requests', function() { + const request = adapter.buildRequests([VALID_BID_BANNER], BIDDER_REQUEST_BANNER); + delete request.data.id; + + expect(request).to.deep.equal(VALID_REQUEST_BANNER); + }); + + if (FEATURES.VIDEO) { + it('should return the right formatted video requests', function() { + const request = adapter.buildRequests([VALID_BID_VIDEO], BIDDER_REQUEST_VIDEO); + delete request.data.id; + + expect(request).to.deep.equal(VALID_REQUEST_VIDEO); + }); + } + + it('should return the right formatted request with endpoint test', function() { + let endpoint = 'https://bid-test.sparteo.com/auction'; + + let bids = mergeDeep(deepClone([VALID_BID_BANNER, VALID_BID_VIDEO]), { + params: { + endpoint: endpoint + } + }); + + let requests = mergeDeep(deepClone(VALID_REQUEST)); + + const request = adapter.buildRequests(bids, BIDDER_REQUEST); + requests.url = endpoint; + delete request.data.id; + + expect(requests).to.deep.equal(requests); + }); + }); + }); + + describe('interpretResponse', function() { + describe('Check method return', function () { + it('should return the right formatted response', function() { + let response = { + body: { + 'id': '63f4d300-6896-4bdc-8561-0932f73148b1', + 'cur': 'EUR', + 'seatbid': [ + { + 'seat': 'sparteo', + 'group': 0, + 'bid': [ + { + 'id': 'cdbb6982-a269-40c7-84e5-04797f11d87a', + 'impid': '1a2b3c4d', + 'price': 4.5, + 'ext': { + 'prebid': { + 'type': 'banner' + } + }, + 'adm': 'script', + 'crid': 'crid', + 'w': 1, + 'h': 1, + 'nurl': 'https://t.bidder.sparteo.com/img' + } + ] + } + ] + } + }; + + if (FEATURES.VIDEO) { + response.body.seatbid[0].bid.push({ + 'id': 'cdbb6982-a269-40c7-84e5-04797f11d87b', + 'impid': '5e6f7g8h', + 'price': 5, + 'ext': { + 'prebid': { + 'type': 'video' + } + }, + 'adm': 'tag', + 'crid': 'crid', + 'w': 640, + 'h': 480, + 'nurl': 'https://t.bidder.sparteo.com/img' + }); + } + + let formattedReponse = [ + { + requestId: '1a2b3c4d', + seatBidId: 'cdbb6982-a269-40c7-84e5-04797f11d87a', + cpm: 4.5, + width: 1, + height: 1, + creativeId: 'crid', + creative_id: 'crid', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'banner', + meta: {}, + ad: 'script
' + } + ]; + + if (FEATURES.VIDEO) { + formattedReponse.push({ + requestId: '5e6f7g8h', + seatBidId: 'cdbb6982-a269-40c7-84e5-04797f11d87b', + cpm: 5, + width: 640, + height: 480, + playerWidth: 640, + playerHeight: 360, + creativeId: 'crid', + creative_id: 'crid', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastUrl: 'https://t.bidder.sparteo.com/img', + vastXml: 'tag' + }); + } + + if (FEATURES.VIDEO) { + const request = adapter.buildRequests([VALID_BID_BANNER, VALID_BID_VIDEO], BIDDER_REQUEST); + expect(adapter.interpretResponse(response, request)).to.deep.equal(formattedReponse); + } else { + const request = adapter.buildRequests([VALID_BID_BANNER], BIDDER_REQUEST_BANNER); + expect(adapter.interpretResponse(response, request)).to.deep.equal(formattedReponse); + } + }); + }); + }); + + describe('onBidWon', function() { + describe('Check methods succeed', function () { + it('should not throw error', function() { + let bids = [ + { + requestId: '1a2b3c4d', + seatBidId: 'cdbb6982-a269-40c7-84e5-04797f11d87a', + cpm: 4.5, + width: 1, + height: 1, + creativeId: 'crid', + creative_id: 'crid', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'banner', + meta: {}, + ad: 'script
', + nurl: [ + 'win.domain.com' + ] + }, + { + requestId: '2570', + seatBidId: 'cdbb6982-a269-40c7-84e5-04797f11d87b', + id: 'id-5678', + cpm: 5, + width: 640, + height: 480, + creativeId: 'crid', + currency: CURRENCY, + netRevenue: true, + ttl: TTL, + mediaType: 'video', + meta: {}, + vastXml: 'vast xml', + nurl: [ + 'win.domain2.com' + ] + } + ]; + + bids.forEach(function(bid) { + expect(adapter.onBidWon.bind(adapter, bid)).to.not.throw(); + }); + }); + }); + }); +}); From fabbbf103b628ab07dbbf2e6d78a4b371e357b62 Mon Sep 17 00:00:00 2001 From: ChangsikChoi <49671733+ChangsikChoi@users.noreply.github.com> Date: Fri, 27 Oct 2023 23:36:09 +0900 Subject: [PATCH 133/760] A1media Bid Adpater: resolve AUCTION_PRICE macro (#10624) Co-authored-by: ChangsikChoi <> --- modules/a1MediaBidAdapter.js | 17 ++++++++++++- test/spec/modules/a1MediaBidAdapter_spec.js | 28 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/a1MediaBidAdapter.js b/modules/a1MediaBidAdapter.js index 6a137e621c5..d640bbfe2d7 100644 --- a/modules/a1MediaBidAdapter.js +++ b/modules/a1MediaBidAdapter.js @@ -1,6 +1,7 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { replaceAuctionPrice } from '../src/utils.js'; const BIDDER_CODE = 'a1media'; const END_POINT = 'https://d11.contentsfeed.com/dsp/breq/a1'; @@ -81,7 +82,21 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { - const bids = converter.fromORTB({response: serverResponse.body, request: bidRequest.data}).bids; + if (!serverResponse.body) return []; + const parsedSeatbid = serverResponse.body.seatbid.map(seatbidItem => { + const parsedBid = seatbidItem.bid.map((bidItem) => ({ + ...bidItem, + adm: replaceAuctionPrice(bidItem.adm, bidItem.price), + nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price) + })); + return {...seatbidItem, bid: parsedBid}; + }); + + const responseBody = {...serverResponse.body, seatbid: parsedSeatbid}; + const bids = converter.fromORTB({ + response: responseBody, + request: bidRequest.data, + }).bids; return bids; }, diff --git a/test/spec/modules/a1MediaBidAdapter_spec.js b/test/spec/modules/a1MediaBidAdapter_spec.js index 060fe3b5a65..e1db2b9ad8d 100644 --- a/test/spec/modules/a1MediaBidAdapter_spec.js +++ b/test/spec/modules/a1MediaBidAdapter_spec.js @@ -3,6 +3,7 @@ import { config } from 'src/config.js'; import { BANNER, VIDEO, NATIVE } from 'src/mediaTypes.js'; import 'modules/currency.js'; import 'modules/priceFloors.js'; +import { replaceAuctionPrice } from '../../../src/utils'; const ortbBlockParams = { battr: [ 13 ], @@ -102,6 +103,9 @@ const getBidderResponse = () => { const bannerAdm = '
'; const videoAdm = 'testvast1'; const nativeAdm = '{"ver":"1.2","link":{"url":"test_url"},"assets":[{"id":1,"required":1,"title":{"text":"native_title"}}]}'; +const macroAdm = '
'; +const macroNurl = 'https://d11.contentsfeed.com/dsp/win/example.com/SITE/a1/${AUCTION_PRICE}'; +const interpretedNurl = `
`; describe('a1MediaBidAdapter', function() { describe('isValidRequest', function() { @@ -216,5 +220,29 @@ describe('a1MediaBidAdapter', function() { expect(interpretedRes[0].mediaType).equal(BANNER); }); }); + + describe('resolve the AUCTION_PRICE macro', function() { + let bidRequest; + beforeEach(function() { + const bidderRequest = getBidderRequest(true); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + }); + it('should return empty array when bid response has not contents', function() { + const emptyResponse = { body: '' }; + const interpretedRes = spec.interpretResponse(emptyResponse, bidRequest); + expect(interpretedRes.length).equal(0); + }); + it('should replace macro keyword if is exist', function() { + const bidderResponse = getBidderResponse(); + bidderResponse.body.seatbid[0].bid[0].adm = macroAdm; + bidderResponse.body.seatbid[0].bid[0].nurl = macroNurl; + const interpretedRes = spec.interpretResponse(bidderResponse, bidRequest); + + const expectedResPrice = 9; + const expectedAd = replaceAuctionPrice(macroAdm, expectedResPrice) + replaceAuctionPrice(interpretedNurl, expectedResPrice); + + expect(interpretedRes[0].ad).equal(expectedAd); + }); + }); }); }) From 6a1fc2e12ddec6424f97077defbe550920382b85 Mon Sep 17 00:00:00 2001 From: Rares Mihai Preda <54801398+rares-mihai-preda@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:49:55 +0300 Subject: [PATCH 134/760] Connatix Bid Adapter : update PlayerId, CustomerId and Referrer (#10604) * Update PlayerId, CustomerId and Referrer from Connatix bid response * updated unit tests * new line --- modules/connatixBidAdapter.js | 7 +++---- modules/connatixBidAdapter.md | 19 ++++++++++++++++++- test/spec/modules/connatixBidAdapter_spec.js | 20 ++------------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index df56ad580bc..7524cd4e194 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -117,13 +117,12 @@ export const spec = { interpretResponse: (serverResponse) => { const responseBody = serverResponse.body; const bids = responseBody.Bids; - const playerId = responseBody.PlayerId; - const customerId = responseBody.CustomerId; - if (!isArray(bids) || !playerId || !customerId) { + if (!isArray(bids)) { return []; } + const referrer = responseBody.Referrer; return bids.map(bidResponse => ({ requestId: bidResponse.RequestId, cpm: bidResponse.Cpm, @@ -134,8 +133,8 @@ export const spec = { width: bidResponse.Width, height: bidResponse.Height, creativeId: bidResponse.CreativeId, - referrer: bidResponse.Referrer, ad: bidResponse.Ad, + referrer: referrer, })); }, diff --git a/modules/connatixBidAdapter.md b/modules/connatixBidAdapter.md index 7ac04a64245..595c294e311 100644 --- a/modules/connatixBidAdapter.md +++ b/modules/connatixBidAdapter.md @@ -9,7 +9,24 @@ Maintainer: prebid_integration@connatix.com # Description Connects to Connatix demand source to fetch bids. -Please use ```connatix``` as the bidder code. +Please use ```connatix``` as the bidder code. + +# Configuration +Connatix requires that ```iframe``` is used for user syncing. + +Example configuration: +``` +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: '*', // represents all bidders + filter: 'include' + } + } + } +}); +``` # Test Parameters ``` diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 16ead9f9458..78f6a9d410d 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -135,14 +135,12 @@ describe('connatixBidAdapter', function () { describe('interpretResponse', function () { const CustomerId = '99f20d18-c4b4-4a28-3d8e-d43e2c8cb4ac'; const PlayerId = 'e4984e88-9ff4-45a3-8b9d-33aabcad634f'; - const Bid = {Cpm: 0.1, LineItems: [], RequestId: '2f897340c4eaa3', Ttl: 86400}; + const Bid = {Cpm: 0.1, RequestId: '2f897340c4eaa3', Ttl: 86400, CustomerId, PlayerId}; let serverResponse; this.beforeEach(function () { serverResponse = { body: { - CustomerId, - PlayerId, Bids: [ Bid ] }, headers: function() { } @@ -162,18 +160,6 @@ describe('connatixBidAdapter', function () { expect(response).to.be.an('array').that.is.empty; }); - it('Should return an empty array if CustomerId is null', function () { - serverResponse.body.CustomerId = null; - const response = spec.interpretResponse(serverResponse); - expect(response).to.be.an('array').that.is.empty; - }); - - it('Should return an empty array if PlayerId is null', function () { - serverResponse.body.PlayerId = null; - const response = spec.interpretResponse(serverResponse); - expect(response).to.be.an('array').that.is.empty; - }); - it('Should return one bid response for one bid', function() { const bidResponses = spec.interpretResponse(serverResponse); expect(bidResponses.length).to.equal(1); @@ -212,12 +198,10 @@ describe('connatixBidAdapter', function () { const CustomerId = '99f20d18-c4b4-4a28-3d8e-d43e2c8cb4ac'; const PlayerId = 'e4984e88-9ff4-45a3-8b9d-33aabcad634f'; const UserSyncEndpoint = 'https://connatix.com/sync' - const Bid = {Cpm: 0.1, LineItems: [], RequestId: '2f897340c4eaa3', Ttl: 86400}; + const Bid = {Cpm: 0.1, RequestId: '2f897340c4eaa3', Ttl: 86400, CustomerId, PlayerId}; const serverResponse = { body: { - CustomerId, - PlayerId, UserSyncEndpoint, Bids: [ Bid ] }, From fb3a6d6d3e13f106ed04367f4eb101dedbcc80c6 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Oct 2023 15:31:47 +0000 Subject: [PATCH 135/760] Prebid 8.21.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a4198915e3..c9dc8a57fd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.21.0-pre", + "version": "8.21.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c3ff9a85604..51ce94e8917 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.21.0-pre", + "version": "8.21.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8eff469e79f788c9d496abeac9d64d96c3347d06 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 27 Oct 2023 15:31:47 +0000 Subject: [PATCH 136/760] Increment version to 8.22.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9dc8a57fd8..373d8f5c5ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.21.0", + "version": "8.22.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 51ce94e8917..d68b68f3b51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.21.0", + "version": "8.22.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From faff309001a03abf8f25d5b8cbf6975d22d93cda Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Sat, 28 Oct 2023 03:37:56 +0800 Subject: [PATCH 137/760] Discovery Bid Adapter: add adUnitCode, gpid, and adslot (#10615) * feat(getItems): add bid request params * feat(getItems): add bid request imp.ext params * feat(getItems): add plaintext * feat(spec):test buildRequests * feat(bid params): use utils.parseSizesInput to get screen size * feat(getScreenSize): add plaintext --------- Co-authored-by: yubei01 --- modules/discoveryBidAdapter.js | 59 +++++++++++++++++++ test/spec/modules/discoveryBidAdapter_spec.js | 40 +++++++++++++ 2 files changed, 99 insertions(+) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 7ad75f64215..465c6754e77 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -211,6 +211,60 @@ const popInAdSize = [ { w: 336, h: 280 }, ]; +/** + * get screen size + * + * @returns {Array} eg: "['widthxheight']" + */ +function getScreenSize() { + return utils.parseSizesInput([window.screen.width, window.screen.height]); +} + +/** + * @param {BidRequest} bidRequest + * @param bidderRequest + * @returns {string} + */ +function getReferrer(bidRequest = {}, bidderRequest = {}) { + let pageUrl; + if (bidRequest.params && bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else { + pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page'); + } + return pageUrl; +} + +/** + * format imp ad test ext params + * + * @param validBidRequest sigleBidRequest + * @param bidderRequest + */ +function addImpExtParams(bidRequest = {}, bidderRequest = {}) { + const { deepAccess } = utils; + const { params = {}, adUnitCode } = bidRequest; + const ext = { + adUnitCode: adUnitCode || '', + token: params.token || '', + siteId: params.siteId || '', + zoneId: params.zoneId || '', + publisher: params.publisher || '', + p_pos: params.position || '', + screenSize: getScreenSize(), + referrer: getReferrer(bidRequest, bidderRequest), + b_pos: deepAccess(bidRequest, 'mediaTypes.banner.pos', '', ''), + ortbUser: deepAccess(bidRequest, 'ortb2.user', {}, {}), + ortbSite: deepAccess(bidRequest, 'ortb2.site', {}, {}), + tid: deepAccess(bidRequest, 'ortb2Imp.ext.tid', '', ''), + browsiViewability: deepAccess(bidRequest, 'ortb2Imp.ext.data.browsi.browsiViewability', '', ''), + adserverName: deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.name', '', ''), + adslot: deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot', '', ''), + gpid: deepAccess(bidRequest, 'ortb2Imp.ext.gpid', '', ''), + }; + return ext; +} + /** * get aditem setting * @param {Array} validBidRequests an an array of bids @@ -261,6 +315,11 @@ function getItems(validBidRequests, bidderRequest) { tagid: req.params && req.params.tagid }; } + + try { + ret.ext = addImpExtParams(req, bidderRequest); + } catch (e) {} + itemMaps[id] = { req, ret, diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 078add73046..9ebedfb7438 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -11,11 +11,51 @@ describe('discovery:BidAdapterTests', function () { bidder: 'discovery', params: { token: 'd0f4902b616cc5c38cbe0a08676d0ed9', + siteId: 'siteId_01', + zoneId: 'zoneId_01', + publisher: '52', + position: 'left', + referrer: 'https://discovery.popin.cc', + }, + refererInfo: { + page: 'https://discovery.popin.cc', }, mediaTypes: { banner: { sizes: [[300, 250]], + pos: 'left', + }, + }, + ortb2: { + user: { + ext: { + data: { + CxSegments: [] + } + } }, + site: { + domain: 'discovery.popin.cc', + publisher: { + domain: 'discovery.popin.cc' + }, + page: 'https://discovery.popin.cc' + }, + }, + ortb2Imp: { + ext: { + gpid: 'adslot_gpid', + tid: 'tid_01', + data: { + browsi: { + browsiViewability: 'NA', + }, + adserver: { + name: 'adserver_name', + adslot: 'adslot_name', + } + } + } }, adUnitCode: 'regular_iframe', transactionId: 'd163f9e2-7ecd-4c2c-a3bd-28ceb52a60ee', From 0e617283e8e34a85d3e6466755ffba3671655c1d Mon Sep 17 00:00:00 2001 From: hamper Date: Mon, 30 Oct 2023 15:27:50 +0300 Subject: [PATCH 138/760] Programmatica Bid Adapter : initial release (#10598) * add programmatica bid adapter * add more tests --- modules/programmaticaBidAdapter.js | 153 ++++++++++ modules/programmaticaBidAdapter.md | 46 +++ .../modules/programmaticaBidAdapter_spec.js | 263 ++++++++++++++++++ 3 files changed, 462 insertions(+) create mode 100644 modules/programmaticaBidAdapter.js create mode 100644 modules/programmaticaBidAdapter.md create mode 100644 test/spec/modules/programmaticaBidAdapter_spec.js diff --git a/modules/programmaticaBidAdapter.js b/modules/programmaticaBidAdapter.js new file mode 100644 index 00000000000..7d52e305189 --- /dev/null +++ b/modules/programmaticaBidAdapter.js @@ -0,0 +1,153 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { deepAccess, parseSizesInput, isArray } from '../src/utils.js'; + +const BIDDER_CODE = 'programmatica'; +const DEFAULT_ENDPOINT = 'asr.programmatica.com'; +const SYNC_ENDPOINT = 'sync.programmatica.com'; +const ADOMAIN = 'programmatica.com'; +const TIME_TO_LIVE = 360; + +export const spec = { + code: BIDDER_CODE, + + isBidRequestValid: function(bid) { + let valid = bid.params.siteId && bid.params.placementId; + + return !!valid; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let requests = []; + for (const bid of validBidRequests) { + let endpoint = bid.params.endpoint || DEFAULT_ENDPOINT; + + requests.push({ + method: 'GET', + url: `https://${endpoint}/get`, + data: { + site_id: bid.params.siteId, + placement_id: bid.params.placementId, + prebid: true, + }, + bidRequest: bid, + }); + } + + return requests; + }, + + interpretResponse: function(serverResponse, request) { + if (!serverResponse?.body?.content?.data) { + return []; + } + + const bidResponses = []; + const body = serverResponse.body; + + let mediaType = BANNER; + let ad, vastXml; + let width; + let height; + + let sizes = getSize(body.size); + if (isArray(sizes)) { + [width, height] = sizes; + } + + if (body.type.format != '') { + // banner + ad = body.content.data; + if (body.content.imps?.length) { + for (const imp of body.content.imps) { + ad += ``; + } + } + } else { + // video + vastXml = body.content.data; + mediaType = VIDEO; + + if (!width || !height) { + const pSize = deepAccess(request.bidRequest, 'mediaTypes.video.playerSize'); + const reqSize = getSize(pSize); + if (isArray(reqSize)) { + [width, height] = reqSize; + } + } + } + + const bidResponse = { + requestId: request.bidRequest.bidId, + cpm: body.cpm, + currency: body.currency || 'USD', + width: parseInt(width), + height: parseInt(height), + creativeId: body.id, + netRevenue: true, + ttl: TIME_TO_LIVE, + ad: ad, + mediaType: mediaType, + vastXml: vastXml, + meta: { + advertiserDomains: [ADOMAIN], + } + }; + + if ((mediaType === VIDEO && request.bidRequest.mediaTypes?.video) || (mediaType === BANNER && request.bidRequest.mediaTypes?.banner)) { + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = [] + + if (!hasPurpose1Consent(gdprConsent)) { + return syncs; + } + + let params = `usp=${uspConsent ?? ''}&consent=${gdprConsent?.consentString ?? ''}`; + if (typeof gdprConsent?.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}`; + } + + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `//${SYNC_ENDPOINT}/match/sp.ifr?${params}` + }); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: `//${SYNC_ENDPOINT}/match/sp?${params}` + }); + } + + return syncs; + }, + + onTimeout: function(timeoutData) {}, + onBidWon: function(bid) {}, + onSetTargeting: function(bid) {}, + onBidderError: function() {}, + supportedMediaTypes: [ BANNER, VIDEO ] +} + +registerBidder(spec); + +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; +} diff --git a/modules/programmaticaBidAdapter.md b/modules/programmaticaBidAdapter.md new file mode 100644 index 00000000000..5982edf143e --- /dev/null +++ b/modules/programmaticaBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: Programmatica Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@programmatica.com +``` + +# Description +Connects to Programmatica server for bids. +Module supports banner and video mediaType. + +# Test Parameters + +``` + var adUnits = [{ + code: '/test/div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'programmatica', + params: { + siteId: 'cga9l34ipgja79esubrg', + placementId: 'cgim20sipgj0vj1cb510' + } + }] + }, + { + code: '/test/div', + mediaTypes: { + video: { + playerSize: [[640, 360]] + } + }, + bids: [{ + bidder: 'programmatica', + params: { + siteId: 'cga9l34ipgja79esubrg', + placementId: 'cioghpcipgj8r721e9ag' + } + }] + },]; +``` diff --git a/test/spec/modules/programmaticaBidAdapter_spec.js b/test/spec/modules/programmaticaBidAdapter_spec.js new file mode 100644 index 00000000000..247d20752c3 --- /dev/null +++ b/test/spec/modules/programmaticaBidAdapter_spec.js @@ -0,0 +1,263 @@ +import { expect } from 'chai'; +import { spec } from 'modules/programmaticaBidAdapter.js'; +import { deepClone } from 'src/utils.js'; + +describe('programmaticaBidAdapterTests', function () { + let bidRequestData = { + bids: [ + { + bidId: 'testbid', + bidder: 'programmatica', + params: { + siteId: 'testsite', + placementId: 'testplacement', + }, + sizes: [[300, 250]] + } + ] + }; + let request = []; + + it('validate_pub_params', function () { + expect( + spec.isBidRequestValid({ + bidder: 'programmatica', + params: { + siteId: 'testsite', + placementId: 'testplacement', + } + }) + ).to.equal(true); + }); + + it('validate_generated_url', function () { + const request = spec.buildRequests(deepClone(bidRequestData.bids), { timeout: 1234 }); + let req_url = request[0].url; + + expect(req_url).to.equal('https://asr.programmatica.com/get'); + }); + + it('validate_response_params', function () { + let serverResponse = { + body: { + 'id': 'crid', + 'type': { + 'format': 'Image', + 'source': 'passback', + 'dspId': '', + 'dspCreativeId': '' + }, + 'content': { + 'data': 'test ad', + 'imps': null, + 'click': { + 'url': '', + 'track': null + } + }, + 'size': '300x250', + 'matching': '', + 'cpm': 10, + 'currency': 'USD' + } + }; + + const bidRequest = deepClone(bidRequestData.bids) + bidRequest[0].mediaTypes = { + banner: {} + } + + const request = spec.buildRequests(bidRequest); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(10); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crid'); + expect(bid.meta.advertiserDomains).to.deep.equal(['programmatica.com']); + }); + + it('validate_response_params_imps', function () { + let serverResponse = { + body: { + 'id': 'crid', + 'type': { + 'format': 'Image', + 'source': 'passback', + 'dspId': '', + 'dspCreativeId': '' + }, + 'content': { + 'data': 'test ad', + 'imps': [ + 'testImp' + ], + 'click': { + 'url': '', + 'track': null + } + }, + 'size': '300x250', + 'matching': '', + 'cpm': 10, + 'currency': 'USD' + } + }; + + const bidRequest = deepClone(bidRequestData.bids) + bidRequest[0].mediaTypes = { + banner: {} + } + + const request = spec.buildRequests(bidRequest); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(10); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crid'); + expect(bid.meta.advertiserDomains).to.deep.equal(['programmatica.com']); + }) + + it('validate_invalid_response', function () { + let serverResponse = { + body: {} + }; + + const bidRequest = deepClone(bidRequestData.bids) + bidRequest[0].mediaTypes = { + banner: {} + } + + const request = spec.buildRequests(bidRequest); + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(0); + }) + + it('video_bid', function () { + const bidRequest = deepClone(bidRequestData.bids); + bidRequest[0].mediaTypes = { + video: { + playerSize: [234, 765] + } + }; + + const request = spec.buildRequests(bidRequest, { timeout: 1234 }); + const vastXml = ''; + let serverResponse = { + body: { + 'id': 'cki2n3n6snkuulqutpf0', + 'type': { + 'format': '', + 'source': 'rtb', + 'dspId': '1' + }, + 'content': { + 'data': vastXml, + 'imps': [ + 'https://asr.dev.programmatica.com/track/imp' + ], + 'click': { + 'url': '', + 'track': null + } + }, + 'size': '', + 'matching': '', + 'cpm': 70, + 'currency': 'RUB' + } + }; + + let bids = spec.interpretResponse(serverResponse, request[0]); + expect(bids).to.have.lengthOf(1); + + let bid = bids[0]; + expect(bid.mediaType).to.equal('video'); + expect(bid.vastXml).to.equal(vastXml); + expect(bid.width).to.equal(234); + expect(bid.height).to.equal(765); + }); +}); + +describe('getUserSyncs', function() { + it('returns empty sync array', function() { + const syncOptions = {}; + + expect(spec.getUserSyncs(syncOptions)).to.deep.equal([]); + }); + + it('Should return array of objects with proper sync config , include CCPA', function() { + const syncData = spec.getUserSyncs({ + pixelEnabled: true, + }, {}, {}, '1---'); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.programmatica.com/match/sp?usp=1---&consent=') + }); + + it('Should return array of objects with proper sync config , include GDPR', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: true, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: { + purpose: { + consents: { + 1: true + }, + }, + } + }, ''); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.programmatica.com/match/sp.ifr?usp=&consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=1') + }); + + it('Should return array of objects with proper sync config , include GDPR, no purpose', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: true, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: { + purpose: { + consents: { + 1: false + }, + }, + } + }, ''); + expect(syncData).is.empty; + }); + + it('Should return array of objects with proper sync config , GDPR not applies', function() { + const syncData = spec.getUserSyncs({ + iframeEnabled: true, + }, {}, { + gdprApplies: false, + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + }, ''); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('iframe') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('//sync.programmatica.com/match/sp.ifr?usp=&consent=COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw&gdpr=0') + }); +}) From 5bea60769c2e05a81b8e51e664cc0c1a5e0cfc65 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 30 Oct 2023 13:34:41 +0100 Subject: [PATCH 139/760] Impactify Bid Adapter : add different mediatypes and manage local storage (#10601) * Remove use of local storage As requested, we remove the use of local storage. https://github.com/prebid/Prebid.js/issues/8689 * Update impactifyBidAdapter.js * Add differents mediatypes to Impactify bidder * Add differents mediatypes to Impactify bidder * Add differents mediatypes to Impactify bidder * Add format parameter for banner * add getFloor * add getFloor * add getFloor * add parsing of local storage * delete unused var * fix spacing with import * Add local storage key management * Adjustments * Fix eids object * Fix eids object * Fix eids object * Fix tests --------- Co-authored-by: Thomas De Stefano Co-authored-by: Pang Ronnie --- modules/impactifyBidAdapter.js | 212 +++++++++++++----- modules/impactifyBidAdapter.md | 38 +++- test/spec/modules/impactifyBidAdapter_spec.js | 173 ++++++++++++-- 3 files changed, 342 insertions(+), 81 deletions(-) diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index f2bf9aaddcb..f446050265a 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,7 +1,10 @@ -import {deepAccess, deepSetValue, generateUUID} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {config} from '../src/config.js'; -import {ajax} from '../src/ajax.js'; +'use strict'; + +import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; @@ -10,35 +13,101 @@ const DEFAULT_VIDEO_WIDTH = 640; const DEFAULT_VIDEO_HEIGHT = 360; const ORIGIN = 'https://sonic.impactify.media'; const LOGGER_URI = 'https://logger.impactify.media'; -const AUCTIONURI = '/bidder'; -const COOKIESYNCURI = '/static/cookie_sync.html'; -const GVLID = 606; -const GETCONFIG = config.getConfig; - -const getDeviceType = () => { - // OpenRTB Device type - if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { - return 5; - } - if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { - return 4; - } - return 2; -}; +const AUCTION_URI = '/bidder'; +const COOKIE_SYNC_URI = '/static/cookie_sync.html'; +const GVL_ID = 606; +const GET_CONFIG = config.getConfig; +export const STORAGE = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); +export const STORAGE_KEY = '_im_str' + +/** + * Helpers object + * @type {{getExtParamsFromBid(*): {impactify: {appId}}, createOrtbImpVideoObj(*): {context: string, playerSize: [number,number], id: string, mimes: [string]}, getDeviceType(): (number), createOrtbImpBannerObj(*, *): {format: [], id: string}}} + */ +const helpers = { + getExtParamsFromBid(bid) { + let ext = { + impactify: { + appId: bid.params.appId + }, + }; -const getFloor = (bid) => { - const floorInfo = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: '*', - size: '*' - }); - if (typeof floorInfo === 'object' && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { - return parseFloat(floorInfo.floor); + if (typeof bid.params.format == 'string') { + ext.impactify.format = bid.params.format; + } + + if (typeof bid.params.style == 'string') { + ext.impactify.style = bid.params.style; + } + + if (typeof bid.params.container == 'string') { + ext.impactify.container = bid.params.container; + } + + if (typeof bid.params.size == 'string') { + ext.impactify.size = bid.params.size; + } + + return ext; + }, + + getDeviceType() { + // OpenRTB Device type + if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(navigator.userAgent.toLowerCase()))) { + return 5; + } + if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(navigator.userAgent.toLowerCase()))) { + return 4; + } + return 2; + }, + + createOrtbImpBannerObj(bid, size) { + let sizes = size.split('x'); + + return { + id: 'banner-' + bid.bidId, + format: [{ + w: parseInt(sizes[0]), + h: parseInt(sizes[1]) + }] + } + }, + + createOrtbImpVideoObj(bid) { + return { + id: 'video-' + bid.bidId, + playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], + context: 'outstream', + mimes: ['video/mp4'], + } + }, + + getFloor(bid) { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + return parseFloat(floorInfo.floor); + } + return null; + }, + + getImStrFromLocalStorage() { + return STORAGE.localStorageIsEnabled(false) ? STORAGE.getDataFromLocalStorage(STORAGE_KEY, false) : ''; } - return null; + } -const createOpenRtbRequest = (validBidRequests, bidderRequest) => { +/** + * Create an OpenRTB formated object from prebid payload + * @param validBidRequests + * @param bidderRequest + * @returns {{cur: string[], validBidRequests, id, source: {tid}, imp: *[]}} + */ +function createOpenRtbRequest(validBidRequests, bidderRequest) { // Create request and set imp bids inside let request = { id: bidderRequest.bidderRequestId, @@ -52,16 +121,17 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const checkPrebid = urlParams.get('_checkPrebid'); - // Force impactify debugging parameter + + // Force impactify debugging parameter if present if (checkPrebid != null) { request.test = Number(checkPrebid); } - // Set Schain in request + // Set SChain in request let schain = deepAccess(validBidRequests, '0.schain'); if (schain) request.source.ext = { schain: schain }; - // Set eids + // Set Eids let eids = deepAccess(validBidRequests, '0.userIdAsEids'); if (eids && eids.length) { deepSetValue(request, 'user.ext.eids', eids); @@ -73,7 +143,7 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { request.device = { w: window.innerWidth, h: window.innerHeight, - devicetype: getDeviceType(), + devicetype: helpers.getDeviceType(), ua: navigator.userAgent, js: 1, dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, @@ -91,9 +161,10 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { if (bidderRequest.uspConsent) { deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); + this.syncStore.uspConsent = bidderRequest.uspConsent; } - if (GETCONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); + if (GET_CONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); if (bidderRequest.uspConsent) { deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); @@ -104,42 +175,50 @@ const createOpenRtbRequest = (validBidRequests, bidderRequest) => { // Create imps with bids validBidRequests.forEach((bid) => { + let bannerObj = deepAccess(bid.mediaTypes, `banner`); + let videoObj = deepAccess(bid.mediaTypes, `video`); + let imp = { id: bid.bidId, bidfloor: bid.params.bidfloor ? bid.params.bidfloor : 0, - ext: { - impactify: { - appId: bid.params.appId, - format: bid.params.format, - style: bid.params.style - }, - }, - video: { - playerSize: [DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT], - context: 'outstream', - mimes: ['video/mp4'], - }, + ext: helpers.getExtParamsFromBid(bid) }; - if (bid.params.container) { - imp.ext.impactify.container = bid.params.container; + + if (videoObj) { + imp.video = { + ...helpers.createOrtbImpVideoObj(bid) + } } + + if (bannerObj && typeof imp.ext.impactify.size == 'string') { + imp.banner = { + ...helpers.createOrtbImpBannerObj(bid, imp.ext.impactify.size) + } + } + if (typeof bid.getFloor === 'function') { - const floor = getFloor(bid); + const floor = helpers.getFloor(bid); if (floor) { imp.bidfloor = floor; } } + request.imp.push(imp); }); return request; -}; +} +/** + * Export BidderSpec type object and register it to Prebid + * @type {{supportedMediaTypes: string[], interpretResponse: ((function(ServerResponse, *): Bid[])|*), code: string, aliases: string[], getUserSyncs: ((function(SyncOptions, ServerResponse[], *, *): UserSync[])|*), buildRequests: (function(*, *): {method: string, data: string, url}), onTimeout: (function(*): boolean), gvlid: number, isBidRequestValid: ((function(BidRequest): (boolean))|*), onBidWon: (function(*): boolean)}} + */ export const spec = { code: BIDDER_CODE, - gvlid: GVLID, + gvlid: GVL_ID, supportedMediaTypes: ['video', 'banner'], aliases: BIDDER_ALIAS, + storageAllowed: true, /** * Determines whether or not the given bid request is valid. @@ -148,13 +227,21 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!bid.params.appId || typeof bid.params.appId != 'string' || !bid.params.format || typeof bid.params.format != 'string' || !bid.params.style || typeof bid.params.style != 'string') { + let isBanner = deepAccess(bid.mediaTypes, `banner`); + + if (typeof bid.params.appId != 'string' || !bid.params.appId) { return false; } - if (bid.params.format != 'screen' && bid.params.format != 'display') { + if (typeof bid.params.format != 'string' || typeof bid.params.style != 'string' || !bid.params.format || !bid.params.style) { return false; } - if (bid.params.style != 'inline' && bid.params.style != 'impact' && bid.params.style != 'static') { + if (bid.params.format !== 'screen' && bid.params.format !== 'display') { + return false; + } + if (bid.params.style !== 'inline' && bid.params.style !== 'impact' && bid.params.style !== 'static') { + return false; + } + if (isBanner && (typeof bid.params.size != 'string' || !bid.params.size.includes('x') || bid.params.size.split('x').length != 2)) { return false; } @@ -171,11 +258,20 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { // Create a clean openRTB request let request = createOpenRtbRequest(validBidRequests, bidderRequest); + const imStr = helpers.getImStrFromLocalStorage(); + const options = {} + + if (imStr) { + options.customHeaders = { + 'x-impact': imStr + }; + } return { method: 'POST', - url: ORIGIN + AUCTIONURI, + url: ORIGIN + AUCTION_URI, data: JSON.stringify(request), + options }; }, @@ -265,7 +361,7 @@ export const spec = { return [{ type: 'iframe', - url: ORIGIN + COOKIESYNCURI + params + url: ORIGIN + COOKIE_SYNC_URI + params }]; }, @@ -274,7 +370,7 @@ export const spec = { * @param {Bid} The bid that won the auction */ onBidWon: function(bid) { - ajax(`${LOGGER_URI}/log/bidder/won`, null, JSON.stringify(bid), { + ajax(`${LOGGER_URI}/prebid/won`, null, JSON.stringify(bid), { method: 'POST', contentType: 'application/json' }); @@ -287,7 +383,7 @@ export const spec = { * @param {data} Containing timeout specific data */ onTimeout: function(data) { - ajax(`${LOGGER_URI}/log/bidder/timeout`, null, JSON.stringify(data[0]), { + ajax(`${LOGGER_URI}/prebid/timeout`, null, JSON.stringify(data[0]), { method: 'POST', contentType: 'application/json' }); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md index 3de9a8cfb84..de3373395dc 100644 --- a/modules/impactifyBidAdapter.md +++ b/modules/impactifyBidAdapter.md @@ -10,14 +10,22 @@ Maintainer: thomas.destefano@impactify.io Module that connects to the Impactify solution. The impactify bidder need 3 parameters: - - appId : This is your unique publisher identifier - - format : This is the ad format needed, can be : screen or display - - style : This is the ad style needed, can be : inline, impact or static +- appId : This is your unique publisher identifier +- format : This is the ad format needed, can be : screen or display (Only for video media type) +- style : This is the ad style needed, can be : inline, impact or static (Only for video media type) + +Note : Impactify adapter need storage access to work properly (Do not forget to set storageAllowed to true). # Test Parameters ``` - var adUnits = [{ - code: 'your-slot-div-id', // This is your slot div id + pbjs.bidderSettings = { + impactify: { + storageAllowed: true // Mandatory + } + }; + + var adUnitsVideo = [{ + code: 'your-slot-div-id-video', // This is your slot div id mediaTypes: { video: { context: 'outstream' @@ -32,4 +40,24 @@ The impactify bidder need 3 parameters: } }] }]; + + var adUnitsBanner = [{ + code: 'your-slot-div-id-banner', // This is your slot div id + mediaTypes: { + banner: { + sizes: [ + [728, 90] + ] + } + }, + bids: [{ + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'display', + size: '728x90', + style: 'static' + } + }] + }]; ``` diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js index 215972ff450..adf968d610d 100644 --- a/test/spec/modules/impactifyBidAdapter_spec.js +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { spec } from 'modules/impactifyBidAdapter.js'; +import { spec, STORAGE, STORAGE_KEY } from 'modules/impactifyBidAdapter.js'; import * as utils from 'src/utils.js'; +import sinon from 'sinon'; const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; @@ -19,89 +20,203 @@ var gdprData = { }; describe('ImpactifyAdapter', function () { + let getLocalStorageStub; + let localStorageIsEnabledStub; + let sandbox; + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + impactify: { + storageAllowed: true + } + }; + sinon.stub(document.body, 'appendChild'); + sandbox = sinon.sandbox.create(); + getLocalStorageStub = sandbox.stub(STORAGE, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sandbox.stub(STORAGE, 'localStorageIsEnabled'); + }); + + afterEach(function() { + $$PREBID_GLOBAL$$.bidderSettings = {}; + document.body.appendChild.restore(); + sandbox.restore(); + }); + describe('isBidRequestValid', function () { - let validBid = { - bidder: 'impactify', - params: { - appId: '1', - format: 'screen', - style: 'inline' + let validBids = [ + { + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'screen', + style: 'inline' + } + }, + { + bidder: 'impactify', + params: { + appId: 'example.com', + format: 'display', + size: '728x90', + style: 'static' + } + } + ]; + + let videoBidRequests = [ + { + bidder: 'impactify', + params: { + appId: '1', + format: 'screen', + style: 'inline' + }, + mediaTypes: { + video: { + context: 'instream' + } + }, + adUnitCode: 'adunit-code', + sizes: [[DEFAULT_VIDEO_WIDTH, DEFAULT_VIDEO_HEIGHT]], + bidId: '123456789', + bidderRequestId: '987654321', + auctionId: '19ab94a9-b0d7-4ed7-9f80-ad0c033cf1b1', + transactionId: 'f7b2c372-7a7b-11eb-9439-0242ac130002', + userId: { + pubcid: '87a0327b-851c-4bb3-a925-0c7be94548f5' + }, + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [ + { + id: '87a0327b-851c-4bb3-a925-0c7be94548f5', + atype: 1 + } + ] + } + ] + } + ]; + let videoBidderRequest = { + bidderRequestId: '98845765110', + auctionId: '165410516454', + bidderCode: 'impactify', + bids: [ + { + ...videoBidRequests[0] + } + ], + refererInfo: { + referer: 'https://impactify.io' } }; it('should return true when required params found', function () { - expect(spec.isBidRequestValid(validBid)).to.equal(true); + expect(spec.isBidRequestValid(validBids[0])).to.equal(true); + expect(spec.isBidRequestValid(validBids[1])).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, validBid); + let bid = Object.assign({}, validBids[0]); delete bid.params; bid.params = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); + + let bid2 = Object.assign({}, validBids[1]); + delete bid2.params; + bid2.params = {}; + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); it('should return false when appId is missing', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); delete bid.params.appId; - expect(spec.isBidRequestValid(bid)).to.equal(false); + + const bid2 = utils.deepClone(validBids[1]); + delete bid2.params.appId; + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); it('should return false when appId is not a string', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); + const bid2 = utils.deepClone(validBids[1]); bid.params.appId = 123; + bid2.params.appId = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); bid.params.appId = false; + bid2.params.appId = false; expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); bid.params.appId = void (0); + bid2.params.appId = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); bid.params.appId = {}; + bid2.params.appId = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); it('should return false when format is missing', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); delete bid.params.format; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when format is not a string', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); + const bid2 = utils.deepClone(validBids[1]); bid.params.format = 123; + bid2.params.format = 123; + expect(spec.isBidRequestValid(bid)).to.equal(false); expect(spec.isBidRequestValid(bid)).to.equal(false); bid.params.format = false; + bid2.params.format = false; expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); bid.params.format = void (0); + bid2.params.format = void (0); expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); bid.params.format = {}; + bid2.params.format = {}; expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(bid2)).to.equal(false); }); it('should return false when format is not equals to screen or display', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); if (bid.params.format != 'screen' && bid.params.format != 'display') { expect(spec.isBidRequestValid(bid)).to.equal(false); } + + const bid2 = utils.deepClone(validBids[1]); + if (bid2.params.format != 'screen' && bid2.params.format != 'display') { + expect(spec.isBidRequestValid(bid2)).to.equal(false); + } }); it('should return false when style is missing', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); delete bid.params.style; expect(spec.isBidRequestValid(bid)).to.equal(false); }); it('should return false when style is not a string', () => { - const bid = utils.deepClone(validBid); + const bid = utils.deepClone(validBids[0]); bid.params.style = 123; expect(spec.isBidRequestValid(bid)).to.equal(false); @@ -174,15 +289,37 @@ describe('ImpactifyAdapter', function () { } } - const res = spec.buildRequests(videoBidRequests, videoBidderRequest) + const res = spec.buildRequests(videoBidRequests, videoBidderRequest); const resData = JSON.parse(res.data) expect(resData.imp[0].bidfloor).to.equal(1.23) }); it('sends video bid request to ENDPOINT via POST', function () { + localStorageIsEnabledStub.returns(true); + + getLocalStorageStub.returns('testValue'); + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.url).to.equal(ORIGIN + AUCTIONURI); expect(request.method).to.equal('POST'); + expect(request.options.customHeaders['x-impact']).to.equal('testValue'); + }); + + it('should set header value from localstorage correctly', function () { + localStorageIsEnabledStub.returns(true); + getLocalStorageStub.returns('testValue'); + + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.options.customHeaders).to.be.an('object'); + expect(request.options.customHeaders['x-impact']).to.equal('testValue'); + }); + + it('should set header value to empty if localstorage is not enabled', function () { + localStorageIsEnabledStub.returns(false); + + const request = spec.buildRequests(videoBidRequests, videoBidderRequest); + expect(request.options.customHeaders).to.be.undefined; }); }); describe('interpretResponse', function () { From 56292c4672a0b19bee34840dca93bd3e1e5b8afe Mon Sep 17 00:00:00 2001 From: adxpremium <55161519+adxpremium@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:24:36 +0100 Subject: [PATCH 140/760] dynamicAdBoostModule: New module (#10377) --- modules/dynamicAdBoostRtdProvider.js | 114 ++++++++++++++++++ modules/dynamicAdBoostRtdProvider.md | 40 ++++++ .../modules/dynamicAdBoostRtdProvider_spec.js | 77 ++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 modules/dynamicAdBoostRtdProvider.js create mode 100644 modules/dynamicAdBoostRtdProvider.md create mode 100644 test/spec/modules/dynamicAdBoostRtdProvider_spec.js diff --git a/modules/dynamicAdBoostRtdProvider.js b/modules/dynamicAdBoostRtdProvider.js new file mode 100644 index 00000000000..fe08795f313 --- /dev/null +++ b/modules/dynamicAdBoostRtdProvider.js @@ -0,0 +1,114 @@ +/** + * The {@link module:modules/realTimeData} module is required + * @module modules/dynamicAdBoost + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js' +import { loadExternalScript } from '../src/adloader.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; + +const MODULE_NAME = 'dynamicAdBoost'; +const SCRIPT_URL = 'https://adxbid.info'; +const CLIENT_SUPPORTS_IO = window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && + 'intersectionRatio' in window.IntersectionObserverEntry.prototype; +// Options for the Intersection Observer +const dabOptions = { + threshold: 0.5 // Trigger callback when 50% of the element is visible +}; +let observer; +let dabStartDate; +let dabStartTime; + +// Array of div IDs to track +let dynamicAdBoostAdUnits = {}; + +function init(config, userConsent) { + dabStartDate = new Date(); + dabStartTime = dabStartDate.getTime(); + if (!CLIENT_SUPPORTS_IO) { + return false; + } + // Create an Intersection Observer instance + observer = new IntersectionObserver(dabHandleIntersection, dabOptions); + if (config.params.keyId) { + let keyId = config.params.keyId; + if (keyId && !isEmptyStr(keyId)) { + let dabDivIdsToTrack = config.params.adUnits; + let dabInterval = setInterval(function() { + // Observe each div by its ID + dabDivIdsToTrack.forEach(divId => { + let div = document.getElementById(divId); + if (div) { + observer.observe(div); + } + }); + + let dabDateNow = new Date(); + let dabTimeNow = dabDateNow.getTime(); + let dabElapsedSeconds = Math.floor((dabTimeNow - dabStartTime) / 1000); + let elapsedThreshold = 30; + if (config.params.threshold) { + elapsedThreshold = config.params.threshold; + } + if (dabElapsedSeconds >= elapsedThreshold) { + clearInterval(dabInterval); // Stop + loadLmScript(keyId); + } + }, 1000); + + return true; + } + } + return false; +} + +function loadLmScript(keyId) { + let viewableAdUnits = Object.keys(dynamicAdBoostAdUnits); + let viewableAdUnitsCSV = viewableAdUnits.join(','); + const scriptUrl = `${SCRIPT_URL}/${keyId}.js?viewableAdUnits=${viewableAdUnitsCSV}`; + loadExternalScript(scriptUrl, MODULE_NAME); + observer.disconnect(); +} + +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + const reqAdUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; + + if (Array.isArray(reqAdUnits)) { + reqAdUnits.forEach(adunit => { + let gptCode = deepAccess(adunit, 'code'); + if (dynamicAdBoostAdUnits.hasOwnProperty(gptCode)) { + // AdUnits has reached target viewablity at some point + deepSetValue(adunit, `ortb2Imp.ext.data.${MODULE_NAME}.${gptCode}`, dynamicAdBoostAdUnits[gptCode]); + } + }); + } + callback(); +} + +let markViewed = (entry, observer) => { + return () => { + observer.unobserve(entry.target); + } +} + +// Callback function when an observed element becomes visible +function dabHandleIntersection(entries) { + entries.forEach(entry => { + if (entry.isIntersecting && entry.intersectionRatio > 0.5) { + dynamicAdBoostAdUnits[entry.target.id] = entry.intersectionRatio; + markViewed(entry, observer) + } + }); +} + +/** @type {RtdSubmodule} */ +export const subModuleObj = { + name: MODULE_NAME, + init, + getBidRequestData, + markViewed +}; + +submodule('realTimeData', subModuleObj); diff --git a/modules/dynamicAdBoostRtdProvider.md b/modules/dynamicAdBoostRtdProvider.md new file mode 100644 index 00000000000..93efe3b3f97 --- /dev/null +++ b/modules/dynamicAdBoostRtdProvider.md @@ -0,0 +1,40 @@ +# Overview + +Module Name: Dynamic Ad Boost +Module Type: Track when a adunit is viewable +Maintainer: info@luponmedia.com + +# Description + +Enhance your revenue with the cutting-edge DynamicAdBoost module! By seamlessly integrating the powerful LuponMedia technology, our module retrieves adunits viewability data, providing publishers with valuable insights to optimize their revenue streams. To unlock the full potential of this technology, we provide a customized LuponMedia module tailored to your specific site requirements. Boost your ad revenue and gain unprecedented visibility into your performance with our advanced solution. + +In order to utilize this module, it is essential to collaborate with [LuponMedia](https://www.luponmedia.com/) to create an account and obtain detailed guidelines on configuring your sites. Working hand in hand with LuponMedia will ensure a smooth integration process, enabling you to fully leverage the capabilities of this module on your website. Take the first step towards optimizing your ad revenue and enhancing your site's performance by partnering with LuponMedia for a seamless experience. +Contact info@luponmedia.com for information. + +## Building Prebid with Real-time Data Support + +First, make sure to add the Dynamic AdBoost submodule to your Prebid.js package with: + +`gulp build --modules=rtdModule,dynamicAdBoostRtdProvider` + +The following configuration parameters are available: + +``` +pbjs.setConfig( + ... + realTimeData: { + auctionDelay: 2000, + dataProviders: [ + { + name: "dynamicAdBoost", + params: { + keyId: "[PROVIDED_KEY]", // Your provided Dynamic AdBoost keyId + adUnits: ["allowedAdUnit1", "allowedAdUnit2"], + threshold: 35 // optional + } + } + ] + } + ... +} +``` diff --git a/test/spec/modules/dynamicAdBoostRtdProvider_spec.js b/test/spec/modules/dynamicAdBoostRtdProvider_spec.js new file mode 100644 index 00000000000..66c24435589 --- /dev/null +++ b/test/spec/modules/dynamicAdBoostRtdProvider_spec.js @@ -0,0 +1,77 @@ +import { subModuleObj as rtdProvider } from 'modules/dynamicAdBoostRtdProvider.js'; +import { loadExternalScript } from '../../../src/adloader.js'; +import { expect } from 'chai'; + +const configWithParams = { + params: { + keyId: 'dynamic', + adUnits: ['gpt-123'], + threshold: 1 + } +}; + +const configWithoutRequiredParams = { + params: { + keyId: '' + } +}; + +describe('dynamicAdBoost', function() { + let clock; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(Date.now()); + }); + afterEach(function () { + sandbox.restore(); + }); + describe('init', function() { + describe('initialize without expected params', function() { + it('fails initalize when keyId is not present', function() { + expect(rtdProvider.init(configWithoutRequiredParams)).to.be.false; + }) + }) + + describe('initialize with expected params', function() { + it('successfully initialize with load script', function() { + expect(rtdProvider.init(configWithParams)).to.be.true; + clock.tick(1000); + expect(loadExternalScript.called).to.be.true; + }) + }); + }); +}) + +describe('markViewed tests', function() { + let sandbox; + const mockObserver = { + unobserve: sinon.spy() + }; + const makeElement = (id) => { + const el = document.createElement('div'); + el.setAttribute('id', id); + return el; + } + const mockEntry = { + target: makeElement('target_id') + }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + }) + + afterEach(function() { + sandbox.restore() + }) + + it('markViewed returns a function', function() { + expect(rtdProvider.markViewed(mockEntry, mockObserver)).to.be.a('function') + }); + + it('markViewed unobserves', function() { + const func = rtdProvider.markViewed(mockEntry, mockObserver); + func(); + expect(mockObserver.unobserve.calledOnce).to.be.true; + }); +}) From a94f7f02c874d6f0df6cd98d5282bb383ab93540 Mon Sep 17 00:00:00 2001 From: Jeremy Sadwith Date: Mon, 30 Oct 2023 12:19:03 -0400 Subject: [PATCH 141/760] Kargo Bid Adapter: Support for sending ortb2.user.data (#10659) * KargoBidAdapter: GPP Support * kargo adapter to forward schain object (#21) * wrap in if statement (#22) * KRKPD-572: Add spec for schain (#23) * wrap in if statement * update test for schain, file formatting * Adding site to Kargo adapter. * KRKPD-619 Updating Site object. * KRKPD-619 Adding null check for Site object. * Update modules/kargoBidAdapter.js Co-authored-by: Julian Gan * Reducing the size of Site object. * remove white space that is causing linting error * Kargo Bid Adapter: Updates to gpid retrieval * Support for sending ortb2.user.data --------- Co-authored-by: Julian Gan Co-authored-by: Neil Flynn --- modules/kargoBidAdapter.js | 6 +++++ test/spec/modules/kargoBidAdapter_spec.js | 30 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 02e64e42f00..9d8c7bc06a1 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -24,6 +24,7 @@ const CURRENCY = Object.freeze({ }); const REQUEST_KEYS = Object.freeze({ + USER_DATA: 'ortb2.user.data', SOCIAL_CANVAS: 'params.socialCanvas', SUA: 'ortb2.device.sua', TDID_ADAPTER: 'userId.tdid', @@ -103,15 +104,20 @@ function buildRequests(validBidRequests, bidderRequest) { } } + // Add schain if (firstBidRequest.schain && firstBidRequest.schain.nodes) { krakenParams.schain = firstBidRequest.schain } + // Add user data object if available + krakenParams.user.data = deepAccess(firstBidRequest, REQUEST_KEYS.USER_DATA) || []; + const reqCount = getRequestCount() if (reqCount != null) { krakenParams.requestCount = reqCount; } + // Add currency if not USD if (currency != null && currency != CURRENCY.US_DOLLAR) { krakenParams.cur = currency; } diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 7a9d89eb3a1..f43c3b11aac 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -147,6 +147,22 @@ describe('kargo adapter tests', function () { id: '1234', name: 'SiteName', cat: ['IAB1', 'IAB2', 'IAB3'] + }, + user: { + data: [ + { + name: 'prebid.org', + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + id: '133' + }, + ] + }, + ] } }, ortb2Imp: { @@ -522,6 +538,20 @@ describe('kargo adapter tests', function () { } ] } + ], + data: [ + { + name: 'prebid.org', + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + id: '133' + } + ] + } ] } }; From ae03319488d15bef4283e4dc59b1715c71bd2e39 Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:15:22 +0100 Subject: [PATCH 142/760] sspBC Bid Adapter: update video detection for VAST4 (#10660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * [sspbc-adapter] fix video ad detection * [sspbc-adapter] update adapter version to 5.92 --------- Co-authored-by: Wojciech Biały --- modules/sspBCBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 2b39faa02d8..93aa0973cc7 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.91'; +const BIDDER_VERSION = '5.92'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -506,7 +506,7 @@ const mapImpression = slot => { } const isVideoAd = bid => { - const xmlTester = new RegExp(/^<\?xml/); + const xmlTester = new RegExp(/^<\?xml| Date: Mon, 30 Oct 2023 12:05:28 -0700 Subject: [PATCH 143/760] fledgeForGpt: use `imp.ext.ae` instead of `defaultForSlots`; add `prebid.ortb2` and `prebid.ortb2Imp` auction signals (#10649) * set ext.ae directly instead of defaultForSlots * pass fpd for PBS fledge * set ortb2, ortb2Imp in fledge auctionSignals --- modules/fledgeForGpt.js | 32 ++-- modules/prebidServerBidAdapter/index.js | 4 +- .../prebidServerBidAdapter/ortbConverter.js | 11 +- src/adapters/bidderFactory.js | 4 +- test/spec/modules/fledgeForGpt_spec.js | 147 ++++++++---------- .../modules/prebidServerBidAdapter_spec.js | 75 +++++---- test/spec/unit/core/bidderFactory_spec.js | 4 +- 7 files changed, 149 insertions(+), 128 deletions(-) diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index fd29c41210c..566dd5f40e2 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -96,14 +96,20 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests}) { } } -export function addComponentAuctionHook(next, auctionId, adUnitCode, componentAuctionConfig) { +function setFPDSignals(auctionConfig, fpd) { + auctionConfig.auctionSignals = mergeDeep({}, {prebid: fpd}, auctionConfig.auctionSignals); +} + +export function addComponentAuctionHook(next, request, componentAuctionConfig) { + const {adUnitCode, auctionId, ortb2, ortb2Imp} = request; if (PENDING.hasOwnProperty(auctionId)) { + setFPDSignals(componentAuctionConfig, {ortb2, ortb2Imp}); !PENDING[auctionId].hasOwnProperty(adUnitCode) && (PENDING[auctionId][adUnitCode] = []); PENDING[auctionId][adUnitCode].push(componentAuctionConfig); } else { logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig) } - next(auctionId, adUnitCode, componentAuctionConfig); + next(request, componentAuctionConfig); } function isFledgeSupported() { @@ -114,25 +120,21 @@ export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { const globalFledgeConfig = config.getConfig('fledgeForGpt'); const bidders = globalFledgeConfig?.bidders ?? []; - bidderRequests.forEach((req) => { - const useGlobalConfig = globalFledgeConfig?.enabled && (bidders.length == 0 || bidders.includes(req.bidderCode)); - Object.assign(req, config.runWithBidder(req.bidderCode, () => { - return { - fledgeEnabled: config.getConfig('fledgeEnabled') ?? (useGlobalConfig ? globalFledgeConfig.enabled : undefined), - defaultForSlots: config.getConfig('defaultForSlots') ?? (useGlobalConfig ? globalFledgeConfig?.defaultForSlots : undefined) - } - })); + bidderRequests.forEach((bidderReq) => { + const useGlobalConfig = globalFledgeConfig?.enabled && (bidders.length === 0 || bidders.includes(bidderReq.bidderCode)); + config.runWithBidder(bidderReq.bidderCode, () => { + const fledgeEnabled = config.getConfig('fledgeEnabled') ?? (useGlobalConfig ? globalFledgeConfig.enabled : undefined); + const defaultForSlots = config.getConfig('defaultForSlots') ?? (useGlobalConfig ? globalFledgeConfig?.defaultForSlots : undefined); + Object.assign(bidderReq, {fledgeEnabled}); + bidderReq.bids.forEach(bidReq => { deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidReq.ortb2Imp?.ext?.ae ?? defaultForSlots) }) + }) }); } next(bidderRequests); } export function setImpExtAe(imp, bidRequest, context) { - if (context.bidderRequest.fledgeEnabled) { - imp.ext = Object.assign(imp.ext || {}, { - ae: imp.ext?.ae ?? context.bidderRequest.defaultForSlots - }) - } else { + if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { delete imp.ext?.ae; } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 0fff93cdcd1..a530c415839 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -503,8 +503,8 @@ export function PrebidServer() { } } }, - onFledge: ({adUnitCode, config}) => { - addComponentAuction(bidRequests[0].auctionId, adUnitCode, config); + onFledge: (params) => { + addComponentAuction({auctionId: bidRequests[0].auctionId, ...params}, params.config); } }) } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 54f71c7dc3e..7aeb4302280 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -240,7 +240,16 @@ const PBS_CONVERTER = ortbConverter({ }, fledgeAuctionConfigs(orig, response, ortbResponse, context) { const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({adUnitCode: impCtx.adUnit.code, config: cfg.config}))); + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => { + const bidderReq = impCtx.actualBidderRequests.find(br => br.bidderCode === cfg.bidder); + const bidReq = impCtx.actualBidRequests.get(cfg.bidder); + return { + adUnitCode: impCtx.adUnit.code, + ortb2: bidderReq?.ortb2, + ortb2Imp: bidReq?.ortb2Imp, + config: cfg.config + }; + })); if (configs.length > 0) { response.fledgeAuctionConfigs = configs; } diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index df97d820c96..8628f2c60a0 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -292,7 +292,7 @@ export function newBidder(spec) { fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest.auctionId, bidRequest.adUnitCode, fledgeAuctionConfig.config); + addComponentAuction(bidRequest, fledgeAuctionConfig.config); } else { logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); } @@ -525,7 +525,7 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') -export const addComponentAuction = hook('sync', (adUnitCode, fledgeAuctionConfig) => { +export const addComponentAuction = hook('sync', (request, fledgeAuctionConfig) => { }, 'addComponentAuction'); // check that the bid has a width and height set diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js index b4bff8e82f0..60a8e196ae0 100644 --- a/test/spec/modules/fledgeForGpt_spec.js +++ b/test/spec/modules/fledgeForGpt_spec.js @@ -45,16 +45,17 @@ describe('fledgeForGpt module', () => { }); it('should call next()', function () { - fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'auc', fledgeAuctionConfig); - sinon.assert.calledWith(nextFnSpy, 'aid', 'auc', fledgeAuctionConfig); + const request = {auctionId: 'aid', adUnitCode: 'auc'}; + fledge.addComponentAuctionHook(nextFnSpy, request, fledgeAuctionConfig); + sinon.assert.calledWith(nextFnSpy, request, fledgeAuctionConfig); }); it('should collect auction configs and route them to GPT at end of auction', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); const cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; const cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; - fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au1', cf1); - fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au2', cf2); + fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au1'}, cf1); + fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au2'}, cf2); events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au1'); sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au2'); @@ -75,11 +76,31 @@ describe('fledgeForGpt module', () => { it('should drop auction configs after end of auction', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); - fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au', fledgeAuctionConfig); + fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au'}, fledgeAuctionConfig); events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); sinon.assert.notCalled(mockGptSlot.setConfig); }); + it('should augment auctionSignals with FPD', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); + fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au1', ortb2: {fpd: 1}, ortb2Imp: {fpd: 2}}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + sinon.assert.calledWith(mockGptSlot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: { + ...fledgeAuctionConfig, + auctionSignals: { + prebid: { + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + } + } + }, + }] + }) + }) + describe('floor signal', () => { before(() => { if (!getGlobal().convertCurrency) { @@ -173,7 +194,7 @@ describe('fledgeForGpt module', () => { it('should populate bidfloor/bidfloorcur', () => { events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); - fledge.addComponentAuctionHook(nextFnSpy, 'aid', 'au', fledgeAuctionConfig); + fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au'}, fledgeAuctionConfig); events.emit(CONSTANTS.EVENTS.AUCTION_END, payload); sinon.assert.calledWith(mockGptSlot.setConfig, sinon.match(arg => { return arg.componentAuction.some(au => au.auctionConfig.auctionSignals?.prebid?.bidfloor === bidfloor && au.auctionConfig.auctionSignals?.prebid?.bidfloorcur === bidfloorcur) @@ -223,6 +244,24 @@ describe('fledgeForGpt module', () => { }, ] }]; + function expectFledgeFlags(...enableFlags) { + const bidRequests = adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + + expect(bidRequests[0].bids[0].bidder).equals('appnexus'); + expect(bidRequests[0].fledgeEnabled).to.eql(enableFlags[0].enabled) + bidRequests[0].bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)) + + expect(bidRequests[1].bids[0].bidder).equals('rubicon'); + expect(bidRequests[1].fledgeEnabled).to.eql(enableFlags[1].enabled) + bidRequests[1].bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + } describe('with setBidderConfig()', () => { it('should set fledgeEnabled correctly per bidder', function () { @@ -234,23 +273,7 @@ describe('fledgeForGpt module', () => { defaultForSlots: 1, } }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - expect(bidRequests[1].defaultForSlots).to.be.undefined; + expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); }); }); @@ -264,23 +287,7 @@ describe('fledgeForGpt module', () => { defaultForSlots: 1, } }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.be.undefined; - expect(bidRequests[1].defaultForSlots).to.be.undefined; + expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); }); it('should set fledgeEnabled correctly for all bidders', function () { @@ -291,51 +298,33 @@ describe('fledgeForGpt module', () => { defaultForSlots: 1, } }); - - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[0].fledgeEnabled).to.be.true; - expect(bidRequests[0].defaultForSlots).to.equal(1); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); + + it('should not override pub-defined ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + fledgeForGpt: { + enabled: true, + defaultForSlots: 1, + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); + expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + }) }); }); describe('ortb processors for fledge', () => { - describe('when defaultForSlots is set', () => { - it('imp.ext.ae should be set if fledge is enabled', () => { - const imp = {}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); - expect(imp.ext.ae).to.equal(1); - }); - it('imp.ext.ae should be left intact if set on adunit and fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true, defaultForSlots: 1}}); - expect(imp.ext.ae).to.equal(2); - }); + it('imp.ext.ae should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; }); - describe('when defaultForSlots is not defined', () => { - it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; - setImpExtAe(imp, {}, {bidderRequest: {}}); - expect(imp.ext.ae).to.not.exist; - }); - it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); - }); + it('imp.ext.ae should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: 2}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(2); }); describe('parseExtPrebidFledge', () => { function packageConfigs(configs) { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index ecace22f97f..b5d65c50853 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3425,29 +3425,6 @@ describe('S2S Adapter', function () { }); }); describe('when the response contains ext.prebid.fledge', () => { - let fledgeStub, request, bidderRequests; - - function fledgeHook(next, ...args) { - fledgeStub(...args); - } - - before(() => { - addComponentAuction.before(fledgeHook); - }); - - after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); - }) - - beforeEach(function () { - fledgeStub = sinon.stub(); - config.setConfig({CONFIG}); - request = deepClone(REQUEST); - request.ad_units.forEach(au => deepSetValue(au, 'ortb2Imp.ext.ae', 1)); - bidderRequests = deepClone(BID_REQUESTS); - bidderRequests.forEach(req => req.fledgeEnabled = true); - }); - const AU = 'div-gpt-ad-1460505748561-0'; const FLEDGE_RESP = { ext: { @@ -3456,12 +3433,14 @@ describe('S2S Adapter', function () { auctionconfigs: [ { impid: AU, + bidder: 'appnexus', config: { id: 1 } }, { impid: AU, + bidder: 'other', config: { id: 2 } @@ -3472,20 +3451,62 @@ describe('S2S Adapter', function () { } } + let fledgeStub, request, bidderRequests; + + function fledgeHook(next, ...args) { + fledgeStub(...args); + } + + before(() => { + addComponentAuction.before(fledgeHook); + }); + + after(() => { + addComponentAuction.getHooks({hook: fledgeHook}).remove(); + }) + + beforeEach(function () { + fledgeStub = sinon.stub(); + config.setConfig({CONFIG}); + bidderRequests = deepClone(BID_REQUESTS); + AU + bidderRequests.forEach(req => { + Object.assign(req, { + fledgeEnabled: true, + ortb2: { + fpd: 1 + } + }) + req.bids.forEach(bid => { + Object.assign(bid, { + ortb2Imp: { + fpd: 2 + } + }) + }) + }); + request = deepClone(REQUEST); + request.ad_units.forEach(au => deepSetValue(au, 'ortb2Imp.ext.ae', 1)); + }); + + function expectFledgeCalls() { + const auctionId = bidderRequests[0].auctionId; + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {id: 1}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {id: 2}) + } + it('calls addComponentAuction alongside addBidResponse', function () { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); expect(addBidResponse.called).to.be.true; - sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 1}); - sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 2}); + expectFledgeCalls(); }); it('calls addComponentAuction when there is no bid in the response', () => { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); expect(addBidResponse.called).to.be.false; - sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 1}); - sinon.assert.calledWith(fledgeStub, bidderRequests[0].auctionId, AU, {id: 2}); + expectFledgeCalls(); }) }); }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 4c13d830206..f089059b65a 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1448,7 +1448,7 @@ describe('bidderFactory', () => { bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(fledgeStub.calledOnce).to.equal(true); - sinon.assert.calledWith(fledgeStub, bidRequest.auctionId, 'mock/placement', fledgeAuctionConfig.config); + sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); }) @@ -1462,7 +1462,7 @@ describe('bidderFactory', () => { bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(fledgeStub.calledOnce).to.be.true; - sinon.assert.calledWith(fledgeStub, bidRequest.auctionId, 'mock/placement', fledgeAuctionConfig.config); + sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); expect(addBidResponseStub.calledOnce).to.equal(false); }) }) From 5790fd6bc8df3133f27a910de64c51f6fe793516 Mon Sep 17 00:00:00 2001 From: Arne Schulz Date: Mon, 30 Oct 2023 22:19:57 +0100 Subject: [PATCH 144/760] send all bidRequests fields to orbidder and change tests to be more (#10662) generic --- modules/orbidderBidAdapter.js | 10 +- test/spec/modules/orbidderBidAdapter_spec.js | 102 ++++++++++++------- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 53fff39047f..efc2effdd62 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -99,15 +99,7 @@ export const spec = { data: { v: getGlobal().version, pageUrl: referer, - bidId: bidRequest.bidId, - auctionId: bidRequest.auctionId, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - transactionId: bidRequest.ortb2Imp?.ext?.tid, - adUnitCode: bidRequest.adUnitCode, - bidRequestCount: bidRequest.bidRequestCount, - params: bidRequest.params, - sizes: bidRequest.sizes, - mediaTypes: bidRequest.mediaTypes + ...bidRequest // get all data provided by bid request } }; diff --git a/test/spec/modules/orbidderBidAdapter_spec.js b/test/spec/modules/orbidderBidAdapter_spec.js index 5af5a4d710f..cf58d35e636 100644 --- a/test/spec/modules/orbidderBidAdapter_spec.js +++ b/test/spec/modules/orbidderBidAdapter_spec.js @@ -9,39 +9,57 @@ describe('orbidderBidAdapter', () => { const defaultBidRequestBanner = { bidId: 'd66fa86787e0b0ca900a96eacfd5f0bb', auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d8', - ortb2Imp: { - ext: { - tid: 'd58851660c0c4461e4aa06344fc9c0c6', - } - }, + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', bidRequestCount: 1, adUnitCode: 'adunit-code', sizes: [[300, 250], [300, 600]], params: { 'accountId': 'string1', - 'placementId': 'string2' + 'placementId': 'string2', + 'bidfloor': 1.23 }, mediaTypes: { banner: { - sizes: [[300, 250], [300, 600]], + sizes: [[300, 250], [300, 600]] } - } + }, + userId: { + 'id5id': { + 'uid': 'ID5*XXXXXXXXXXXXX', + 'ext': { + 'linkType': 2, + 'pba': 'XXXXXXXXXXXX==' + } + } + }, + userIdAsEids: [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5*XXXXXXXXXXXXX', + 'atype': 1, + 'ext': { + 'linkType': 2, + 'pba': 'XXXXXXXXXXXX==' + } + } + ] + } + ] }; const defaultBidRequestNative = { bidId: 'd66fa86787e0b0ca900a96eacfd5f0bc', auctionId: 'ccc4c7cdfe11cfbd74065e6dd28413d9', - ortb2Imp: { - ext: { - tid: 'd58851660c0c4461e4aa06344fc9c0c7', - } - }, + transactionId: 'd58851660c0c4461e4aa06344fc9c0c6', bidRequestCount: 1, adUnitCode: 'adunit-code-native', sizes: [], params: { 'accountId': 'string3', - 'placementId': 'string4' + 'placementId': 'string4', + 'bidfloor': 2.34 }, mediaTypes: { native: { @@ -56,7 +74,31 @@ describe('orbidderBidAdapter', () => { required: true } } - } + }, + userId: { + 'id5id': { + 'uid': 'ID5*YYYYYYYYYYYYYYY', + 'ext': { + 'linkType': 2, + 'pba': 'YYYYYYYYYYYYY==' + } + } + }, + userIdAsEids: [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': 'ID5*YYYYYYYYYYYYYYY', + 'atype': 1, + 'ext': { + 'linkType': 2, + 'pba': 'YYYYYYYYYYYYY==' + } + } + ] + } + ] }; const deepClone = function(val) { @@ -179,34 +221,20 @@ describe('orbidderBidAdapter', () => { // we add two, because we add pageUrl and version from bidderRequest object expect(Object.keys(request.data).length).to.equal(Object.keys(defaultBidRequestBanner).length + 2); - expect(request.data.bidId).to.equal(defaultBidRequestBanner.bidId); - expect(request.data.auctionId).to.equal(defaultBidRequestBanner.auctionId); - expect(request.data.transactionId).to.equal(defaultBidRequestBanner.ortb2Imp.ext.tid); - expect(request.data.bidRequestCount).to.equal(defaultBidRequestBanner.bidRequestCount); - expect(request.data.adUnitCode).to.equal(defaultBidRequestBanner.adUnitCode); - expect(request.data.pageUrl).to.equal('https://localhost:9876/'); - expect(request.data.v).to.equal($$PREBID_GLOBAL$$.version); - expect(request.data.sizes).to.equal(defaultBidRequestBanner.sizes); - - expect(_.isEqual(request.data.params, defaultBidRequestBanner.params)).to.be.true; - expect(_.isEqual(request.data.mediaTypes, defaultBidRequestBanner.mediaTypes)).to.be.true; + const expectedBidRequest = deepClone(defaultBidRequestBanner); + expectedBidRequest.pageUrl = 'https://localhost:9876/'; + expectedBidRequest.v = $$PREBID_GLOBAL$$.version; + expect(request.data).to.deep.equal(expectedBidRequest); }); it('native: sends correct bid parameters', () => { // we add two, because we add pageUrl and version from bidderRequest object expect(Object.keys(nativeRequest.data).length).to.equal(Object.keys(defaultBidRequestNative).length + 2); - expect(nativeRequest.data.bidId).to.equal(defaultBidRequestNative.bidId); - expect(nativeRequest.data.auctionId).to.equal(defaultBidRequestNative.auctionId); - expect(nativeRequest.data.transactionId).to.equal(defaultBidRequestNative.ortb2Imp.ext.tid); - expect(nativeRequest.data.bidRequestCount).to.equal(defaultBidRequestNative.bidRequestCount); - expect(nativeRequest.data.adUnitCode).to.equal(defaultBidRequestNative.adUnitCode); - expect(nativeRequest.data.pageUrl).to.equal('https://localhost:9876/'); - expect(nativeRequest.data.v).to.equal($$PREBID_GLOBAL$$.version); - expect(nativeRequest.data.sizes).to.be.empty; - - expect(_.isEqual(nativeRequest.data.params, defaultBidRequestNative.params)).to.be.true; - expect(_.isEqual(nativeRequest.data.mediaTypes, defaultBidRequestNative.mediaTypes)).to.be.true; + const expectedBidRequest = deepClone(defaultBidRequestNative); + expectedBidRequest.pageUrl = 'https://localhost:9876/'; + expectedBidRequest.v = $$PREBID_GLOBAL$$.version; + expect(nativeRequest.data).to.deep.equal(expectedBidRequest); }); it('banner: handles empty gdpr object', () => { From 29772f9c5fcc9f750e44fd05102dd12a9b691739 Mon Sep 17 00:00:00 2001 From: aus_gomez Date: Tue, 31 Oct 2023 13:03:43 -0500 Subject: [PATCH 145/760] Insticator Bid Adapter : get config fix for device (#10667) * getConfig cleanup of device * Revert "getConfig cleanup of device" This reverts commit 8cda77848adee6110e831a3064c3fa5f12367a87. * getConfig-fix-for-device --- modules/insticatorBidAdapter.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index c770ac69dbe..a18c893b5fc 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -106,8 +106,10 @@ function buildImpression(bidRequest) { return imp; } -function buildDevice() { - const deviceConfig = config.getConfig('device'); +function buildDevice(bidRequest) { + const ortb2Data = bidRequest?.ortb2 || {}; + const deviceConfig = ortb2Data?.device || {} + const device = { w: window.innerWidth, h: window.innerHeight, @@ -184,7 +186,7 @@ function buildRequest(validBidRequests, bidderRequest) { page: bidderRequest.refererInfo.page, ref: bidderRequest.refererInfo.ref, }, - device: buildDevice(), + device: buildDevice(bidderRequest), regs: buildRegs(bidderRequest), user: buildUser(validBidRequests[0]), imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), From 9630925de7dac26ce22e544345e55efce591e9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Wed, 1 Nov 2023 01:28:52 +0100 Subject: [PATCH 146/760] Agma Analytics Adapter: Init (#10642) --- modules/agmaAnalyticsAdapter.js | 225 ++++++++++ modules/agmaAnalyticsAdapter.md | 28 ++ .../spec/modules/agmaAnalyticsAdapter_spec.js | 388 ++++++++++++++++++ 3 files changed, 641 insertions(+) create mode 100644 modules/agmaAnalyticsAdapter.js create mode 100644 modules/agmaAnalyticsAdapter.md create mode 100644 test/spec/modules/agmaAnalyticsAdapter_spec.js diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js new file mode 100644 index 00000000000..afbc3e771ec --- /dev/null +++ b/modules/agmaAnalyticsAdapter.js @@ -0,0 +1,225 @@ +import { ajax } from '../src/ajax.js'; +import { + generateUUID, + logInfo, + logError, + getPerformanceNow, + isEmpty, + isEmptyStr, +} from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import CONSTANTS from '../src/constants.json'; +import adapterManager, { gdprDataHandler } from '../src/adapterManager.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { config } from '../src/config.js'; + +const GVLID = 1122; +const ModuleCode = 'agma'; +const analyticsType = 'endpoint'; +const scriptVersion = '1.7.0'; +const batchDelayInMs = 1000; +const agmaURL = 'https://pbc.agma-analytics.de/v1'; +const pageViewId = generateUUID(); + +const { + EVENTS: { AUCTION_INIT }, +} = CONSTANTS; + +// Helper functions +const getScreen = () => { + const w = window; + const d = document; + const e = d.documentElement; + const g = d.getElementsByTagName('body')[0]; + const x = w.innerWidth || e.clientWidth || g.clientWidth; + const y = w.innerHeight || e.clientHeight || g.clientHeight; + return { x, y }; +}; + +const getUserIDs = () => { + try { + return getGlobal().getUserIdsAsEids(); + } catch (e) {} + return []; +}; + +export const getOrtb2Data = (options) => { + let site = null; + let user = null; + + // check if data is provided via config + if (options.ortb2) { + if (options.ortb2.user) { + user = options.ortb2.user; + } + if (options.ortb2.site) { + site = options.ortb2.site; + } + if (site && user) { + return { site, user }; + } + } + try { + const configData = config.getConfig('agma'); + // try to fallback to global config + if (configData.ortb2) { + site = site || configData.ortb2.site; + user = user || configData.ortb2.user; + } + } catch (e) {} + + return { site, user }; +}; + +export const getTiming = () => { + // Timing API V2 + let ttfb = 0; + try { + const entry = performance.getEntriesByType('navigation')[0]; + ttfb = Math.round(entry.responseStart - entry.startTime); + } catch (e) { + // Timing API V1 + try { + const entry = performance.timing; + ttfb = Math.round(entry.responseStart - entry.fetchStart); + } catch (e) { + // Timing API not available + return null; + } + } + const elapsedTime = getPerformanceNow(); + ttfb = ttfb >= 0 && ttfb <= elapsedTime ? ttfb : 0; + return { + ttfb, + elapsedTime, + }; +}; + +export const getPayload = (auctionIds, options) => { + if (!options || !auctionIds || auctionIds.length === 0) { + return false; + } + const consentData = gdprDataHandler.getConsentData(); + let gdprApplies = true; // we assume gdpr applies + let useExtendedPayload = false; + if (consentData) { + gdprApplies = consentData.gdprApplies; + const consents = consentData.vendorData?.vendor?.consents || {}; + useExtendedPayload = consents[GVLID]; + } + const ortb2 = getOrtb2Data(options); + const ri = getRefererInfo() || {}; + + let payload = { + auctionIds: auctionIds, + triggerEvent: options.triggerEvent, + pageViewId, + domain: ri.domain, + gdprApplies, + code: options.code, + ortb2: { site: ortb2.site }, + pageUrl: ri.page, + prebidVersion: '$prebid.version$', + scriptVersion, + debug: options.debug, + timing: getTiming(), + }; + + if (useExtendedPayload) { + const { x, y } = getScreen(); + const userIdsAsEids = getUserIDs(); + payload = { + ...payload, + ortb2, + extended: true, + timestamp: Date.now(), + gdprConsentString: consentData.consentString, + timezoneOffset: new Date().getTimezoneOffset(), + language: window.navigator.language, + referrer: ri.topmostLocation, + pageUrl: ri.page, + screenWidth: x, + screenHeight: y, + userIdsAsEids, + }; + } + return payload; +}; + +const agmaAnalytics = Object.assign(adapter({ analyticsType }), { + auctionIds: [], + timer: null, + track(data) { + const { eventType, args } = data; + if (eventType === this.options.triggerEvent && args && args.auctionId) { + this.auctionIds.push(args.auctionId); + if (this.timer === null) { + this.timer = setTimeout(() => { + this.processBatch(); + }, batchDelayInMs); + } + } + }, + processBatch() { + const currentBatch = [...this.auctionIds]; + const payload = getPayload(currentBatch, this.options); + this.auctionIds = []; + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + this.send(payload); + }, + send(payload) { + if (!payload) { + return; + } + return ajax( + agmaURL, + () => { + logInfo(ModuleCode, 'flushed', payload); + }, + JSON.stringify(payload), + { + contentType: 'text/plain', + method: 'POST', + } + ); + }, +}); + +agmaAnalytics.originEnableAnalytics = agmaAnalytics.enableAnalytics; +agmaAnalytics.enableAnalytics = function (config = {}) { + const { options } = config; + + if (isEmpty(options)) { + logError(ModuleCode, 'Please set options'); + return false; + } + + if (options.site && !options.code) { + logError(ModuleCode, 'Please set `code` - `site` is deprecated'); + options.code = options.site; + } + + if (!options.code || isEmptyStr(options.code)) { + logError(ModuleCode, 'Please set `code` option - agma Analytics is disabled'); + return false; + } + + agmaAnalytics.options = { + triggerEvent: AUCTION_INIT, + ...options, + }; + + agmaAnalytics.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: agmaAnalytics, + code: ModuleCode, + gvlid: GVLID, +}); + +export default agmaAnalytics; diff --git a/modules/agmaAnalyticsAdapter.md b/modules/agmaAnalyticsAdapter.md new file mode 100644 index 00000000000..30c88fb92ec --- /dev/null +++ b/modules/agmaAnalyticsAdapter.md @@ -0,0 +1,28 @@ +# Overview + Module Name: Agma Analytics + Module Type: Analytics Adapter + Maintainer: [www.agma-mmc.de](https://www.agma-mmc.de) + Technical Support: [info@mllrsohn.com](mailto:info@mllrsohn.com) + +# Description + +Agma Analytics adapter. Please contact [team-internet@agma-mmc.de](mailto:team-internet@agma-mmc.de) for signup and access to [futher documentation](https://docs.agma-analytics.de). + +# Usage + +Add the `agmaAnalyticsAdapter` to your build: + +``` +gulp build --modules=...,agmaAnalyticsAdapter... +``` + +Configure the analytics module: + +```javascript +pbjs.enableAnalytics({ + provider: 'agma', + options: { + code: 'provided-by-agma' // change to the code you received from agma + } +}); +``` diff --git a/test/spec/modules/agmaAnalyticsAdapter_spec.js b/test/spec/modules/agmaAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..ba71624e3b3 --- /dev/null +++ b/test/spec/modules/agmaAnalyticsAdapter_spec.js @@ -0,0 +1,388 @@ +import adapterManager from '../../../src/adapterManager.js'; +import agmaAnalyticsAdapter, { + getTiming, + getOrtb2Data, + getPayload, +} from '../../../modules/agmaAnalyticsAdapter.js'; +import { gdprDataHandler } from '../../../src/adapterManager.js'; +import { expect } from 'chai'; +import * as events from '../../../src/events.js'; +import constants from '../../../src/constants.json'; +import { generateUUID } from '../../../src/utils.js'; +import { server } from '../../mocks/xhr.js'; +import { config } from 'src/config.js'; + +const INGEST_URL = 'https://pbc.agma-analytics.de/v1'; +const extendedKey = [ + 'auctionIds', + 'code', + 'domain', + 'extended', + 'gdprApplies', + 'gdprConsentString', + 'language', + 'ortb2', + 'pageUrl', + 'pageViewId', + 'prebidVersion', + 'referrer', + 'screenHeight', + 'screenWidth', + 'scriptVersion', + 'timestamp', + 'timezoneOffset', + 'timing', + 'triggerEvent', + 'userIdsAsEids', +]; +const nonExtendedKey = [ + 'auctionIds', + 'code', + 'domain', + 'gdprApplies', + 'ortb2', + 'pageUrl', + 'pageViewId', + 'prebidVersion', + 'scriptVersion', + 'timing', + 'triggerEvent', +]; + +describe('AGMA Analytics Adapter', () => { + let agmaConfig, sandbox, clock; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + sandbox.stub(events, 'getEvents').returns([]); + agmaConfig = { + options: { + code: 'test', + }, + }; + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('configuration', () => { + it('registers itself with the adapter manager', () => { + const adapter = adapterManager.getAnalyticsAdapter('agma'); + expect(adapter).to.exist; + expect(adapter.gvlid).to.equal(1122); + }); + }); + + describe('getPayload', () => { + it('should use non extended payload with no consent info', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => null) + const payload = getPayload([generateUUID()], { + code: 'test', + }); + + expect(payload).to.have.all.keys([...nonExtendedKey, 'debug']); + }); + + it('should use non extended payload when agma is not in the TC String', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => ({ + vendorData: { + vendor: { + consents: { + 1122: false, + }, + }, + }, + })); + const payload = getPayload([generateUUID()], { + code: 'test', + }); + expect(payload).to.have.all.keys([...nonExtendedKey, 'debug']); + }); + + it('should use extended payload when agma is in the TC String', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => ({ + vendorData: { + vendor: { + consents: { + 1122: true, + }, + }, + }, + })); + const payload = getPayload([generateUUID()], { + code: 'test', + }); + expect(payload).to.have.all.keys([...extendedKey, 'debug']); + }); + }); + + describe('getTiming', () => { + let originalPerformance; + let originalWindowPerformanceNow; + + beforeEach(() => { + originalPerformance = global.performance; + originalWindowPerformanceNow = window.performance.now; + }); + + afterEach(() => { + global.performance = originalPerformance; + window.performance.now = originalWindowPerformanceNow; + }); + + it('returns TTFB using Timing API V2', () => { + global.performance = { + getEntriesByType: sinon + .stub() + .returns([{ responseStart: 100, startTime: 50 }]), + now: sinon.stub().returns(150), + }; + + const result = getTiming(); + + expect(result).to.deep.equal({ ttfb: 50, elapsedTime: 150 }); + }); + + it('returns TTFB using Timing API V1 when V2 is not available', () => { + global.performance = { + getEntriesByType: sinon.stub().throws(), + timing: { responseStart: 150, fetchStart: 50 }, + now: sinon.stub().returns(200), + }; + + const result = getTiming(); + + expect(result).to.deep.equal({ ttfb: 100, elapsedTime: 200 }); + }); + + it('returns null when Timing API is not available', () => { + global.performance = { + getEntriesByType: sinon.stub().throws(), + timing: undefined, + }; + + const result = getTiming(); + + expect(result).to.be.null; + }); + + it('returns ttfb as 0 if calculated value is negative', () => { + global.performance = { + getEntriesByType: sinon + .stub() + .returns([{ responseStart: 50, startTime: 150 }]), + now: sinon.stub().returns(200), + }; + + const result = getTiming(); + + expect(result).to.deep.equal({ ttfb: 0, elapsedTime: 200 }); + }); + + it('returns ttfb as 0 if calculated value exceeds performance.now()', () => { + global.performance = { + getEntriesByType: sinon + .stub() + .returns([{ responseStart: 50, startTime: 0 }]), + now: sinon.stub().returns(40), + }; + + const result = getTiming(); + + expect(result).to.deep.equal({ ttfb: 0, elapsedTime: 40 }); + }); + }); + + describe('getOrtb2Data', () => { + it('returns site and user from options when available', () => { + sandbox.stub(config, 'getConfig').callsFake((key) => { + return {}; + }); + + const ortb2 = { + user: 'user', + site: 'site', + }; + + const result = getOrtb2Data({ + ortb2, + }); + + expect(result).to.deep.equal(ortb2); + }); + + it('returns a combination of data from options and pGlobal.readConfig', () => { + sandbox.stub(config, 'getConfig').callsFake((key) => { + return { + ortb2: { + site: { + foo: 'bar', + }, + }, + }; + }); + + const ortb2 = { + user: 'user', + }; + const result = getOrtb2Data({ + ortb2, + }); + + expect(result).to.deep.equal({ + site: { + foo: 'bar', + }, + user: 'user', + }); + }); + }); + + describe('Event Payload', () => { + beforeEach(() => { + agmaAnalyticsAdapter.enableAnalytics({ + ...agmaConfig, + }); + server.respondWith('POST', INGEST_URL, [ + 200, + { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + '', + ]); + }); + + afterEach(() => { + agmaAnalyticsAdapter.auctionIds = []; + if (agmaAnalyticsAdapter.timer) { + clearTimeout(agmaAnalyticsAdapter.timer); + } + agmaAnalyticsAdapter.disableAnalytics(); + }); + + it('should only send once per minute', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => ({ + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendor: { + consents: { + 1122: true, + }, + }, + }, + })); + const auction = { + auctionId: generateUUID(), + }; + + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: generateUUID('1'), + auction, + }); + + clock.tick(200); + + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: generateUUID('2'), + auction, + }); + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: generateUUID('3'), + auction, + }); + events.emit(constants.EVENTS.AUCTION_INIT, { + auctionId: generateUUID('4'), + auction, + }); + + clock.tick(900); + + const [request] = server.requests; + const requestBody = JSON.parse(request.requestBody); + expect(request.url).to.equal(INGEST_URL); + expect(requestBody).to.have.all.keys(extendedKey); + expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(server.requests).to.have.length(1); + }); + + it('should send the extended payload with consent', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => ({ + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendor: { + consents: { + 1122: true, + }, + }, + }, + })); + const auction = { + auctionId: generateUUID(), + }; + + events.emit(constants.EVENTS.AUCTION_INIT, auction); + clock.tick(1100); + + const [request] = server.requests; + const requestBody = JSON.parse(request.requestBody); + expect(request.url).to.equal(INGEST_URL); + expect(requestBody).to.have.all.keys(extendedKey); + expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(server.requests).to.have.length(1); + expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); + }); + + it('should send the non extended payload with no explicit consent', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => ({ + gdprApplies: true, + consentString: 'consentDataString', + })); + + const auction = { + auctionId: generateUUID(), + }; + + events.emit(constants.EVENTS.AUCTION_INIT, auction); + clock.tick(1000); + + const [request] = server.requests; + const requestBody = JSON.parse(request.requestBody); + expect(request.url).to.equal(INGEST_URL); + expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(server.requests).to.have.length(1); + expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); + }); + + it('should set the trigger Event', () => { + sandbox.stub(gdprDataHandler, 'getConsentData').callsFake(() => null); + agmaAnalyticsAdapter.disableAnalytics(); + agmaAnalyticsAdapter.enableAnalytics({ + provider: 'agma', + options: { + code: 'test', + triggerEvent: constants.EVENTS.AUCTION_END + }, + }); + const auction = { + auctionId: generateUUID(), + }; + + events.emit(constants.EVENTS.AUCTION_INIT, auction); + events.emit(constants.EVENTS.AUCTION_END, auction); + clock.tick(1000); + + const [request] = server.requests; + const requestBody = JSON.parse(request.requestBody); + expect(request.url).to.equal(INGEST_URL); + expect(requestBody.auctionIds).to.have.length(1); + expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_END); + expect(server.requests).to.have.length(1); + expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); + }); + }); +}); From 981459b4d8211104ac94a8004780c5acbf946ebb Mon Sep 17 00:00:00 2001 From: Rupesh Lakhani <35333377+AskRupert-DM@users.noreply.github.com> Date: Wed, 1 Nov 2023 13:06:15 +0000 Subject: [PATCH 147/760] ADAPTER UPDATES (#10602) ADAPTER UPDATES FOR ALIAS Co-authored-by: newspassid-prebid <107485317+newspassid-prebid@users.noreply.github.com> --- modules/ozoneBidAdapter.js | 8 ++++++-- test/spec/modules/ozoneBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 970c7d49fb9..0d921f57cda 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -22,10 +22,10 @@ const AUCTIONURI = '/openrtb2/auction'; const OZONECOOKIESYNC = '/static/load-cookie.html'; const OZONE_RENDERER_URL = 'https://prebid.the-ozone-project.com/ozone-renderer.js'; const ORIGIN_DEV = 'https://test.ozpr.net'; -const OZONEVERSION = '2.9.0'; +const OZONEVERSION = '2.9.1'; export const spec = { gvlid: 524, - aliases: [{code: 'lmc', gvlid: 524}], + aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], version: OZONEVERSION, code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], @@ -78,6 +78,9 @@ export const spec = { if (bidderConfig.hasOwnProperty('batchRequests')) { this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; } + if (arr.hasOwnProperty('batchRequests')) { + this.propertyBag.whitelabel.batchRequests = true; + } try { if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { logInfo('GET: auction=dev'); @@ -100,6 +103,7 @@ export const spec = { return this.propertyBag.whitelabel.rendererUrl; }, isBatchRequests() { + logInfo('isBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); return this.propertyBag.whitelabel.batchRequests; }, isBidRequestValid(bid) { diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 64b345c5d9c..73df2fba8fd 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -2069,6 +2069,19 @@ describe('ozone Adapter', function () { expect(request.length).to.equal(3); config.resetConfig(); }); + it('should not batch into 10s if config is set to false and singleRequest is true', function () { + config.setConfig({ozone: {'batchRequests': false, 'singleRequest': true}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 15; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); + config.resetConfig(); + }); it('should use GET values auction=dev & cookiesync=dev if set', function() { var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { From 7c25daab00edc963ef0dadd3a827433f229997af Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:21:18 -0500 Subject: [PATCH 148/760] FreeWheel SSP Adapter: add gvlid in spec (#10654) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * FreeWheel-SSP-Adapter add userIdAsEids support * Freewheel-SSP-Adapter add test for eids * Freewheel SSP Adapter: add prebid version in request * code cleanup * FreeWheel SSP Bid Adapter: support video context and placement * update test * FreeWheel SSP Bid Adapter: add GPP support * Freewheel SSP Bid Adapter: test update * FreeWheel SSP Adapter: update the default value for video placement and context * update test * FreeWheel SSP Adapter: add support for video.plcmt * FreeWheel Adapter: support content object in config * code update * FreeWheel SSP Adapter: add gvlid in spec * FreeWheel SSP Adapter: update code for site.content * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update --- modules/freewheel-sspBidAdapter.js | 6 +++-- .../modules/freewheel-sspBidAdapter_spec.js | 23 ++++++++----------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 1d733052f93..5e6cee71630 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -4,6 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; const BIDDER_CODE = 'freewheel-ssp'; +const GVL_ID = 285; const PROTOCOL = getProtocol(); const FREEWHEEL_ADSSETUP = PROTOCOL + '://ads.stickyadstv.com/www/delivery/swfIndex.php'; @@ -314,6 +315,7 @@ var getOutstreamScript = function(bid) { export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [BANNER, VIDEO], aliases: ['stickyadstv', 'freewheelssp'], // aliases for freewheel-ssp /** @@ -383,9 +385,9 @@ export const spec = { } // Add content object - if (typeof config.getConfig('content') === 'object') { + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site && bidderRequest.ortb2.site.content && typeof bidderRequest.ortb2.site.content === 'object') { try { - requestParams._fw_prebid_content = JSON.stringify(config.getConfig('content')); + requestParams._fw_prebid_content = JSON.stringify(bidderRequest.ortb2.site.content); } catch (error) { logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the content object: ' + error); } diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 6351d8956f9..90ebe0b80ee 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -118,20 +118,6 @@ describe('freewheelSSP BidAdapter Test', () => { } ]; - it('should get correct value from content object', () => { - config.setConfig({ - content: { - 'title': 'freewheel', - 'series': 'abc', - 'id': 'iris_5e7' - } - }); - - const request = spec.buildRequests(bidRequests); - const payload = request[0].data; - expect(payload._fw_prebid_content).to.deep.equal('{\"title\":\"freewheel\",\"series\":\"abc\",\"id\":\"iris_5e7\"}'); - }); - it('should get bidfloor value from params if no getFloor method', () => { const request = spec.buildRequests(bidRequests); const payload = request[0].data; @@ -218,6 +204,14 @@ describe('freewheelSSP BidAdapter Test', () => { let bidderRequest = { 'gdprConsent': { 'consentString': gdprConsentString + }, + 'ortb2': { + 'site': { + 'content': { + 'test': 'news', + 'test2': 'param' + } + } } }; @@ -231,6 +225,7 @@ describe('freewheelSSP BidAdapter Test', () => { expect(payload.playerSize).to.equal('300x600'); expect(payload._fw_gdpr_consent).to.exist.and.to.be.a('string'); expect(payload._fw_gdpr_consent).to.equal(gdprConsentString); + expect(payload._fw_prebid_content).to.deep.equal('{\"test\":\"news\",\"test2\":\"param\"}'); let gdprConsent = { 'gdprApplies': true, From 335697b9d097e860babe5df3e58e32d3dde91fca Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Wed, 1 Nov 2023 17:40:36 -0700 Subject: [PATCH 149/760] openxBidAdapter: add prebid version (#10678) --- modules/openxBidAdapter.js | 3 ++- test/spec/modules/openxBidAdapter_spec.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 181a0c70c7e..0f8bee213f7 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -50,7 +50,8 @@ const converter = ortbConverter({ mergeDeep(req, { at: 1, ext: { - bc: `${bidderConfig}_${bidderVersion}` + bc: `${bidderConfig}_${bidderVersion}`, + pv: '$prebid.version$' } }) const bid = context.bidRequests[0]; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index f2cff7f470c..1af0fce103d 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -14,6 +14,7 @@ import 'modules/consentManagement.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import {deepClone} from 'src/utils.js'; +import {version} from 'package.json'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -316,6 +317,7 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal(REQUEST_URL); expect(request[0].method).to.equal('POST'); + expect(request[0].data.ext.pv).to.equal(version); }); it('should send delivery domain, if available', function () { From 3f2cc28dabac78f0751dd6ce0b39fcb3a38aae06 Mon Sep 17 00:00:00 2001 From: Harry King-Riches <109534328+Strife9224@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:16:54 +0000 Subject: [PATCH 150/760] Add size 720x1280 to rubicon adapter (#10676) Internal request to get this size added. Mapped to the DV+ size id. --- modules/rubiconBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 4cfd40fb682..66dbd51f050 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -124,6 +124,7 @@ var sizeMap = { 278: '320x500', 282: '320x400', 288: '640x380', + 484: '720x1280', 524: '1x2', 548: '500x1000', 550: '980x480', From a54c17c8d208b35350a883ea74797290361163a0 Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Thu, 2 Nov 2023 18:05:07 +0300 Subject: [PATCH 151/760] AdMatic Bid Adapter: added native type (#10647) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter --- modules/admaticBidAdapter.js | 87 ++++++++++- test/spec/modules/admaticBidAdapter_spec.js | 152 ++++++++++++++++++++ 2 files changed, 233 insertions(+), 6 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index eeb0cddde89..fc5cf9c8f7b 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,15 +1,38 @@ import {getValue, logError, isEmpty, deepAccess, isArray, getBidIdParameter} 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 { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +export const OPENRTB = { + NATIVE: { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + SPONSORED: 5, + CTA: 6 + }, + DATA_ASSET_TYPE: { + SPONSORED: 1, + DESC: 2, + CTA_TEXT: 12, + }, + } +}; + let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; + export const spec = { code: BIDDER_CODE, aliases: [ {code: 'pixad'} ], - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** f * @param {object} bid * @return {boolean} @@ -46,10 +69,10 @@ export const spec = { }, blacklist: [], site: { - page: location.href, - ref: location.origin, + page: bidderRequest.refererInfo.page, + ref: bidderRequest.refererInfo.page, publisher: { - name: location.hostname, + name: bidderRequest.refererInfo.domain, publisherId: networkId } }, @@ -122,6 +145,8 @@ export const spec = { 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) }; bidResponses.push(resbid); @@ -157,6 +182,11 @@ function enrichSlotWithFloors(slot, bidRequest) { videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = bidRequest.getFloor({ size: videoSize, mediaType: VIDEO })); } + if (bidRequest.mediaTypes?.native) { + slotFloors.native = {}; + slotFloors.native['*'] = bidRequest.getFloor({ size: '*', mediaType: NATIVE }); + } + if (Object.keys(slotFloors).length > 0) { if (!slot) { slot = {} @@ -196,6 +226,11 @@ function buildRequestObject(bid) { reqObj.type = 'video'; reqObj.mediatype = bid.mediaTypes.video; } + if (bid.mediaTypes?.native) { + reqObj.type = 'native'; + reqObj.size = [{w: 1, h: 1}]; + reqObj.mediatype = bid.mediaTypes.native; + } if (deepAccess(bid, 'ortb2Imp.ext')) { reqObj.ext = bid.ortb2Imp.ext; @@ -215,10 +250,11 @@ function getSizes(bid) { function concatSizes(bid) { let playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); let videoSizes = deepAccess(bid, 'mediaTypes.video.sizes'); + let nativeSizes = deepAccess(bid, 'mediaTypes.native.sizes'); let bannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); if (isArray(bannerSizes) || isArray(playerSize) || isArray(videoSizes)) { - let mediaTypesSizes = [bannerSizes, videoSizes, playerSize]; + let mediaTypesSizes = [bannerSizes, videoSizes, nativeSizes, playerSize]; return mediaTypesSizes .reduce(function(acc, currSize) { if (isArray(currSize)) { @@ -233,6 +269,45 @@ function concatSizes(bid) { } } +function interpretNativeAd(adm) { + const native = JSON.parse(adm).native; + const result = { + clickUrl: encodeURI(native.link.url), + impressionTrackers: native.imptrackers + }; + native.assets.forEach(asset => { + switch (asset.id) { + case OPENRTB.NATIVE.ASSET_ID.TITLE: + result.title = asset.title.text; + break; + case OPENRTB.NATIVE.ASSET_ID.IMAGE: + result.image = { + url: encodeURI(asset.img.url), + width: asset.img.w, + height: asset.img.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.ICON: + result.icon = { + url: encodeURI(asset.img.url), + width: asset.img.w, + height: asset.img.h + }; + break; + case OPENRTB.NATIVE.ASSET_ID.BODY: + result.body = asset.data.value; + break; + case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + result.sponsoredBy = asset.data.value; + break; + case OPENRTB.NATIVE.ASSET_ID.CTA: + result.cta = asset.data.value; + break; + } + }); + return result; +} + function _validateId(id) { return (parseInt(id) > 0); } diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index b378ec2d2a4..bd409958b1a 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -16,6 +16,10 @@ describe('admaticBidAdapter', () => { describe('isBidRequestValid', function() { let bid = { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, 'bidder': 'admatic', 'params': { 'networkId': 10433394, @@ -48,6 +52,10 @@ describe('admaticBidAdapter', () => { describe('buildRequests', function () { it('sends bid request to ENDPOINT via POST', function () { let validRequest = [ { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, 'bidder': 'admatic', 'params': { 'networkId': 10433394, @@ -151,6 +159,51 @@ describe('admaticBidAdapter', () => { 'placement': 2 }, 'id': '45e86fc7ce7fc93' + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'title': { + 'required': true, + 'len': 120 + }, + 'image': { + 'required': true + }, + 'icon': { + 'required': false, + 'sizes': [ + 640, + 480 + ] + }, + 'sponsoredBy': { + 'required': false + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': false + }, + 'displayUrl': { + 'required': false + } + }, + 'ext': { + 'instl': 0, + 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', + 'data': { + 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' + } + }, + 'id': '16e0c8982318f91' } ], 'ext': { @@ -159,6 +212,10 @@ describe('admaticBidAdapter', () => { } } ]; let bidderRequest = { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, 'bidder': 'admatic', 'params': { 'networkId': 10433394, @@ -262,6 +319,51 @@ describe('admaticBidAdapter', () => { 'placement': 2 }, 'id': '45e86fc7ce7fc93' + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'title': { + 'required': true, + 'len': 120 + }, + 'image': { + 'required': true + }, + 'icon': { + 'required': false, + 'sizes': [ + 640, + 480 + ] + }, + 'sponsoredBy': { + 'required': false + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': false + }, + 'displayUrl': { + 'required': false + } + }, + 'ext': { + 'instl': 0, + 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', + 'data': { + 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' + } + }, + 'id': '16e0c8982318f91' } ], 'ext': { @@ -307,6 +409,10 @@ describe('admaticBidAdapter', () => { }, ]; let bidderRequest = { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, 'bidder': 'admatic', 'params': { 'networkId': 10433394, @@ -378,6 +484,19 @@ describe('admaticBidAdapter', () => { 'adomain': ['admatic.com.tr'], 'party_tag': 'https://www.admatic.com.tr', 'iurl': 'https://www.admatic.com.tr' + }, + { + 'id': 4, + 'creative_id': '3742', + 'width': 1, + 'height': 1, + 'price': 0.01, + 'type': 'native', + 'mime_type': 'iframe', + 'bidder': 'admatic', + 'adomain': ['admatic.com.tr'], + 'party_tag': '{"native":{"ver":"1.1","assets":[{"id":1,"title":{"text":"title"}},{"id":4,"data":{"value":"body"}},{"id":5,"data":{"value":"sponsored"}},{"id":2,"img":{"url":"https://www.admatic.com.tr","w":1200,"h":628}},{"id":3,"img":{"url":"https://www.admatic.com.tr","w":640,"h":480}}],"link":{"url":"https://www.admatic.com.tr"},"imptrackers":["https://www.admatic.com.tr"]}}', + 'iurl': 'https://www.admatic.com.tr' } ], 'queryId': 'cdnbh24rlv0hhkpfpln0', @@ -437,6 +556,39 @@ describe('admaticBidAdapter', () => { }, ttl: 60, bidder: 'admatic' + }, + { + requestId: 4, + cpm: 0.01, + width: 1, + height: 1, + currency: 'TRY', + mediaType: 'native', + netRevenue: true, + native: { + 'clickUrl': 'https://www.admatic.com.tr', + 'impressionTrackers': ['https://www.admatic.com.tr'], + 'title': 'title', + 'body': 'body', + 'sponsoredBy': 'sponsored', + 'image': { + 'url': 'https://www.admatic.com.tr', + 'width': 1200, + 'height': 628 + }, + 'icon': { + 'url': 'https://www.admatic.com.tr', + 'width': 640, + 'height': 480 + } + }, + creativeId: '3742', + meta: { + model: 'iframe', + advertiserDomains: ['admatic.com.tr'] + }, + ttl: 60, + bidder: 'admatic' } ]; const request = { From ef8bec95d03e29d8c927d1c8946dd4831157d18d Mon Sep 17 00:00:00 2001 From: Jhon Barreiro <90330032+jhon-reset@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:09:02 -0500 Subject: [PATCH 152/760] Reset Digital Bid Adapter: refactoring usersync method (#10673) * Reset Digital Bid Adapter: refactoring usersync method * Bugfix on userSyncs --------- Co-authored-by: Jhon --- modules/resetdigitalBidAdapter.js | 122 +++++++++++++++++------------- 1 file changed, 69 insertions(+), 53 deletions(-) diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index 8264e0cc9cc..2bac6c6dcba 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,25 +1,29 @@ import { timestamp, deepAccess, isStr, deepClone } from '../src/utils.js'; import { getOrigin } from '../libraries/getOrigin/index.js'; import { config } from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'resetdigital'; const CURRENCY = 'USD'; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [ 'banner', 'video' ], - isBidRequestValid: function(bid) { - return (!!(bid.params.pubId || bid.params.zoneId)); + supportedMediaTypes: ['banner', 'video'], + isBidRequestValid: function (bid) { + return !!(bid.params.pubId || bid.params.zoneId); }, - buildRequests: function(validBidRequests, bidderRequest) { - let stack = (bidderRequest.refererInfo && - bidderRequest.refererInfo.stack ? bidderRequest.refererInfo.stack - : []) - - let spb = (config.getConfig('userSync') && config.getConfig('userSync').syncsPerBidder) - ? config.getConfig('userSync').syncsPerBidder : 5 + buildRequests: function (validBidRequests, bidderRequest) { + let stack = + bidderRequest.refererInfo && bidderRequest.refererInfo.stack + ? bidderRequest.refererInfo.stack + : []; + + let spb = + config.getConfig('userSync') && + config.getConfig('userSync').syncsPerBidder + ? config.getConfig('userSync').syncsPerBidder + : 5; const payload = { start_time: timestamp(), @@ -29,19 +33,19 @@ export const spec = { iframe: !bidderRequest.refererInfo.reachedTop, // TODO: the last element in refererInfo.stack is window.location.href, that's unlikely to have been the intent here url: stack && stack.length > 0 ? [stack.length - 1] : null, - https: (window.location.protocol === 'https:'), + https: window.location.protocol === 'https:', // TODO: is 'page' the right value here? - referrer: bidderRequest.refererInfo.page + referrer: bidderRequest.refererInfo.page, }, imps: [], user_ids: validBidRequests[0].userId, - sync_limit: spb + sync_limit: spb, }; if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr = { applies: bidderRequest.gdprConsent.gdprApplies, - consent: bidderRequest.gdprConsent.consentString + consent: bidderRequest.gdprConsent.consentString, }; } @@ -50,10 +54,16 @@ export const spec = { } function getOrtb2Keywords(ortb2Obj) { - const fields = ['site.keywords', 'site.content.keywords', 'user.keywords', 'app.keywords', 'app.content.keywords']; + const fields = [ + 'site.keywords', + 'site.content.keywords', + 'user.keywords', + 'app.keywords', + 'app.content.keywords', + ]; let result = []; - fields.forEach(path => { + fields.forEach((path) => { let keyStr = deepAccess(ortb2Obj, path); if (isStr(keyStr)) result.push(keyStr); }); @@ -79,18 +89,26 @@ export const spec = { const floorInfo = req.getFloor({ currency: CURRENCY, mediaType: BANNER, - size: '*' + size: '*', }); - if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + if ( + typeof floorInfo === 'object' && + floorInfo.currency === CURRENCY && + !isNaN(parseFloat(floorInfo.floor)) + ) { bidFloor = parseFloat(floorInfo.floor); bidFloorCur = CURRENCY; } } // get param kewords (if it exists) - let paramsKeywords = req.params.keywords ? req.params.keywords.split(',') : []; + let paramsKeywords = req.params.keywords + ? req.params.keywords.split(',') + : []; // merge all keywords - let keywords = ortb2KeywordsList.concat(paramsKeywords).concat(metaKeywords); + let keywords = ortb2KeywordsList + .concat(paramsKeywords) + .concat(metaKeywords); payload.imps.push({ pub_id: req.params.pubId, @@ -110,32 +128,32 @@ export const spec = { sizes: req.sizes, force_bid: req.params.forceBid, coppa: config.getConfig('coppa') === true ? 1 : 0, - media_types: deepAccess(req, 'mediaTypes') + media_types: deepAccess(req, 'mediaTypes'), }); } - let params = validBidRequests[0].params - let url = params.endpoint ? params.endpoint : '//ads.resetsrv.com' + let params = validBidRequests[0].params; + let url = params.endpoint ? params.endpoint : '//ads.resetsrv.com'; return { method: 'POST', url: url, data: JSON.stringify(payload), - bids: validBidRequests + bids: validBidRequests, }; }, - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; if (!serverResponse || !serverResponse.body) { - return bidResponses + return bidResponses; } let res = serverResponse.body; if (!res.bids || !res.bids.length) { - return [] + return []; } for (let x = 0; x < serverResponse.body.bids.length; x++) { - let bid = serverResponse.body.bids[x] + let bid = serverResponse.body.bids[x]; bidResponses.push({ requestId: bid.bid_id, @@ -152,47 +170,45 @@ export const spec = { netRevenue: true, currency: 'USD', meta: { - advertiserDomains: bid.adomain - } - }) + advertiserDomains: bid.adomain, + }, + }); } return bidResponses; }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { - const syncs = [] + getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + const syncs = []; if (!serverResponses.length || !serverResponses[0].body) { - return syncs + return syncs; } - let pixels = serverResponses[0].body.pixels + let pixels = serverResponses[0].body.pixels; if (!pixels || !pixels.length) { - return syncs + return syncs; } - let gdprParams = null + let gdprParams = ''; if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { - gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` + gdprParams = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${ + gdprConsent.consentString + }`; } else { - gdprParams = `gdpr_consent=${gdprConsent.consentString}` + gdprParams = `gdpr_consent=${gdprConsent.consentString}`; } } - for (let x = 0; x < pixels.length; x++) { - let pixel = pixels[x] - - if ((pixel.type === 'iframe' && syncOptions.iframeEnabled) || - (pixel.type === 'image' && syncOptions.pixelEnabled)) { - if (gdprParams && gdprParams.length) { - pixel = (pixel.indexOf('?') === -1 ? '?' : '&') + gdprParams - } - syncs.push(pixel) - } + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + return [ + { + type: 'iframe', + url: 'https://media.reset-digital.com/prebid/async_usersync.html?' + gdprParams.length ? gdprParams : '', + }, + ]; } - return syncs; - } + }, }; registerBidder(spec); From 30ac7f5dff10cd5ce9841c4451641d797e888d4f Mon Sep 17 00:00:00 2001 From: Vitaliy Pavlenko Date: Thu, 2 Nov 2023 20:08:50 +0300 Subject: [PATCH 153/760] Criteo Bid Adapter: Fledge Support (#10665) * add Fledge auction configs support * add Fledge auction configs support * add Fledge auction configs support * add Fledge auction configs support * use native array find method instead of polyfill --- modules/criteoBidAdapter.js | 49 ++++++- test/spec/modules/criteoBidAdapter_spec.js | 145 +++++++++++++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 9ff6b540467..6de9e63f6d6 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -16,6 +16,9 @@ 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: '; @@ -201,7 +204,7 @@ export const spec = { /** * @param {*} response * @param {ServerRequest} request - * @return {Bid[]} + * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} */ interpretResponse: (response, request) => { const body = response.body || response; @@ -215,6 +218,7 @@ export const spec = { } const bids = []; + const fledgeAuctionConfigs = []; if (body && body.slots && isArray(body.slots)) { body.slots.forEach(slot => { @@ -268,6 +272,45 @@ export const spec = { }); } + if (isArray(body.ext?.igbid)) { + const seller = body.ext.seller || FLEDGE_SELLER_DOMAIN; + const sellerTimeout = body.ext.sellerTimeout || FLEDGE_SELLER_TIMEOUT; + const sellerSignals = body.ext.sellerSignals || {}; + 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); + if (!sellerSignals.floor && bidRequest.params.bidFloor) { + sellerSignals.floor = bidRequest.params.bidFloor; + } + if (!sellerSignals.sellerCurrency && bidRequest.params.bidFloorCur) { + sellerSignals.sellerCurrency = bidRequest.params.bidFloorCur; + } + const bidId = bidRequest.bidId; + fledgeAuctionConfigs.push({ + bidId, + config: { + seller, + sellerSignals, + sellerTimeout, + perBuyerSignals, + auctionSignals: {}, + decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, + interestGroupBuyers: Object.keys(perBuyerSignals), + }, + }); + }); + } + + if (fledgeAuctionConfigs.length) { + return { + bids, + fledgeAuctionConfigs, + }; + } + return bids; }, /** @@ -529,6 +572,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { enrichSlotWithFloors(slot, bidRequest); + if (!bidderRequest.fledgeEnabled && slot.ext?.ae) { + delete slot.ext.ae; + } + return slot; }), }; diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 7cba0e2fbdf..e333d0c6143 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1879,6 +1879,70 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.slots[0].rwdd).to.be.undefined; }); + + it('should properly build a request when FLEDGE is enabled', function () { + const bidderRequest = { + fledgeEnabled: true, + }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + ext: { + ae: 1 + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext.ae).to.equal(1); + }); + + it('should properly build a request when FLEDGE is disabled', function () { + const bidderRequest = { + fledgeEnabled: false, + }; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + ext: { + bidfloor: 0.75 + } + }, + ortb2Imp: { + ext: { + ae: 1 + } + } + }, + ]; + + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.slots[0].ext).to.not.have.property('ae'); + }); }); describe('interpretResponse', function () { @@ -2410,6 +2474,87 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].height).to.equal(90); }); + it('should properly parse a bid response with FLEDGE auction configs', function () { + const response = { + body: { + ext: { + igbid: [{ + impid: 'test-bidId', + igbuyer: [{ + origin: 'https://first-buyer-domain.com', + buyerdata: { + foo: 'bar', + }, + }, { + origin: 'https://second-buyer-domain.com', + buyerdata: { + foo: 'baz', + }, + }] + }], + seller: 'https://seller-domain.com', + sellerTimeout: 500, + sellerSignals: { + foo: 'bar', + }, + }, + }, + }; + const bidderRequest = { + ortb2: { + source: { + tid: 'abc' + } + } + }; + const bidRequests = [ + { + bidId: 'test-bidId', + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + bidFloor: 1, + bidFloorCur: 'EUR' + } + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const interpretedResponse = spec.interpretResponse(response, request); + expect(interpretedResponse).to.have.property('bids'); + expect(interpretedResponse).to.have.property('fledgeAuctionConfigs'); + expect(interpretedResponse.bids).to.have.lengthOf(0); + expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(1); + 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' + }, + }, + seller: 'https://seller-domain.com', + sellerTimeout: 500, + sellerSignals: { + foo: 'bar', + floor: 1, + sellerCurrency: 'EUR', + }, + }, + }); + }); + [{ hasBidResponseLevelPafData: true, hasBidResponseBidLevelPafData: true, From 534230a2507dedd75d2b2293ceedec9ed12363b3 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 3 Nov 2023 14:31:33 +0000 Subject: [PATCH 154/760] Prebid 8.22.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 373d8f5c5ff..b3473b8eade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.22.0-pre", + "version": "8.22.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index d68b68f3b51..63327e56eba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.22.0-pre", + "version": "8.22.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From e1384af3ac779e4315597b9433ac71fae3a47003 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 3 Nov 2023 14:31:34 +0000 Subject: [PATCH 155/760] Increment version to 8.23.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3473b8eade..8dd76a8f0ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.22.0", + "version": "8.23.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 63327e56eba..9e718d76456 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.22.0", + "version": "8.23.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d74533ba1515bc0b1b5180ad0a76622b8bc81f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20Lindstr=C3=B6m?= Date: Fri, 3 Nov 2023 17:57:44 +0100 Subject: [PATCH 156/760] relevantBidAdapter: support s2s (#10610) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hugo Lindström --- modules/relevantdigitalBidAdapter.js | 27 +++++- .../modules/relevantdigitalBidAdapter_spec.js | 92 +++++++++++++++++-- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/modules/relevantdigitalBidAdapter.js b/modules/relevantdigitalBidAdapter.js index ad9ee5e1e14..ef4e1c8e33d 100644 --- a/modules/relevantdigitalBidAdapter.js +++ b/modules/relevantdigitalBidAdapter.js @@ -98,7 +98,14 @@ export const spec = { isBidRequestValid: (bid) => bid.params?.placementId && getBidderConfig([bid]).complete, /** Trigger impression-pixel */ - onBidWon: ({pbsWurl}) => pbsWurl && triggerPixel(pbsWurl), + onBidWon(bid) { + if (bid.pbsWurl) { + triggerPixel(bid.pbsWurl) + } + if (bid.burl) { + triggerPixel(bid.burl) + } + }, /** Build BidRequest for PBS */ buildRequests(bidRequests, bidderRequest) { @@ -193,6 +200,24 @@ export const spec = { }); return syncs; }, + + /** If server side, transform bid params if needed */ + transformBidParams(params, isOrtb, adUnit, bidRequests) { + if (!params.placementId) { + return; + } + const bid = bidRequests.flatMap(req => req.adUnitsS2SCopy || []).flatMap((adUnit) => adUnit.bids).find((bid) => bid.params?.placementId === params.placementId); + if (!bid) { + return; + } + const cfg = getBidderConfig([bid]); + FIELDS.forEach(({ name }) => { + if (cfg[name] && !params[name]) { + params[name] = cfg[name]; + } + }); + return params; + }, }; registerBidder(spec); diff --git a/test/spec/modules/relevantdigitalBidAdapter_spec.js b/test/spec/modules/relevantdigitalBidAdapter_spec.js index b2a5495b3cb..0e21453c8ba 100644 --- a/test/spec/modules/relevantdigitalBidAdapter_spec.js +++ b/test/spec/modules/relevantdigitalBidAdapter_spec.js @@ -1,5 +1,10 @@ import {spec, resetBidderConfigs} from 'modules/relevantdigitalBidAdapter.js'; import { parseUrl, deepClone } from 'src/utils.js'; +import { config } from 'src/config.js'; +import CONSTANTS from 'src/constants.json'; + +import adapterManager, { +} from 'src/adapterManager.js'; const expect = require('chai').expect; @@ -9,14 +14,29 @@ const ACCOUNT_ID = 'example_account_id'; const TEST_DOMAIN = 'example.com'; const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; -const BID_REQUEST = -{ - 'bidder': 'relevantdigital', +const CONFIG = { + enabled: true, + endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT, + timeout: 1000, + maxBids: 1, + adapter: 'prebidServer', + bidders: ['relevantdigital'], + accountId: 'abc' +}; + +const ADUNIT_CODE = '/19968336/header-bid-tag-0'; + +const BID_PARAMS = { 'params': { 'placementId': PLACEMENT_ID, 'accountId': ACCOUNT_ID, - 'pbsHost': PBS_HOST, - }, + 'pbsHost': PBS_HOST + } +}; + +const BID_REQUEST = { + 'bidder': 'relevantdigital', + ...BID_PARAMS, 'ortb2Imp': { 'ext': { 'tid': 'e13391ea-00f3-495d-99a6-d937990d73a9' @@ -32,7 +52,7 @@ const BID_REQUEST = ] } }, - 'adUnitCode': '/19968336/header-bid-tag-0', + 'adUnitCode': ADUNIT_CODE, 'transactionId': 'e13391ea-00f3-495d-99a6-d937990d73a9', 'sizes': [ [ @@ -292,4 +312,64 @@ describe('Relevant Digital Bid Adaper', function () { expect(allSyncs).to.deep.equal(expectedResult) }); }); + describe('transformBidParams', function () { + beforeEach(() => { + config.setConfig({ + s2sConfig: CONFIG, + }); + }); + afterEach(() => { + config.resetConfig(); + }); + + const adUnit = (params) => ({ + code: ADUNIT_CODE, + bids: [ + { + bidder: 'relevantdigital', + adUnitCode: ADUNIT_CODE, + params, + } + ] + }); + + const request = (params) => adapterManager.makeBidRequests([adUnit(params)], 123, 'auction-id', 123, [], {})[0]; + + it('transforms adunit bid params and config params correctly', function () { + config.setConfig({ + relevantdigital: { + pbsHost: PBS_HOST, + accountId: ACCOUNT_ID, + }, + }); + const adUnitParams = { placementId: PLACEMENT_ID }; + const expextedTransformedBidParams = { + ...BID_PARAMS.params, pbsHost: `https://${BID_PARAMS.params.pbsHost}`, 'pbsBufferMs': 250 + }; + expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); + }); + it('transforms adunit bid params correctly', function () { + const adUnitParams = { ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 }; + const expextedTransformedBidParams = { + ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 + }; + expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); + }); + it('transforms adunit bid params correctly', function () { + const adUnitParams = { ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 }; + const expextedTransformedBidParams = { + ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 + }; + expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); + }); + it('does not transform bid params if placementId is missing', function () { + const adUnitParams = { ...BID_PARAMS.params, placementId: null }; + expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.equal(undefined); + }); + it('does not transform bid params s2s config is missing', function () { + config.resetConfig(); + const adUnitParams = BID_PARAMS.params; + expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.equal(undefined); + }); + }) }); From b4598fa6e689f16ee1340f2e753ebdffa489199a Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:28:08 -0700 Subject: [PATCH 157/760] PublinkIdSystem: Pass cached id through for better tracking (#10638) Co-authored-by: johwier --- modules/publinkIdSystem.js | 42 +++++++++++++--------- test/spec/modules/publinkIdSystem_spec.js | 43 ++++++++++++++++++++--- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 5b20dbb620a..1a993c99b45 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -16,6 +16,8 @@ const MODULE_NAME = 'publinkId'; const GVLID = 24; const PUBLINK_COOKIE = '_publink'; const PUBLINK_S2S_COOKIE = '_publink_srv'; +const PUBLINK_REQUEST_PATH = '/cvx/client/sync/publink'; +const PUBLINK_REFRESH_PATH = '/cvx/client/sync/publink/refresh'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); @@ -23,10 +25,9 @@ function isHex(s) { return /^[A-F0-9]+$/i.test(s); } -function publinkIdUrl(params, consentData) { - let url = parseUrl('https://proc.ad.cpe.dotomi.com/cvx/client/sync/publink'); +function publinkIdUrl(params, consentData, storedId) { + let url = parseUrl('https://proc.ad.cpe.dotomi.com' + PUBLINK_REFRESH_PATH); url.search = { - deh: params.e, mpn: 'Prebid.js', mpv: '$prebid.version$', }; @@ -36,9 +37,21 @@ function publinkIdUrl(params, consentData) { url.search.gdpr_consent = consentData.consentString; } - if (params.site_id) { url.search.sid = params.site_id; } + if (params) { + if (params.e) { + // if there's an email parameter call the request path + url.search.deh = params.e; + url.pathname = PUBLINK_REQUEST_PATH; + } + + if (params.site_id) { url.search.sid = params.site_id; } + + if (params.api_key) { url.search.apikey = params.api_key; } + } - if (params.api_key) { url.search.apikey = params.api_key; } + if (storedId) { + url.search.publink = storedId; + } const usPrivacyString = uspDataHandler.getConsentData(); if (usPrivacyString && typeof usPrivacyString === 'string') { @@ -48,7 +61,7 @@ function publinkIdUrl(params, consentData) { return buildUrl(url); } -function makeCallback(config = {}, consentData) { +function makeCallback(config = {}, consentData, storedId) { return function(prebidCallback) { const options = {method: 'GET', withCredentials: true}; let handleResponse = function(responseText, xhr) { @@ -59,15 +72,12 @@ function makeCallback(config = {}, consentData) { } } }; - - if (config.params && config.params.e) { - if (isHex(config.params.e)) { - ajax(publinkIdUrl(config.params, consentData), handleResponse, undefined, options); - } else { - logError('params.e must be a hex string'); - } + if ((config.params && config.params.e && isHex(config.params.e)) || storedId) { + ajax(publinkIdUrl(config.params, consentData, storedId), handleResponse, undefined, options); + } else if (config.params.e) { + logError('params.e must be a hex string'); } - }; + } } function getlocalValue() { @@ -137,9 +147,7 @@ export const publinkIdSubmodule = { if (localValue) { return {id: localValue}; } - if (!storedId) { - return {callback: makeCallback(config, consentData)}; - } + return {callback: makeCallback(config, consentData, storedId)}; }, eids: { 'publinkId': { diff --git a/test/spec/modules/publinkIdSystem_spec.js b/test/spec/modules/publinkIdSystem_spec.js index f35a7453403..5ad58ea1a37 100644 --- a/test/spec/modules/publinkIdSystem_spec.js +++ b/test/spec/modules/publinkIdSystem_spec.js @@ -72,11 +72,6 @@ describe('PublinkIdSystem', () => { expect(result.callback).to.be.a('function'); }); - it('Use local copy', () => { - const result = publinkIdSubmodule.getId({}, undefined, TEST_COOKIE_VALUE); - expect(result).to.be.undefined; - }); - describe('callout for id', () => { let callbackSpy = sinon.spy(); @@ -84,6 +79,44 @@ describe('PublinkIdSystem', () => { callbackSpy.resetHistory(); }); + it('Has cached id', () => { + const config = {storage: {type: 'cookie'}}; + let submoduleCallback = publinkIdSubmodule.getId(config, undefined, TEST_COOKIE_VALUE).callback; + submoduleCallback(callbackSpy); + + const request = server.requests[0]; + const parsed = parseUrl(request.url); + + expect(parsed.hostname).to.equal('proc.ad.cpe.dotomi.com'); + expect(parsed.pathname).to.equal('/cvx/client/sync/publink/refresh'); + expect(parsed.search.mpn).to.equal('Prebid.js'); + expect(parsed.search.mpv).to.equal('$prebid.version$'); + expect(parsed.search.publink).to.equal(TEST_COOKIE_VALUE); + + request.respond(200, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink); + }); + + it('Request path has priority', () => { + const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', site_id: '102030'}}; + let submoduleCallback = publinkIdSubmodule.getId(config, undefined, TEST_COOKIE_VALUE).callback; + submoduleCallback(callbackSpy); + + const request = server.requests[0]; + const parsed = parseUrl(request.url); + + expect(parsed.hostname).to.equal('proc.ad.cpe.dotomi.com'); + expect(parsed.pathname).to.equal('/cvx/client/sync/publink'); + expect(parsed.search.mpn).to.equal('Prebid.js'); + expect(parsed.search.mpv).to.equal('$prebid.version$'); + expect(parsed.search.publink).to.equal(TEST_COOKIE_VALUE); + + request.respond(200, {}, JSON.stringify(serverResponse)); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.lastCall.lastArg).to.equal(serverResponse.publink); + }); + it('Fetch with consent data', () => { const config = {storage: {type: 'cookie'}, params: {e: 'ca11c0ca7', site_id: '102030'}}; const consentData = {gdprApplies: 1, consentString: 'myconsentstring'}; From a069331010df0dfd778c3cc35a0205b77b7316ff Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Sat, 4 Nov 2023 12:24:32 -0700 Subject: [PATCH 158/760] support for cookie deprecation label (#10695) --- modules/pubmaticBidAdapter.js | 4 ++++ test/spec/modules/pubmaticBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index aa953fb295d..ee80de03c03 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1232,6 +1232,10 @@ export const spec = { payload.device.sua = device?.sua; } + if (device?.ext?.cdep) { + deepSetValue(payload, 'device.ext.cdep', device.ext.cdep); + } + if (user?.geo && device?.geo) { payload.device.geo = { ...payload.device.geo, ...device.geo }; payload.user.geo = { ...payload.user.geo, ...user.geo }; diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index a9f80b6e25a..154a8e1253b 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -2311,6 +2311,23 @@ describe('PubMatic adapter', function () { expect(data.device.sua).to.deep.equal(suaObject); }); + it('should pass device.ext.cdep if present in bidderRequest fpd ortb2 object', function () { + const cdepObj = { + cdep: 'example_label_1' + }; + let request = spec.buildRequests(multipleMediaRequests, { + auctionId: 'new-auction-id', + ortb2: { + device: { + ext: cdepObj + } + } + }); + let data = JSON.parse(request.data); + expect(data.device.ext.cdep).to.exist.and.to.be.an('string'); + expect(data.device.ext).to.deep.equal(cdepObj); + }); + it('Request params should have valid native bid request for all valid params', function () { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' From c54410085f8996ec5c950e5dee40c860ef4a6a7f Mon Sep 17 00:00:00 2001 From: samous Date: Mon, 6 Nov 2023 14:05:24 +0100 Subject: [PATCH 159/760] BLIINK Bid Adapter: hotfix gvlid parameter (#10697) Co-authored-by: Samous --- modules/bliinkBidAdapter.js | 2 ++ test/spec/modules/bliinkBidAdapter_spec.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 7042dd86cb9..e674c6987c3 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -4,6 +4,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' export const BIDDER_CODE = 'bliink' +export const GVL_ID = 658 export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' export const BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME = 'https://tag.bliink.io/usersync.html' @@ -343,6 +344,7 @@ const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent) => */ export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, aliases: aliasBidderCode, supportedMediaTypes: supportedMediaTypes, isBidRequestValid, diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index cd1dcf4a20a..3db97a17d88 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -8,6 +8,7 @@ import { getEffectiveConnectionType, getUserIds, getDomLoadingDuration, + GVL_ID, } from 'modules/bliinkBidAdapter.js'; import { config } from 'src/config.js'; @@ -1168,3 +1169,7 @@ describe('getEffectiveConnectionType', () => { }); } }); + +it('should expose gvlid', function () { + expect(spec.gvlid).to.equal(GVL_ID); +}); From 99d7e719d56ddf57ff155550421bbde509968e21 Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:15:55 +0100 Subject: [PATCH 160/760] Tappx Bid Adapter : fixed referrer for iframes using external sites error (#10698) * Tappx Refactor: Optimizing and adding more checkers and tests * Fix: fixed site referrer for iframes using external sites error #13231 --------- Co-authored-by: Jordi Arnau --- modules/tappxBidAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 531927114c8..90438f92e1b 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -276,7 +276,11 @@ function buildOneRequest(validBidRequests, bidderRequest) { site.name = bundle; site.page = bidderRequest?.refererInfo?.page || deepAccess(validBidRequests, 'params.site.page') || bidderRequest?.refererInfo?.topmostLocation || window.location.href || bundle; site.domain = bundle; - site.ref = bidderRequest?.refererInfo?.ref || window.top.document.referrer || ''; + try { + site.ref = bidderRequest?.refererInfo?.ref || window.top.document.referrer || ''; + } catch (e) { + site.ref = bidderRequest?.refererInfo?.ref || window.document.referrer || ''; + } site.ext = {}; site.ext.is_amp = bidderRequest?.refererInfo?.isAmp || 0; site.ext.page_da = deepAccess(validBidRequests, 'params.site.page') || '-'; From bb9be09c96a6bc204712b5f38391aa08ba2475ed Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Tue, 7 Nov 2023 04:30:01 +0800 Subject: [PATCH 161/760] Mediago Bid Adapter: add imp params for analysis (#10691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * update Mediago & Discovery BidAdapter:remove size filter * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:add param in banner format * update mediago & discovery:first party data * update mediago & discovery:first party data * update mediago & discovery:first party data * fix(mediago & discovery): update param tagid * fix(mediago & discovery): update param tagid * feat:add imp params for analysis * fix:add imp params for analysis * code format * test:add test data & test * format code * rerun circleci --------- Co-authored-by: BaronYu --- modules/mediagoBidAdapter.js | 11 ++++---- test/spec/modules/mediagoBidAdapter_spec.js | 29 +++++++++++++++++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 756e636572d..efd730acace 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -221,10 +221,10 @@ function getItems(validBidRequests, bidderRequest) { } const bidFloor = getBidFloor(req); - // const gpid = - // utils.deepAccess(req, 'ortb2Imp.ext.gpid') || - // utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || - // utils.deepAccess(req, 'params.placementId', 0); + const gpid = + utils.deepAccess(req, 'ortb2Imp.ext.gpid') || + utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || + utils.deepAccess(req, 'params.placementId', 0); // if (mediaTypes.native) {} // banner广告类型 @@ -240,7 +240,8 @@ function getItems(validBidRequests, bidderRequest) { format: sizes, }, ext: { - // gpid: gpid, // 加入后无法返回广告 + ortb2Imp: utils.deepAccess(req, 'ortb2Imp'), // 传入完整对象,分析日志数据 + gpid: gpid, // 加入后无法返回广告 }, tagid: req.params && req.params.tagid, }; diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index e77af544429..5eb362893e3 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -11,11 +11,40 @@ describe('mediago:BidAdapterTests', function () { bidder: 'mediago', params: { token: '85a6b01e41ac36d49744fad726e3655d', + siteId: 'siteId_01', + zoneId: 'zoneId_01', + publisher: '52', + position: 'left', + referrer: 'https://trace.mediago.io', bidfloor: 0.01, + ortb2Imp: { + ext: { + gpid: 'adslot_gpid', + tid: 'tid_01', + data: { + browsi: { + browsiViewability: 'NA', + }, + adserver: { + name: 'adserver_name', + adslot: 'adslot_name', + }, + }, + }, + }, }, mediaTypes: { banner: { sizes: [[300, 250]], + pos: 'left', + }, + }, + ortb2: { + user: { + ext: { + data: { + }, + }, }, }, adUnitCode: 'regular_iframe', From 87141d46a3c74133f00cb698be8dac4e247979ac Mon Sep 17 00:00:00 2001 From: Keith Doggett Date: Tue, 7 Nov 2023 10:06:01 -0500 Subject: [PATCH 162/760] GoldfishAds Rtd Provider : Initial Module Release (#10623) * Add goldfishAdsRtdProvider Module * Update description and change instances of GoldfishAds to Goldfish Ads * update names --- modules/goldfishAdsRtdProvider.js | 194 ++++++++++++++++++ modules/goldfishAdsRtdProvider.md | 48 +++++ .../modules/goldfishAdsRtdProvider_spec.js | 163 +++++++++++++++ 3 files changed, 405 insertions(+) create mode 100755 modules/goldfishAdsRtdProvider.js create mode 100755 modules/goldfishAdsRtdProvider.md create mode 100755 test/spec/modules/goldfishAdsRtdProvider_spec.js diff --git a/modules/goldfishAdsRtdProvider.js b/modules/goldfishAdsRtdProvider.js new file mode 100755 index 00000000000..c466ac5ec25 --- /dev/null +++ b/modules/goldfishAdsRtdProvider.js @@ -0,0 +1,194 @@ +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepAccess } from '../src/utils.js'; + +export const MODULE_NAME = 'goldfishAdsRtd'; +export const MODULE_TYPE = 'realTimeData'; +export const ENDPOINT_URL = 'https://prebid.goldfishads.com/iab-segments'; +export const DATA_STORAGE_KEY = 'goldfishads_data'; +export const DATA_STORAGE_TTL = 1800 * 1000// TTL in seconds + +export const ADAPTER_VERSION = '1.0'; + +export const storage = getStorageManager({ + gvlid: null, + moduleName: MODULE_NAME, + moduleType: MODULE_TYPE, +}); + +/** + * + * @param {{response: string[]} } response + * @returns + */ +export const manageCallbackResponse = (response) => { + try { + const foo = JSON.parse(response.response); + if (!Array.isArray(foo)) throw new Error('Invalid response'); + const enrichedResponse = { + ext: { + segtax: 4 + }, + segment: foo.map((segment) => { return { id: segment } }), + }; + const output = { + name: 'goldfishads.com', + ...enrichedResponse, + }; + return output; + } catch (e) { + throw e; + }; +}; + +/** + * @param {string} key + * @returns { Promise<{name: 'goldfishads.com', ext: { segtag: 4 }, segment: string[]}> } + */ + +const getTargetingDataFromApi = (key) => { + return new Promise((resolve, reject) => { + const requestOptions = { + customHeaders: { + 'Accept': 'application/json' + } + } + const callbacks = { + success(responseText, response) { + try { + const output = manageCallbackResponse(response); + resolve(output); + } catch (e) { + reject(e); + } + }, + error(error) { + reject(error); + } + }; + ajax(`${ENDPOINT_URL}?key=${key}`, callbacks, null, requestOptions) + }) +}; + +/** + * @returns {{ + * name: 'golfishads.com', + * ext: { segtax: 4}, + * segment: string[] + * } | null } + */ +export const getStorageData = () => { + const now = new Date(); + const data = storage.getDataFromLocalStorage(DATA_STORAGE_KEY); + if (data === null) return null; + try { + const foo = JSON.parse(data); + if (now.getTime() > foo.expiry) return null; + return foo.targeting; + } catch (e) { + return null; + } +}; + +/** + * @param { { key: string } } payload + * @returns {Promise<{ + * name: string, + * ext: { segtax: 4}, + * segment: string[] + * }> | null + * } + */ + +const getTargetingData = (payload) => new Promise((resolve) => { + const targeting = getStorageData(); + if (targeting === null) { + getTargetingDataFromApi(payload.key) + .then((response) => { + const now = new Date() + const data = { + targeting: response, + expiry: now.getTime() + DATA_STORAGE_TTL, + }; + storage.setDataInLocalStorage(DATA_STORAGE_KEY, JSON.stringify(data)); + resolve(response); + }) + .catch((e) => { + resolve(null); + }); + } else { + resolve(targeting); + } +}) + +/** + * + * @param {*} config + * @param {*} userConsent + * @returns {boolean} + */ + +const init = (config, userConsent) => { + if (!config.params || !config.params.key) return false; + // return { type: (typeof config.params.key === 'string') }; + if (!(typeof config.params.key === 'string')) return false; + return true; +}; + +/** + * + * @param {{ + * name: string, +* ext: { segtax: 4}, +* segment: {id: string}[] +* } | null } userData + * @param {*} reqBidsConfigObj + * @returns + */ +export const updateUserData = (userData, reqBidsConfigObj) => { + if (userData === null) return; + const bidders = ['appnexus', 'rubicon', 'nexx360']; + for (let i = 0; i < bidders.length; i++) { + const bidderCode = bidders[i]; + const originalConfig = deepAccess(reqBidsConfigObj, `ortb2Fragments.bidder[${bidderCode}].user.data`) || []; + const userConfig = [ + ...originalConfig, + userData, + ]; + reqBidsConfigObj.ortb2Fragments = reqBidsConfigObj.ortb2Fragments || {}; + reqBidsConfigObj.ortb2Fragments.bidder = reqBidsConfigObj.ortb2Fragments.bidder || {}; + reqBidsConfigObj.ortb2Fragments.bidder[bidderCode] = reqBidsConfigObj.ortb2Fragments.bidder[bidderCode] || {}; + reqBidsConfigObj.ortb2Fragments.bidder[bidderCode].user = reqBidsConfigObj.ortb2Fragments.bidder[bidderCode].user = {}; + reqBidsConfigObj.ortb2Fragments.bidder[bidderCode].user.data = reqBidsConfigObj.ortb2Fragments.bidder[bidderCode].user.data || userConfig; + } + return reqBidsConfigObj; +} + +/** + * + * @param {*} reqBidsConfigObj + * @param {*} callback + * @param {*} moduleConfig + * @param {*} userConsent + * @returns {void} + */ +const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { + const payload = { + key: moduleConfig.params.key, + }; + getTargetingData(payload) + .then((userData) => { + updateUserData(userData, reqBidsConfigObj); + callback(); + }); +}; + +/** @type {RtdSubmodule} */ +export const goldfishAdsSubModule = { + name: MODULE_NAME, + init, + getBidRequestData, +}; + +submodule(MODULE_TYPE, goldfishAdsSubModule); diff --git a/modules/goldfishAdsRtdProvider.md b/modules/goldfishAdsRtdProvider.md new file mode 100755 index 00000000000..4625c9a7988 --- /dev/null +++ b/modules/goldfishAdsRtdProvider.md @@ -0,0 +1,48 @@ +# Goldfish Ads Real-time Data Submodule + +## Overview + + Module Name: Goldfish Ads Rtd Provider + Module Type: Rtd Provider + Maintainer: keith@goldfishads.com + +## Description + +This RTD module provides access to the Goldfish Ads Geograph, which leverages geographic and temporal data on a privcay-first platform. This module works without using cookies, PII, emails, or device IDs across all website traffic, including unauthenticated users, and adds audience data into bid requests to increase scale and yields. + +## Usage + +### Build +``` +gulp build --modules="rtdModule,goldfishAdsRtdProvider,appnexusBidAdapter,..." +``` + +> Note that the global RTD module, `rtdModule`, is a prerequisite of the Goldfish Ads RTD module. + +### Configuration + +Use `setConfig` to instruct Prebid.js to initialize the Goldfish Ads RTD module, as specified below. + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 300, + dataProviders: [{ + name: 'goldfishAds', + waitForIt: true, + params: { + key: 'testkey' + } + }] + } +}) +``` + +### Parameters +| Name | Type | Description | Default | +|:-----------------|:----------------------------------------|:-----------------------------------------------------------------------------|:-----------------------| +| name | String | Real time data module name | Always 'goldfishAds' | +| waitForIt | Boolean | Set to true to maximize chance for bidder enrichment, used with auctionDelay | `false` | +| params.key | String | Your key id issued by Goldfish Ads | | diff --git a/test/spec/modules/goldfishAdsRtdProvider_spec.js b/test/spec/modules/goldfishAdsRtdProvider_spec.js new file mode 100755 index 00000000000..39a1e0c9b33 --- /dev/null +++ b/test/spec/modules/goldfishAdsRtdProvider_spec.js @@ -0,0 +1,163 @@ +import { + goldfishAdsSubModule, + manageCallbackResponse, +} from 'modules/goldfishAdsRtdProvider.js'; +import { getStorageManager } from '../../../src/storageManager.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +import { config as _config } from 'src/config.js'; +import { DATA_STORAGE_KEY, MODULE_NAME, MODULE_TYPE, getStorageData, updateUserData } from '../../../modules/goldfishAdsRtdProvider'; + +const responseHeader = { 'Content-Type': 'application/json' }; + +const sampleConfig = { + name: 'golfishAds', + waitForIt: true, + params: { + key: 'testkey' + } +}; + +const sampleAdUnits = [ + { + code: 'one-div-id', + mediaTypes: { + banner: { + sizes: [970, 250] + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 12345370, + } + }] + }, + { + code: 'two-div-id', + mediaTypes: { + banner: { sizes: [300, 250] } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 12345370, + } + }] + }]; + +const sampleOutputData = [1, 2, 3] + +describe('goldfishAdsRtdProvider is a RTD provider that', function () { + describe('has a method `init` that', function () { + it('exists', function () { + expect(goldfishAdsSubModule.init).to.be.a('function'); + }); + it('returns false missing config params', function () { + const config = { + name: 'goldfishAds', + waitForIt: true, + }; + const value = goldfishAdsSubModule.init(config); + expect(value).to.equal(false); + }); + it('returns false if missing providers param', function () { + const config = { + name: 'goldfishAds', + waitForIt: true, + params: {} + }; + const value = goldfishAdsSubModule.init(config); + expect(value).to.equal(false); + }); + it('returns false if wrong providers param included', function () { + const config = { + name: 'goldfishAds', + waitForIt: true, + params: { + account: 'test' + } + }; + const value = goldfishAdsSubModule.init(config); + expect(value).to.equal(false); + }); + it('returns true if good providers param included', function () { + const config = { + name: 'goldfishAds', + waitForIt: true, + params: { + key: 'testkey' + } + }; + const value = goldfishAdsSubModule.init(config); + expect(value).to.equal(true); + }); + }); + + describe('has a method `getBidRequestData` that', function () { + it('exists', function () { + expect(goldfishAdsSubModule.getBidRequestData).to.be.a('function'); + }); + + it('send correct request', function () { + const callback = sinon.spy(); + let request; + const reqBidsConfigObj = { adUnits: sampleAdUnits }; + goldfishAdsSubModule.getBidRequestData(reqBidsConfigObj, callback, sampleConfig); + request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify(sampleOutputData)); + expect(request.url).to.be.include(`?key=testkey`); + }); + }); + + describe('has a manageCallbackResponse that', function () { + it('properly transforms the response', function () { + const response = { response: '[\"1\", \"2\", \"3\"]' }; + const output = manageCallbackResponse(response); + expect(output.name).to.be.equal('goldfishads.com'); + }); + }); + + describe('has an updateUserData that', function () { + it('properly transforms the response', function () { + const userData = { + segment: [{id: '1'}, {id: '2'}], + ext: { + segtax: 4, + } + }; + const reqBidsConfigObj = { ortb2Fragments: { bidder: { appnexus: { user: { data: [] } } } } }; + const output = updateUserData(userData, reqBidsConfigObj); + expect(output.ortb2Fragments.bidder.appnexus.user.data[0].segment).to.be.length(2); + expect(output.ortb2Fragments.bidder.appnexus.user.data[0].segment[0].id).to.be.eql('1'); + }); + }); + + describe('uses Local Storage to ', function () { + const sandbox = sinon.createSandbox(); + const storage = getStorageManager({ moduleType: MODULE_TYPE, moduleName: MODULE_NAME }) + beforeEach(() => { + storage.setDataInLocalStorage(DATA_STORAGE_KEY, JSON.stringify({ + targeting: { + name: 'goldfishads.com', + segment: [{id: '1'}, {id: '2'}], + ext: { + segtax: 4, + } + }, + expiry: new Date().getTime() + 1000 * 60 * 60 * 24 * 30, + })); + }); + afterEach(() => { + sandbox.restore(); + }); + it('get data from local storage', function () { + const output = getStorageData(); + expect(output.name).to.be.equal('goldfishads.com'); + expect(output.segment).to.be.length(2); + expect(output.ext.segtax).to.be.equal(4); + }); + }); +}); From 15b523f0a892032c509b83c81abe3c612e07249e Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:47:29 +0100 Subject: [PATCH 163/760] ZetaGlobalSsp Analytics Adapter : make event object with only needed fields (#10555) * ZetaGlobalSspAnalyticsAdapter: make new event object with only needed fields * add missing fields * add missing fields (2) * unit test * unit test 2 * unit test 3 * tests 4 * fix checkstyle --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 52 ++++++++++++++++--- .../zeta_global_sspAnalyticsAdapter_spec.js | 27 ++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 3d5466dd906..751b55ec673 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -51,28 +51,64 @@ function adRenderSucceededHandler(args) { let eventType = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + const event = { + adId: args.adId, + bid: { + adId: args.bid?.adId, + auctionId: args.bid?.auctionId, + adUnitCode: args.bid?.adUnitCode, + bidId: args.bid?.bidId, + requestId: args.bid?.requestId, + bidderCode: args.bid?.bidderCode, + mediaTypes: args.bid?.mediaTypes, + sizes: args.bid?.sizes, + adserverTargeting: args.bid?.adserverTargeting, + cpm: args.bid?.cpm, + creativeId: args.bid?.creativeId, + mediaType: args.bid?.mediaType, + renderer: args.bid?.renderer, + size: args.bid?.size, + timeToRespond: args.bid?.timeToRespond, + params: args.bid?.params + }, + doc: { + location: args.doc?.location + } + } + // set zetaParams from cache - if (args.bid && args.bid.auctionId) { - const zetaParams = cache.auctions[args.bid.auctionId]; + if (event.bid && event.bid.auctionId) { + const zetaParams = cache.auctions[event.bid.auctionId]; if (zetaParams) { - args.bid.params = [ zetaParams ]; + event.bid.params = [ zetaParams ]; } } - sendEvent(eventType, args); + sendEvent(eventType, event); } function auctionEndHandler(args) { let eventType = CONSTANTS.EVENTS.AUCTION_END; logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); + const event = { + adUnitCodes: args.adUnitCodes, + adUnits: args.adUnits, + auctionEnd: args.auctionEnd, + auctionId: args.auctionId, + bidderRequests: args.bidderRequests, + bidsReceived: args.bidsReceived, + noBids: args.noBids, + winningBids: args.winningBids + } + // save zetaParams to cache - const zetaParams = getZetaParams(args); - if (zetaParams && args.auctionId) { - cache.auctions[args.auctionId] = zetaParams; + const zetaParams = getZetaParams(event); + if (zetaParams && event.auctionId) { + cache.auctions[event.auctionId] = zetaParams; } - sendEvent(eventType, args); + sendEvent(eventType, event); } /// /////////// ADAPTER DEFINITION /////////////////////////// diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 0796736a162..5194a6a526a 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -398,5 +398,32 @@ describe('Zeta Global SSP Analytics Adapter', function() { expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(EVENTS.AUCTION_END.adUnits[0].bids[0].params); expect(EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); }); + + it('Keep only needed fields', function() { + this.timeout(3000); + + events.emit(CONSTANTS.EVENTS.AUCTION_END, EVENTS.AUCTION_END); + events.emit(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, EVENTS.AD_RENDER_SUCCEEDED); + + expect(requests.length).to.equal(2); + const auctionEnd = JSON.parse(requests[0].requestBody); + const auctionSucceeded = JSON.parse(requests[1].requestBody); + + expect(auctionEnd.adUnitCodes[0]).to.be.equal('/19968336/header-bid-tag-0'); + expect(auctionEnd.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); + expect(auctionEnd.auctionEnd).to.be.equal(1638441234784); + expect(auctionEnd.auctionId).to.be.equal('75e394d9-ccce-4978-9238-91e6a1ac88a1'); + expect(auctionEnd.bidderRequests[0].bidderCode).to.be.equal('zeta_global_ssp'); + expect(auctionEnd.bidsReceived[0].bidderCode).to.be.equal('zeta_global_ssp'); + expect(auctionEnd.noBids[0].bidder).to.be.equal('appnexus'); + + expect(auctionSucceeded.adId).to.be.equal('5759bb3ef7be1e8'); + expect(auctionSucceeded.bid.auctionId).to.be.equal('75e394d9-ccce-4978-9238-91e6a1ac88a1'); + expect(auctionSucceeded.bid.requestId).to.be.equal('206be9a13236af'); + expect(auctionSucceeded.bid.bidderCode).to.be.equal('zeta_global_ssp'); + expect(auctionSucceeded.bid.creativeId).to.be.equal('456456456'); + expect(auctionSucceeded.bid.size).to.be.equal('480x320'); + expect(auctionSucceeded.doc.location.hostname).to.be.equal('localhost'); + }); }); }); From 5b82e3b864faae03d7e4dc06178f8f98a1cbac3d Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:09:40 -0800 Subject: [PATCH 164/760] GumGum Bid Adapter: add support for video.plcmt (#10674) * Add support for video.plcmt * Add unit test. --- modules/gumgumBidAdapter.js | 8 +++++++- test/spec/modules/gumgumBidAdapter_spec.js | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index e14dc7433dc..83fd726fde5 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -176,6 +176,7 @@ function _getVidParams(attributes) { linearity: li, startdelay: sd, placement: pt, + plcmt, protocols = [], playerSize = [] } = attributes; @@ -187,7 +188,7 @@ function _getVidParams(attributes) { pr = protocols.join(','); } - return { + const result = { mind, maxd, li, @@ -197,6 +198,11 @@ function _getVidParams(attributes) { viw, vih }; + // Add vplcmt property to the result object if plcmt is available + if (plcmt !== undefined && plcmt !== null) { + result.vplcmt = plcmt; + } + return result; } /** diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 56c89d329dc..f541a69abe8 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -181,6 +181,7 @@ describe('gumgumAdapter', function () { linearity: 1, startdelay: 1, placement: 123456, + plcmt: 3, protocols: [1, 2] } }; @@ -457,6 +458,7 @@ describe('gumgumAdapter', function () { linearity: 1, startdelay: 1, placement: 123456, + plcmt: 3, protocols: [1, 2] }; const request = Object.assign({}, bidRequests[0]); @@ -475,6 +477,7 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.li).to.eq(videoVals.linearity); expect(bidRequest.data.sd).to.eq(videoVals.startdelay); expect(bidRequest.data.pt).to.eq(videoVals.placement); + expect(bidRequest.data.vplcmt).to.eq(videoVals.plcmt); expect(bidRequest.data.pr).to.eq(videoVals.protocols.join(',')); expect(bidRequest.data.viw).to.eq(videoVals.playerSize[0].toString()); expect(bidRequest.data.vih).to.eq(videoVals.playerSize[1].toString()); @@ -488,6 +491,7 @@ describe('gumgumAdapter', function () { linearity: 1, startdelay: 1, placement: 123456, + plcmt: 3, protocols: [1, 2] }; const request = Object.assign({}, bidRequests[0]); @@ -506,6 +510,7 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.li).to.eq(inVideoVals.linearity); expect(bidRequest.data.sd).to.eq(inVideoVals.startdelay); expect(bidRequest.data.pt).to.eq(inVideoVals.placement); + expect(bidRequest.data.vplcmt).to.eq(inVideoVals.plcmt); expect(bidRequest.data.pr).to.eq(inVideoVals.protocols.join(',')); expect(bidRequest.data.viw).to.eq(inVideoVals.playerSize[0].toString()); expect(bidRequest.data.vih).to.eq(inVideoVals.playerSize[1].toString()); From 680f9240d193050a29779dc61d038bded6f07f74 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:53:27 -0700 Subject: [PATCH 165/760] Rubicon Bid Adapter: PAAPI/Fledge support (#10671) * Rubicon adapter support for PAAPI/Fledge * fix lint * add unit tests * Fix linting * incorporate review feedback * revert return change * fix test --------- Co-authored-by: Chris Huie --- modules/rubiconBidAdapter.js | 20 +++++++-- test/spec/modules/rubiconBidAdapter_spec.js | 46 +++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 66dbd51f050..6cce22b903e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -408,6 +408,7 @@ export const spec = { 'x_source.tid', 'l_pb_bid_id', 'p_screen_res', + 'o_ae', 'rp_floor', 'rp_secure', 'tk_user_key' @@ -520,6 +521,10 @@ export const spec = { if (configUserId) { data['ppuid'] = configUserId; } + + if (bidRequest?.ortb2Imp?.ext?.ae) { + data['o_ae'] = 1; + } // loop through userIds and add to request if (bidRequest.userIdAsEids) { bidRequest.userIdAsEids.forEach(eid => { @@ -619,7 +624,7 @@ export const spec = { * @param {*} responseObj * @param {BidRequest|Object.} request - if request was SRA the bidRequest argument will be a keyed BidRequest array object, * non-SRA responses return a plain BidRequest object - * @return {Bid[]} An array of bids which + * @return {{fledgeAuctionConfigs: *, bids: *}} An array of bids which */ interpretResponse: function (responseObj, request) { responseObj = responseObj.body; @@ -629,7 +634,6 @@ export const spec = { if (!responseObj || typeof responseObj !== 'object') { return []; } - // Response from PBS Java openRTB if (responseObj.seatbid) { const responseErrors = deepAccess(responseObj, 'ext.errors.rubicon'); @@ -655,7 +659,7 @@ export const spec = { return []; } - return ads.reduce((bids, ad, i) => { + let bids = ads.reduce((bids, ad, i) => { (ad.impression_id && lastImpId === ad.impression_id) ? multibid++ : lastImpId = ad.impression_id; if (ad.status !== 'ok') { @@ -716,6 +720,16 @@ export const spec = { }, []).sort((adA, adB) => { return (adB.cpm || 0.0) - (adA.cpm || 0.0); }); + + let fledgeAuctionConfigs = responseObj.component_auction_config?.map(config => { + return { config, bidId: config.bidId } + }); + + if (fledgeAuctionConfigs) { + return { bids, fledgeAuctionConfigs }; + } else { + return bids; + } }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index dd6f52c0646..9b37fe36c77 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2589,6 +2589,15 @@ describe('the rubicon adapter', function () { const slotParams = spec.createSlotParams(bidderRequest.bids[0], bidderRequest); expect(slotParams.kw).to.equal('a,b,c'); }); + + it('should pass along o_ae param when fledge is enabled', () => { + const localBidRequest = Object.assign({}, bidderRequest.bids[0]); + localBidRequest.ortb2Imp.ext.ae = true; + + const slotParams = spec.createSlotParams(localBidRequest, bidderRequest); + + expect(slotParams['o_ae']).to.equal(1) + }); }); describe('classifiedAsVideo', function () { @@ -3309,6 +3318,43 @@ describe('the rubicon adapter', function () { expect(bids).to.be.lengthOf(0); }); + it('Should support recieving an auctionConfig and pass it along to Prebid', function () { + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [{ + 'status': 'ok', + 'cpm': 0, + 'size_id': 15 + }], + 'component_auction_config': [{ + 'random': 'value', + 'bidId': '5432' + }, + { + 'random': 'string', + 'bidId': '6789' + }] + }; + + let {bids, fledgeAuctionConfigs} = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + + expect(bids).to.be.lengthOf(1); + expect(fledgeAuctionConfigs[0].bidId).to.equal('5432'); + expect(fledgeAuctionConfigs[0].config.random).to.equal('value'); + expect(fledgeAuctionConfigs[1].bidId).to.equal('6789'); + }); + it('should handle an error', function () { let response = { 'status': 'ok', From 959589916c7e92709ca058ee38777bcf8118a966 Mon Sep 17 00:00:00 2001 From: pangle-fe <149553960+pangle-fe@users.noreply.github.com> Date: Thu, 9 Nov 2023 01:20:22 +0800 Subject: [PATCH 166/760] feat: pangle support video (#10682) --- modules/pangleBidAdapter.js | 135 +++++++++++++++------ test/spec/modules/pangleBidAdapter_spec.js | 108 +++++++++++++++++ 2 files changed, 203 insertions(+), 40 deletions(-) diff --git a/modules/pangleBidAdapter.js b/modules/pangleBidAdapter.js index 408a8b24c29..e75a96048b4 100644 --- a/modules/pangleBidAdapter.js +++ b/modules/pangleBidAdapter.js @@ -1,14 +1,18 @@ -// ver V1.0.3 -import { BANNER } from '../src/mediaTypes.js'; +// 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'; -import { deepSetValue, generateUUID, timestamp } from '../src/utils.js'; +import { deepSetValue, generateUUID, timestamp, deepAccess } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { Renderer } from '../src/Renderer.js'; + const BIDDER_CODE = 'pangle'; const ENDPOINT = 'https://pangle.pangleglobal.com/api/ad/union/web_js/common/get_ads'; +const OUTSTREAM_RENDERER_URL = 'https://sf16-static.i18n-pglstatp.com/obj/ad-pattern-sg/pangle/web/ads/video.js'; + const DEFAULT_BID_TTL = 30; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; @@ -25,9 +29,7 @@ export function isValidUuid(uuid) { function getPangleCookieId() { let sid = storage.cookiesAreEnabled() && storage.getCookie(PANGLE_COOKIE); - if ( - !sid || !isValidUuid(sid) - ) { + if (!sid || !isValidUuid(sid)) { sid = generateUUID(); setPangleCookieId(sid); } @@ -37,30 +39,99 @@ function getPangleCookieId() { function setPangleCookieId(sid) { if (storage.cookiesAreEnabled()) { - const expires = (new Date(timestamp() + COOKIE_EXP)).toGMTString(); + const expires = new Date(timestamp() + COOKIE_EXP).toGMTString(); storage.setCookie(PANGLE_COOKIE, sid, expires); } } +function createRequest(bidRequests, bidderRequest, mediaType) { + const data = converter.toORTB({ + bidRequests, + bidderRequest, + context: { mediaType }, + }); + const devicetype = spec.getDeviceType(navigator.userAgent); + deepSetValue(data, 'device.devicetype', devicetype); + if (bidderRequest.userId && typeof bidderRequest.userId === 'object') { + const pangleId = getPangleCookieId(); + // add pangle cookie + const _eids = data.user?.ext?.eids ?? []; + deepSetValue(data, 'user.ext.eids', [ + ..._eids, + { + source: document.location.host, + uids: [ + { + id: pangleId, + atype: 1, + }, + ], + }, + ]); + } + bidRequests.forEach((item, idx) => { + deepSetValue(data.imp[idx], 'ext.networkids', item.params); + deepSetValue(data.imp[idx], 'banner.api', [5]); + deepSetValue(data, 'test', item.params.test ?? 0) + }); + return { + method: 'POST', + url: ENDPOINT, + data, + options: { contentType: 'application/json', withCredentials: true } + } +} + +function isVideoBid(bid) { + return !!deepAccess(bid, 'mediaTypes.video'); +} + +function isBannerBid(bid) { + return !!deepAccess(bid, 'mediaTypes.banner'); +} + +function renderOutstream(bid) { + bid.renderer.push(() => { + window.outstreamPlayer({ bid, codeId: bid.adUnitCode }); + }); +} + const converter = ortbConverter({ context: { netRevenue: DEFAULT_NET_REVENUE, ttl: DEFAULT_BID_TTL, currency: DEFAULT_CURRENCY, - mediaType: BANNER - } + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + const { bidRequest } = context; + if (bidRequest.mediaTypes.video?.context === 'outstream') { + const renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode}); + renderer.setRender(renderOutstream); + bidResponse.renderer = renderer; + } + return bidResponse; + }, }); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], getDeviceType: function (ua) { - if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(ua.toLowerCase()))) { + if ( + /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( + ua.toLowerCase() + ) + ) { return 5; // 'tablet' } - if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(ua.toLowerCase()))) { + if ( + /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( + ua.toLowerCase() + ) + ) { return 4; // 'mobile' } return 2; // 'desktop' @@ -71,38 +142,22 @@ export const spec = { }, buildRequests(bidRequests, bidderRequest) { - const data = converter.toORTB({ bidRequests, bidderRequest }) - const devicetype = spec.getDeviceType(navigator.userAgent); - deepSetValue(data, 'device.devicetype', devicetype); - if (bidderRequest.userId && typeof bidderRequest.userId === 'object') { - const pangleId = getPangleCookieId(); - // add pangle cookie - const _eids = data.user?.ext?.eids ?? [] - deepSetValue(data, 'user.ext.eids', [..._eids, { - source: document.location.host, - uids: [ - { - id: pangleId, - atype: 1 - } - ] - }]); - } - bidRequests.forEach((item, idx) => { - deepSetValue(data.imp[idx], 'ext.networkids', item.params); - deepSetValue(data.imp[idx], 'banner.api', [5]); + const videoBids = bidRequests.filter((bid) => isVideoBid(bid)); + const bannerBids = bidRequests.filter((bid) => isBannerBid(bid)); + let requests = bannerBids.length + ? [createRequest(bannerBids, bidderRequest, BANNER)] + : []; + videoBids.forEach((bid) => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); }); - - return [{ - method: 'POST', - url: ENDPOINT, - data, - options: { contentType: 'application/json', withCredentials: true } - }] + return requests; }, interpretResponse(response, request) { - const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + const bids = converter.fromORTB({ + response: response.body, + request: request.data, + }).bids; return bids; }, }; diff --git a/test/spec/modules/pangleBidAdapter_spec.js b/test/spec/modules/pangleBidAdapter_spec.js index 79cbc30b4ec..94f5a6e4b1d 100644 --- a/test/spec/modules/pangleBidAdapter_spec.js +++ b/test/spec/modules/pangleBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/pangleBidAdapter.js'; +import { logInfo } from '../../../src/utils'; const REQUEST = [{ adUnitCode: 'adUnitCode1', @@ -45,6 +46,7 @@ const REQUEST = [{ appid: 111, }, }]; + const DEFAULT_OPTIONS = { userId: { britepoolid: 'pangle-britepool', @@ -184,4 +186,110 @@ describe('pangle bid adapter', function () { expect(deviceType).to.equal(2); }); }); + + // describe('video', function () { + // it('video config', function() { + // logInfo(spec.buildRequests(VIDEO_REQUEST, DEFAULT_OPTIONS)[0].data, 'spec.buildRequests(videoConfig, DEFAULT_OPTIONS)[0].data.imp[0]'); + // const request = spec.buildRequests(VIDEO_REQUEST, DEFAULT_OPTIONS)[0]; + + // expect(request).to.exist.and.to.be.a('object'); + // const payload = request.data; + // expect(payload).to.exist.and.to.be.a('object'); + // const video = payload.imp[0].video; + // expect(video).to.exist.and.to.be.a('object'); + // // console.log(video, 'video???') + // // expect(url).to.equal('https://pangle.pangleglobal.com/api/ad/union/web_js/common/get_ads'); + // // assert.deepEqual(video, { + // // h: 380, + // // mimes: ['video/mp4'], + // // w: 240 + // // }) + // }) + // }) +}); + +describe('Pangle Adapter with video', function() { + const videoBidRequest = [ + { + bidId: '2820132fe18114', + mediaTypes: { video: { context: 'outstream', playerSize: [[300, 250]] } }, + params: { token: 'test-token' } + } + ]; + const bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + const serverResponse = { + 'headers': null, + 'body': { + 'id': '233f1693-68d1-470a-ad85-c156c3faaf6f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2820132fe18114', + 'impid': '2820132fe18114', + 'price': 0.03294, + 'nurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/win/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&win_price=${AUCTION_PRICE}&auction_mwb=${AUCTION_BID_TO_WIN}&use_pb=1', + 'lurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/loss/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&reason=${AUCTION_LOSS}&ad_slot_type=8&auction_mwb=${AUCTION_PRICE}&use_pb=1', + 'adm': '', + 'adid': '1780626232977441', + 'adomain': [ + 'swi.esxcmnb.com' + ], + 'iurl': 'https://p16-ttam-va.ibyteimg.com/origin/ad-site-i18n-sg/202310245d0d598b3ff5993c4f129a8b', + 'cid': '1780626232977441', + 'crid': '1780626232977441', + 'attr': [ + 4 + ], + 'w': 640, + 'h': 640, + 'ext': { + 'pangle': { + 'adtype': 8 + }, + 'event_notification_token': { + 'payload': '980589944:8:1450:7492' + } + } + } + ], + 'seat': 'pangle' + } + ] + } + }; + + describe('Video: buildRequests', function() { + it('should create a POST request for video bid', function() { + const requests = spec.buildRequests(videoBidRequest, bidderRequest); + expect(requests[0].method).to.equal('POST'); + }); + + it('should have a valid URL and payload for an out-stream video bid', function () { + const requests = spec.buildRequests(videoBidRequest, bidderRequest); + expect(requests[0].url).to.equal('https://pangle.pangleglobal.com/api/ad/union/web_js/common/get_ads'); + expect(requests[0].data).to.exist; + }); + }); + + describe('interpretResponse: Video', function () { + it('should get correct bid response', function () { + const request = spec.buildRequests(videoBidRequest, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(serverResponse, request); + expect(interpretedResponse).to.be.an('array'); + const bid = interpretedResponse[0]; + expect(bid).to.exist; + expect(bid.requestId).to.exist; + expect(bid.cpm).to.be.above(0); + expect(bid.ttl).to.exist; + expect(bid.creativeId).to.exist; + if (bid.renderer) { + expect(bid.renderer.render).to.exist; + } + }); + }); }); From ea5bc2c9f9796e94be3243bcee87bf593c47c5a6 Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Wed, 8 Nov 2023 10:21:07 -0700 Subject: [PATCH 167/760] Rubicon Bid Adapter : send Fastlane cdep flag (#10703) * Initial change * Add cdep unit test --- modules/rubiconBidAdapter.js | 2 ++ test/spec/modules/rubiconBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 6cce22b903e..de6ddf0618c 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -409,6 +409,7 @@ export const spec = { 'l_pb_bid_id', 'p_screen_res', 'o_ae', + 'o_cdep', 'rp_floor', 'rp_secure', 'tk_user_key' @@ -482,6 +483,7 @@ export const spec = { 'x_source.tid': bidderRequest.ortb2?.source?.tid, 'x_imp.ext.tid': bidRequest.ortb2Imp?.ext?.tid, 'l_pb_bid_id': bidRequest.bidId, + 'o_cdep': bidRequest.ortb2?.device?.ext?.cdep, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 9b37fe36c77..c0bc35d161e 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -696,6 +696,16 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); + it('should correctly send cdep signal when requested', () => { + var badposRequest = utils.deepClone(bidderRequest); + badposRequest.bids[0].ortb2 = {device: {ext: {cdep: 3}}}; + + let [request] = spec.buildRequests(badposRequest.bids, badposRequest); + let data = parseQuery(request.data); + + expect(data['o_cdep']).to.equal('3'); + }); + it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); From 9bfd68841bc46aebcf71867b796a1df924448856 Mon Sep 17 00:00:00 2001 From: Mark Kuhar Date: Thu, 9 Nov 2023 14:47:16 +0100 Subject: [PATCH 168/760] Outbrain Bid Adapter - add gpp consent to bid request and User sync calls (#10700) --- modules/outbrainBidAdapter.js | 13 ++++++++- test/spec/modules/outbrainBidAdapter_spec.js | 30 +++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index b4f74872082..6015ff37e08 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -149,6 +149,13 @@ export const spec = { if (config.getConfig('coppa') === true) { deepSetValue(request, 'regs.coppa', config.getConfig('coppa') & 1) } + if (bidderRequest.gppConsent) { + deepSetValue(request, 'regs.ext.gpp', bidderRequest.gppConsent.gppString) + deepSetValue(request, 'regs.ext.gpp_sid', bidderRequest.gppConsent.applicableSections) + } else if (deepAccess(bidderRequest, 'ortb2.regs.gpp')) { + deepSetValue(request, 'regs.ext.gpp', bidderRequest.ortb2.regs.gpp) + deepSetValue(request, 'regs.ext.gpp_sid', bidderRequest.ortb2.regs.gpp_sid) + } if (eids) { deepSetValue(request, 'user.ext.eids', eids); @@ -212,7 +219,7 @@ export const spec = { } }).filter(Boolean); }, - getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { const syncs = []; let syncUrl = config.getConfig('outbrain.usersyncUrl'); @@ -225,6 +232,10 @@ export const spec = { if (uspConsent) { query.push('us_privacy=' + encodeURIComponent(uspConsent)); } + if (gppConsent) { + query.push('gpp=' + encodeURIComponent(gppConsent.gppString)); + query.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); + } syncs.push({ type: 'image', diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index ba394a68675..e6abb5e9caa 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -427,7 +427,7 @@ describe('Outbrain Adapter', function () { expect(resData.badv).to.deep.equal(['bad-advertiser']) }); - it('first party data', function () { + it('should pass first party data', function () { const bidRequest = { ...commonBidRequest, ...nativeBidRequestParams, @@ -508,6 +508,28 @@ describe('Outbrain Adapter', function () { config.resetConfig() }); + it('should pass gpp information', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + }; + const bidderRequest = { + ...commonBidderRequest, + 'gppConsent': { + 'gppString': 'abc12345', + 'applicableSections': [8] + } + } + + const res = spec.buildRequests([bidRequest], bidderRequest); + const resData = JSON.parse(res.data); + + expect(resData.regs.ext.gpp).to.exist; + expect(resData.regs.ext.gpp_sid).to.exist; + expect(resData.regs.ext.gpp).to.equal('abc12345'); + expect(resData.regs.ext.gpp_sid).to.deep.equal([8]); + }); + it('should pass extended ids', function () { let bidRequest = { bidId: 'bidId', @@ -861,6 +883,12 @@ describe('Outbrain Adapter', function () { type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); + + it('should pass gpp consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '', { gppString: 'abc12345', applicableSections: [1, 2] })).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?gpp=abc12345&gpp_sid=1%2C2` + }]); + }); }) describe('onBidWon', function () { From 51ca28c4cb4c1ec72152497e2320e110dd27aed6 Mon Sep 17 00:00:00 2001 From: kampungkat <139214234+kampungkat@users.noreply.github.com> Date: Thu, 9 Nov 2023 21:47:55 +0800 Subject: [PATCH 169/760] Appnexus Bid Adapter - Adding 'adzymic' as a new alias for Appnexus (#10639) --- modules/appnexusBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index e6b3441b988..c6230d9f1e4 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { { code: 'beintoo', gvlid: 618 }, { code: 'projectagora', gvlid: 1032 }, { code: 'uol', gvlid: 32 }, + { code: 'adzymic', gvlid: 32 }, ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From a3c64d4d993db6ad69d8e01ab3d1f507f7ba6bb2 Mon Sep 17 00:00:00 2001 From: Jason Piros Date: Thu, 9 Nov 2023 09:21:44 -0700 Subject: [PATCH 170/760] Consumable Bid Adapter: fix us privacy format and make gpp applicable sections optional (#10707) * consumableBidAdapter: fix usp and gpp handling * make gpp applicable sections optional --- modules/consumableBidAdapter.js | 13 ++-- .../spec/modules/consumableBidAdapter_spec.js | 63 +++++++------------ 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index c78ff7cdf51..696549a67dc 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -191,17 +191,20 @@ export const spec = { if (syncOptions.iframeEnabled) { if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`); + syncUrl = appendUrlParam(syncUrl, `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${encodeURIComponent(gdprConsent.consentString) || ''}`); } else { - syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${gdprConsent.consentString}`); + syncUrl = appendUrlParam(syncUrl, `gdpr=0&gdpr_consent=${encodeURIComponent(gdprConsent.consentString) || ''}`); } } if (gppConsent && gppConsent.gppString) { - syncUrl = appendUrlParam(syncUrl, `gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}`); + syncUrl = appendUrlParam(syncUrl, `gpp=${encodeURIComponent(gppConsent.gppString)}`); + if (gppConsent.applicableSections && gppConsent.applicableSections.length > 0) { + syncUrl = appendUrlParam(syncUrl, `gpp_sid=${encodeURIComponent(gppConsent.applicableSections.join(','))}`); + } } - if (uspConsent && uspConsent.consentString) { - syncUrl = appendUrlParam(syncUrl, `us_privacy=${uspConsent.consentString}`); + if (uspConsent) { + syncUrl = appendUrlParam(syncUrl, `us_privacy=${encodeURIComponent(uspConsent)}`); } if (!serverResponses || serverResponses.length === 0 || !serverResponses[0].body.bdr || serverResponses[0].body.bdr !== 'cx') { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index deeb8f7100d..d8e75454245 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -651,21 +651,30 @@ describe('Consumable BidAdapter', function () { expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gdpr=0&gdpr_consent=GDPR_CONSENT_STRING'); }) - it('should return a sync url if iframe syncs are enabled and GPP applies', function () { + it('should return a sync url if iframe syncs are enabled and has GPP consent with applicable sections', function () { let gppConsent = { applicableSections: [1, 2], gppString: 'GPP_CONSENT_STRING' } - let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, {}, gppConsent); + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, '', gppConsent); expect(opts.length).to.equal(1); - expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gpp=GPP_CONSENT_STRING&gpp_sid=1,2'); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gpp=GPP_CONSENT_STRING&gpp_sid=1%2C2'); }) - it('should return a sync url if iframe syncs are enabled and USP applies', function () { - let uspConsent = { - consentString: 'USP_CONSENT_STRING', + it('should return a sync url if iframe syncs are enabled and has GPP consent without applicable sections', function () { + let gppConsent = { + applicableSections: [], + gppString: 'GPP_CONSENT_STRING' } + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, '', gppConsent); + + expect(opts.length).to.equal(1); + expect(opts[0].url).to.equal('https://sync.serverbid.com/ss/730181.html?gpp=GPP_CONSENT_STRING'); + }) + + it('should return a sync url if iframe syncs are enabled and USP applies', function () { + let uspConsent = 'USP_CONSENT_STRING'; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], {}, uspConsent); expect(opts.length).to.equal(1); @@ -677,9 +686,7 @@ describe('Consumable BidAdapter', function () { consentString: 'GDPR_CONSENT_STRING', gdprApplies: true, } - let uspConsent = { - consentString: 'USP_CONSENT_STRING', - } + let uspConsent = 'USP_CONSENT_STRING'; let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE], gdprConsent, uspConsent); expect(opts.length).to.equal(1); @@ -704,50 +711,22 @@ describe('Consumable BidAdapter', function () { sandbox.restore(); }); - it('Request should have unifiedId config params', function() { + it('Request should have EIDs', function() { bidderRequest.bidRequest[0].userId = {}; bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; - bidderRequest.bidRequest[0].userIdAsEids = createEidsArray(bidderRequest.bidRequest[0].userId); - let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); - let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ + bidderRequest.bidRequest[0].userIdAsEids = [{ 'source': 'adserver.org', 'uids': [{ - 'id': 'TTD_ID', + 'id': 'TTD_ID_FROM_USER_ID_MODULE', 'atype': 1, 'ext': { 'rtiPartner': 'TDID' } }] - }]); - }); - - it('Request should have adsrvrOrgId from UserId Module if config and userId module both have TTD ID', function() { - sandbox.stub(config, 'getConfig').callsFake((key) => { - var config = { - adsrvrOrgId: { - 'TDID': 'TTD_ID_FROM_CONFIG', - 'TDID_LOOKUP': 'TRUE', - 'TDID_CREATED_AT': '2022-06-21T09:47:00' - } - }; - return config[key]; - }); - bidderRequest.bidRequest[0].userId = {}; - bidderRequest.bidRequest[0].userId.tdid = 'TTD_ID'; - bidderRequest.bidRequest[0].userIdAsEids = createEidsArray(bidderRequest.bidRequest[0].userId); + }]; let request = spec.buildRequests(bidderRequest.bidRequest, BIDDER_REQUEST_1); let data = JSON.parse(request.data); - expect(data.user.eids).to.deep.equal([{ - 'source': 'adserver.org', - 'uids': [{ - 'id': 'TTD_ID', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - }] - }]); + expect(data.user.eids).to.deep.equal(bidderRequest.bidRequest[0].userIdAsEids); }); it('Request should NOT have adsrvrOrgId params if userId is NOT object', function() { From b3895811bdb2b9310d19561fb27a52f24090a128 Mon Sep 17 00:00:00 2001 From: Abdullah Al Mamun Oronno Date: Thu, 9 Nov 2023 11:32:09 -0500 Subject: [PATCH 171/760] IX Bid Adapter: implement support for exchangeId and externalId [PB-2050] (#10704) --- modules/ixBidAdapter.js | 107 +++++++++++++++++++------ test/spec/modules/ixBidAdapter_spec.js | 78 ++++++++++++++++++ 2 files changed, 161 insertions(+), 24 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 810fa744b2e..41e0d8e97c5 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -238,7 +238,10 @@ export function bidToVideoImp(bid) { imp.video = videoParamRef ? deepClone(bid.params.video) : {}; // populate imp level transactionId - imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + let tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + if (tid) { + deepSetValue(imp, 'ext.tid', tid); + } setDisplayManager(imp, bid); @@ -328,7 +331,10 @@ export function bidToNativeImp(bid) { }; // populate imp level transactionId - imp.ext.tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + let tid = deepAccess(bid, 'ortb2Imp.ext.tid'); + if (tid) { + deepSetValue(imp, 'ext.tid', tid); + } // AdUnit-Specific First Party Data addAdUnitFPD(imp, bid) @@ -348,26 +354,30 @@ function bidToImp(bid, mediaType) { imp.id = bid.bidId; - imp.ext = {}; + if (isExchangeIdConfigured() && deepAccess(bid, `params.externalId`)) { + deepSetValue(imp, 'ext.externalID', bid.params.externalId); + } if (deepAccess(bid, `params.${mediaType}.siteId`) && !isNaN(Number(bid.params[mediaType].siteId))) { switch (mediaType) { case BANNER: - imp.ext.siteID = bid.params.banner.siteId.toString(); + deepSetValue(imp, 'ext.siteID', bid.params.banner.siteId.toString()); break; case VIDEO: - imp.ext.siteID = bid.params.video.siteId.toString(); + deepSetValue(imp, 'ext.siteID', bid.params.video.siteId.toString()); break; case NATIVE: - imp.ext.siteID = bid.params.native.siteId.toString(); + deepSetValue(imp, 'ext.siteID', bid.params.native.siteId.toString()); break; } } else { - imp.ext.siteID = bid.params.siteId.toString(); + if (bid.params.siteId) { + deepSetValue(imp, 'ext.siteID', bid.params.siteId.toString()); + } } // populate imp level sid if (bid.params.hasOwnProperty('id') && (typeof bid.params.id === 'string' || typeof bid.params.id === 'number')) { - imp.ext.sid = String(bid.params.id); + deepSetValue(imp, 'ext.sid', String(bid.params.id)); } return imp; @@ -413,12 +423,12 @@ function _applyFloor(bid, imp, mediaType) { if (moduleFloor) { imp.bidfloor = moduleFloor.floor; imp.bidfloorcur = moduleFloor.currency; - imp.ext.fl = FLOOR_SOURCE.PBJS; + deepSetValue(imp, 'ext.fl', FLOOR_SOURCE.PBJS); setFloor = true; } else if (adapterFloor) { imp.bidfloor = adapterFloor.floor; imp.bidfloorcur = adapterFloor.currency; - imp.ext.fl = FLOOR_SOURCE.IX; + deepSetValue(imp, 'ext.fl', FLOOR_SOURCE.IX); setFloor = true; } @@ -709,8 +719,10 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = applyRegulations(r, bidderRequest); let payload = {}; - siteID = validBidRequests[0].params.siteId; - payload.s = siteID; + if (validBidRequests[0].params.siteId) { + siteID = validBidRequests[0].params.siteId; + payload.s = siteID; + } const impKeys = Object.keys(impressions); let isFpdAdded = false; @@ -746,9 +758,19 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = removeSiteIDs(r); if (isLastAdUnit) { + let exchangeUrl = `${baseUrl}?`; + + if (siteID !== 0) { + exchangeUrl += `s=${siteID}`; + } + + if (isExchangeIdConfigured()) { + exchangeUrl += siteID !== 0 ? '&' : ''; + exchangeUrl += `p=${config.getConfig('exchangeId')}`; + } requests.push({ method: 'POST', - url: baseUrl + '?s=' + siteID, + url: exchangeUrl, data: deepClone(r), option: { contentType: 'text/plain', @@ -970,6 +992,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { for (const impId in bannerImpsKeyed) { const bannerImps = bannerImpsKeyed[impId]; const { id, banner: { topframe } } = bannerImps[0]; + let externalID = deepAccess(bannerImps[0], 'ext.externalID'); const _bannerImpression = { id, banner: { @@ -979,15 +1002,24 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { }; for (let i = 0; i < _bannerImpression.banner.format.length; i++) { - // We add sid in imp.ext.sid therefore, remove from banner.format[].ext - if (_bannerImpression.banner.format[i].ext != null && _bannerImpression.banner.format[i].ext.sid != null) { - delete _bannerImpression.banner.format[i].ext.sid; + // We add sid and externalID in imp.ext therefore, remove from banner.format[].ext + if (_bannerImpression.banner.format[i].ext != null) { + if (_bannerImpression.banner.format[i].ext.sid != null) { + delete _bannerImpression.banner.format[i].ext.sid; + } + if (_bannerImpression.banner.format[i].ext.externalID != null) { + delete _bannerImpression.banner.format[i].ext.externalID; + } } // add floor per size if ('bidfloor' in bannerImps[i]) { _bannerImpression.banner.format[i].ext.bidfloor = bannerImps[i].bidfloor; } + + if (JSON.stringify(_bannerImpression.banner.format[i].ext) === '{}') { + delete _bannerImpression.banner.format[i].ext; + } } const position = impressions[impKeys[adUnitIndex]].pos; @@ -995,13 +1027,14 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment) { + if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; _bannerImpression.ext.gpid = gpid; _bannerImpression.ext.tid = tid; _bannerImpression.ext.sid = sid; + _bannerImpression.ext.externalID = externalID; // enable fledge auction if (auctionEnvironment == 1) { @@ -1030,7 +1063,9 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { // Removes imp.ext.bidfloor // Sets imp.ext.siteID to one of the other [video/native].ext.siteid if imp.ext.siteID doesnt exist otherImpressions.forEach(imp => { - deepSetValue(imp, 'ext.gpid', gpid); + if (gpid) { + deepSetValue(imp, 'ext.gpid', gpid); + } if (r.imp.length > 0) { let matchFound = false; r.imp.forEach((rImp, index) => { @@ -1628,6 +1663,17 @@ function isIndexRendererPreferred(bid) { return !isValid || renderer.backupOnly; } +function isExchangeIdConfigured() { + let exchangeId = config.getConfig('exchangeId'); + if (typeof exchangeId === 'number' && isFinite(exchangeId)) { + return true; + } + if (typeof exchangeId === 'string' && exchangeId.trim() !== '' && isFinite(Number(exchangeId))) { + return true; + } + return false; +} + export const spec = { code: BIDDER_CODE, @@ -1685,14 +1731,21 @@ export const spec = { } } - if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { - logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + if (!isExchangeIdConfigured() && bid.params.siteId == undefined) { + logError('IX Bid Adapter: Invalid configuration - either siteId or exchangeId must be configured.'); return false; } - if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { - logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); - return false; + if (bid.params.siteId !== undefined) { + if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { + logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + return false; + } + + if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { + logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + return false; + } } if (hasBidFloor || hasBidFloorCur) { @@ -1725,6 +1778,11 @@ export const spec = { return nativeMediaTypeValid(bid); }, + // For testing only - resets the siteID to 0 so that it can be set again + resetSiteID: function () { + siteID = 0; + }, + /** * Make a server request from the list of BidRequests. * @@ -1964,8 +2022,9 @@ function buildImgSyncUrl(syncsPerBidder, index) { if (gdprConsent && gdprConsent.hasOwnProperty('consentString')) { consentString = gdprConsent.consentString || ''; } + let siteIdParam = siteID !== 0 ? '&site_id=' + siteID.toString() : ''; - return IMG_USER_SYNC_URL + '&site_id=' + siteID.toString() + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || ''); + return IMG_USER_SYNC_URL + siteIdParam + '&p=' + syncsPerBidder.toString() + '&i=' + index.toString() + '&gdpr=' + gdprApplies + '&gdpr_consent=' + consentString + '&us_privacy=' + (usPrivacy || ''); } /** diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 6968eac78fe..500f2239e55 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -3295,6 +3295,84 @@ describe('IndexexchangeAdapter', function () { }); }); + describe('integration through exchangeId and externalId', function () { + const expectedExchangeId = 123456; + // create banner bids with externalId but no siteId as bidder param + const bannerBids = utils.deepClone(DEFAULT_BANNER_VALID_BID); + delete bannerBids[0].params.siteId; + bannerBids[0].params.externalId = 'exteranl_id_1'; + + beforeEach(() => { + config.setConfig({ exchangeId: expectedExchangeId }); + spec.resetSiteID(); + }); + + afterEach(() => { + config.resetConfig(); + }); + + it('when exchangeId and externalId set but no siteId, isBidRequestValid should return true', function () { + const bid = utils.deepClone(bannerBids[0]); + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('when neither exchangeId nor siteId set, isBidRequestValid should return false', function () { + config.resetConfig(); + const bid = utils.deepClone(bannerBids[0]); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('when exchangeId and externalId set with banner impression but no siteId, bidrequest sent to endpoint with p param and externalID inside imp.ext', function () { + const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION); + const payload = extractPayload(requests[0]); + + const expectedURL = IX_SECURE_ENDPOINT + '?p=' + expectedExchangeId; + expect(requests[0].url).to.equal(expectedURL); + expect(payload.imp[0].ext.externalID).to.equal(bannerBids[0].params.externalId); + expect(payload.imp[0].banner.format[0].ext).to.be.undefined; + expect(payload.imp[0].ext.siteID).to.be.undefined; + }); + + it('when exchangeId and externalId set with video impression, bidrequest sent to endpoint with p param and externalID inside imp.ext', function () { + const validBids = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + delete validBids[0].params.siteId; + validBids[0].params.externalId = 'exteranl_id_1'; + + const requests = spec.buildRequests(validBids, DEFAULT_OPTION); + const payload = extractPayload(requests[0]); + + const expectedURL = IX_SECURE_ENDPOINT + '?p=' + expectedExchangeId; + expect(requests[0].url).to.equal(expectedURL); + expect(payload.imp[0].ext.externalID).to.equal(validBids[0].params.externalId); + expect(payload.imp[0].ext.siteID).to.be.undefined; + }); + + it('when exchangeId and externalId set beside siteId, bidrequest sent to endpoint with both p param and s param and externalID inside imp.ext and siteID inside imp.banner.format.ext', function () { + bannerBids[0].params.siteId = '1234'; + const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION); + const payload = extractPayload(requests[0]); + + const expectedURL = IX_SECURE_ENDPOINT + '?s=' + bannerBids[0].params.siteId + '&p=' + expectedExchangeId; + expect(requests[0].url).to.equal(expectedURL); + expect(payload.imp[0].ext.externalID).to.equal(bannerBids[0].params.externalId); + expect(payload.imp[0].banner.format[0].ext.externalID).to.be.undefined; + expect(payload.imp[0].ext.siteID).to.be.undefined; + expect(payload.imp[0].banner.format[0].ext.siteID).to.equal(bannerBids[0].params.siteId); + }); + + it('when exchangeId and siteId set, but no externalId, bidrequest sent to exchange', function () { + bannerBids[0].params.siteId = '1234'; + delete bannerBids[0].params.externalId; + const requests = spec.buildRequests(bannerBids, DEFAULT_OPTION); + const payload = extractPayload(requests[0]); + + const expectedURL = IX_SECURE_ENDPOINT + '?s=' + bannerBids[0].params.siteId + '&p=' + expectedExchangeId; + expect(requests[0].url).to.equal(expectedURL); + expect(payload.imp[0].ext.externalID).to.be.undefined; + expect(payload.imp[0].banner.format[0].ext.siteID).to.equal(bannerBids[0].params.siteId); + }); + }); + describe('interpretResponse', function () { // generate bidderRequest with real buildRequest logic for intepretResponse testing let bannerBidderRequest From 0154028a0d7dab754d1422f2efccaf2994045595 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Nov 2023 19:06:55 +0000 Subject: [PATCH 172/760] Prebid 8.23.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8dd76a8f0ce..d100bd39cfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.23.0-pre", + "version": "8.23.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9e718d76456..9593b19d5d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.23.0-pre", + "version": "8.23.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7ecec48bd3855af64c39efbcafd555ee3091d400 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 Nov 2023 19:06:55 +0000 Subject: [PATCH 173/760] Increment version to 8.24.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d100bd39cfe..ac54254ee29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.23.0", + "version": "8.24.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9593b19d5d0..06539d1cbca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.23.0", + "version": "8.24.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 6667f3373741a29eeaa575de3a686dedb5aa0a48 Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Fri, 10 Nov 2023 02:39:44 +0530 Subject: [PATCH 174/760] Pubmatic: Contribute vendor details for Prebid server adapter (#10706) * Adding config for openwrap metadapter * Changed from http to https and timeout to 500 * Timeout value update --- modules/prebidServerBidAdapter/config.js | 9 +++ .../modules/prebidServerBidAdapter_spec.js | 68 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 29e80dfcc9f..87274504f64 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -38,5 +38,14 @@ export const S2S_VENDORS = { noP1Consent: 'https://prebid.openx.net/cookie_sync' }, timeout: 1000 + }, + 'openwrap': { + adapter: 'prebidServer', + enabled: true, + endpoint: { + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }, + timeout: 500 } } diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index b5d65c50853..b1667c1075f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -3764,6 +3764,74 @@ describe('S2S Adapter', function () { }) }); + it('should configure the s2sConfig object with openwrap vendor defaults unless specified by user', function () { + const options = { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap' + }; + + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', '1234'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['pubmatic']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }); + expect(vendorConfig).to.have.property('timeout', 500); + }); + + it('should return proper defaults', function () { + const options = { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap', + timeout: 500 + }; + + config.setConfig({ s2sConfig: options }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': '1234', + 'adapter': 'prebidServer', + 'bidders': ['pubmatic'], + 'defaultVendor': 'openwrap', + 'enabled': true, + 'endpoint': { + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }, + 'timeout': 500 + }) + }); + + it('should return default adapterOptions if not set', function () { + config.setConfig({ + s2sConfig: { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap', + timeout: 500 + } + }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + enabled: true, + timeout: 500, + adapter: 'prebidServer', + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap', + endpoint: { + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }, + }) + }); + it('should set adapterOptions', function () { config.setConfig({ s2sConfig: { From f2e46d4d08041643a8e5d18a4818ac46a81aaa22 Mon Sep 17 00:00:00 2001 From: Pascal Salesch Date: Thu, 9 Nov 2023 22:11:36 +0100 Subject: [PATCH 175/760] priceFloors: Prevent warning when floor price value is 0 (#10701) * Prevent warning when priceFloor value is 0 * priceFloors: floor price of zero should not cause warnings --- modules/priceFloors.js | 2 +- test/spec/modules/priceFloors_spec.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index a56be71eac0..4ec8a8e4b7e 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -748,7 +748,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a let floorInfo = getFirstMatchingFloor(floorData.data, matchingBidRequest, {...bid, size: [bid.width, bid.height]}); if (!floorInfo.matchingFloor) { - logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); + if (floorInfo.matchingFloor !== 0) logWarn(`${MODULE_NAME}: unable to determine a matching price floor for bidResponse`, bid); return fn.call(this, adUnitCode, bid, reject); } diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 950e039491d..673a821b497 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -2011,6 +2011,12 @@ describe('the price floors module', function () { expect(returnedBidResponse).to.not.haveOwnProperty('floorData'); expect(logWarnSpy.calledOnce).to.equal(true); }); + it('if it finds a rule with a floor price of zero it should not call log warn', function () { + _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); + _floorDataForAuction[AUCTION_ID].data.values = { '*': 0 }; + runBidResponse(); + expect(logWarnSpy.calledOnce).to.equal(false); + }); it('if it finds a rule and floors should update the bid accordingly', function () { _floorDataForAuction[AUCTION_ID] = utils.deepClone(basicFloorConfig); _floorDataForAuction[AUCTION_ID].data.values = { 'banner': 1.0 }; From e3b99f80571207069908300dca029ef089ae9f89 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Thu, 9 Nov 2023 22:17:16 +0100 Subject: [PATCH 176/760] Update live-connect-js dependency (#10692) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac54254ee29..2c49e7b218f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.2.4" + "live-connect-js": "^6.3.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16242,9 +16242,9 @@ } }, "node_modules/live-connect-js": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.2.4.tgz", - "integrity": "sha512-fjNzKxbtmHSCsPT27UANOf66fVizJkhgSzAj5Ej9X6sYRiox+NClM2VdRSf4eL54BabJznPLEWzWT/s4ZVVWsQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.0.tgz", + "integrity": "sha512-1SnXQZq9gxHIb0scXPX1Da1rQ0oY2sloMGgeRreTAwhCtdQEuip/IYwgOh3/ZeZ6yT6iG9FLb7+AjORC4pO46g==", "dependencies": { "live-connect-common": "^v3.0.2", "tiny-hashes": "1.0.1" @@ -37892,9 +37892,9 @@ "integrity": "sha512-K3LNKd9CpREDJbXGdwKqPojjQaxd4G6c7OAD6Yzp3wsCWTH2hV8xNAbUksSOpOcVyyOT9ilteEFXIJQJrbODxQ==" }, "live-connect-js": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.2.4.tgz", - "integrity": "sha512-fjNzKxbtmHSCsPT27UANOf66fVizJkhgSzAj5Ej9X6sYRiox+NClM2VdRSf4eL54BabJznPLEWzWT/s4ZVVWsQ==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.0.tgz", + "integrity": "sha512-1SnXQZq9gxHIb0scXPX1Da1rQ0oY2sloMGgeRreTAwhCtdQEuip/IYwgOh3/ZeZ6yT6iG9FLb7+AjORC4pO46g==", "requires": { "live-connect-common": "^v3.0.2", "tiny-hashes": "1.0.1" diff --git a/package.json b/package.json index 06539d1cbca..75481237d1b 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.2.4" + "live-connect-js": "^6.3.0" }, "optionalDependencies": { "fsevents": "^2.3.2" From 015f12b3a2ab62d993e2578d6725b6acf8456910 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Fri, 10 Nov 2023 00:37:20 +0300 Subject: [PATCH 177/760] Yieldmo Bid Adapter: Adding tmax to banner bid requests (#10689) * Adding tmax to banner * Adding unit test adding unit tests for timeout --- modules/yieldmoBidAdapter.js | 9 ++++++--- test/spec/modules/yieldmoBidAdapter_spec.js | 9 ++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index d2e97f5178e..e72a671ac74 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -78,14 +78,17 @@ export const spec = { bust: new Date().getTime().toString(), dnt: getDNT(), description: getPageDescription(), + tmax: bidderRequest.timeout || 400, userConsent: JSON.stringify({ // case of undefined, stringify will remove param - gdprApplies: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', + gdprApplies: + deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || '', cmp: deepAccess(bidderRequest, 'gdprConsent.consentString') || '', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || '', - gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || [] + gpp_sid: + deepAccess(bidderRequest, 'gppConsent.applicableSections') || [], }), - us_privacy: deepAccess(bidderRequest, 'uspConsent') || '' + us_privacy: deepAccess(bidderRequest, 'uspConsent') || '', }; if (canAccessTopWindow()) { diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 229dc05e2fa..a80d0a842b7 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -74,7 +74,6 @@ describe('YieldmoAdapter', function () { bidderRequestId: '14c4ede8c693f', bids, auctionStart: 1520001292880, - timeout: 3000, start: 1520001292884, doneCbCallCount: 0, refererInfo: { @@ -169,6 +168,14 @@ describe('YieldmoAdapter', function () { expect(requests[0].url).to.be.equal(BANNER_ENDPOINT); }); + it('should pass default timeout in bid request', function () { + const requests = build([mockBannerBid()]); + expect(requests[0].data.tmax).to.equal(400); + }); + it('should pass tmax to bid request', function () { + const requests = build([mockBannerBid()], mockBidderRequest({timeout: 1000})); + expect(requests[0].data.tmax).to.equal(1000); + }); it('should not blow up if crumbs is undefined', function () { expect(function () { build([mockBannerBid({crumbs: undefined})]); From fe5fbb7f977a7730edbf01f285c34e742eafffcf Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Fri, 10 Nov 2023 22:29:21 +0800 Subject: [PATCH 178/760] Discovery Bid Adapter: add bidId, keywords, and pbadslot (#10694) --- modules/discoveryBidAdapter.js | 5 ++++- test/spec/modules/discoveryBidAdapter_spec.js | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 465c6754e77..fff766d05a4 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -243,8 +243,9 @@ function getReferrer(bidRequest = {}, bidderRequest = {}) { */ function addImpExtParams(bidRequest = {}, bidderRequest = {}) { const { deepAccess } = utils; - const { params = {}, adUnitCode } = bidRequest; + const { params = {}, adUnitCode, bidId } = bidRequest; const ext = { + bidId: bidId || '', adUnitCode: adUnitCode || '', token: params.token || '', siteId: params.siteId || '', @@ -260,7 +261,9 @@ function addImpExtParams(bidRequest = {}, bidderRequest = {}) { browsiViewability: deepAccess(bidRequest, 'ortb2Imp.ext.data.browsi.browsiViewability', '', ''), adserverName: deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.name', '', ''), adslot: deepAccess(bidRequest, 'ortb2Imp.ext.data.adserver.adslot', '', ''), + keywords: deepAccess(bidRequest, 'ortb2Imp.ext.data.keywords', '', ''), gpid: deepAccess(bidRequest, 'ortb2Imp.ext.gpid', '', ''), + pbadslot: deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot', '', ''), }; return ext; } diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 9ebedfb7438..ecf97f1fed3 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -39,7 +39,8 @@ describe('discovery:BidAdapterTests', function () { publisher: { domain: 'discovery.popin.cc' }, - page: 'https://discovery.popin.cc' + page: 'https://discovery.popin.cc', + cat: ['IAB-19', 'IAB-20'], }, }, ortb2Imp: { @@ -53,7 +54,9 @@ describe('discovery:BidAdapterTests', function () { adserver: { name: 'adserver_name', adslot: 'adslot_name', - } + }, + keywords: ['travel', 'sport'], + pbadslot: '202309999' } } }, From 2aadc095508788586dba977aa3846e09a5044457 Mon Sep 17 00:00:00 2001 From: Olivier Date: Fri, 10 Nov 2023 15:58:04 +0100 Subject: [PATCH 179/760] AdagioBidAdapter: fix floor price module support (#10711) --- modules/adagioBidAdapter.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index c775e8223b4..90f62ba4694 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,6 +1,5 @@ import {find} from '../src/polyfill.js'; import { - _map, cleanObj, deepAccess, deepClone, @@ -570,6 +569,7 @@ function _parseNativeBidResponse(bid) { bid.native = native } +// bidRequest param must be the `bidRequest` object with the original `auctionId` value. function _getFloors(bidRequest) { if (!isFn(bidRequest.getFloor)) { return false; @@ -996,7 +996,7 @@ export const spec = { const aucId = generateUUID() - const adUnits = _map(validBidRequests, (rawBidRequest) => { + const adUnits = validBidRequests.map(rawBidRequest => { const bidRequest = deepClone(rawBidRequest); // Fix https://github.com/prebid/Prebid.js/issues/9781 @@ -1068,7 +1068,10 @@ export const spec = { }); // Handle priceFloors module - const computedFloors = _getFloors(bidRequest); + // We need to use `rawBidRequest` as param because: + // - adagioBidAdapter generates its own auctionId due to transmitTid activity limitation (see https://github.com/prebid/Prebid.js/pull/10079) + // - the priceFloors.getFloor() uses a `_floorDataForAuction` map to store the floors based on the auctionId. + const computedFloors = _getFloors(rawBidRequest); if (isArray(computedFloors) && computedFloors.length) { bidRequest.floors = computedFloors @@ -1149,7 +1152,7 @@ export const spec = { }); // Build one request per organizationId - const requests = _map(Object.keys(groupedAdUnits), organizationId => { + const requests = Object.keys(groupedAdUnits).map(organizationId => { return { method: 'POST', url: ENDPOINT, From 95a6107e6071ef5bda861ed5843afcbe21a3fa73 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 13 Nov 2023 01:01:27 -0800 Subject: [PATCH 180/760] Currency module: better error handling (#10679) * reject bids when auction times out before rates are available * retry loading rates after errors * do not use import star --- modules/currency.js | 93 +++++++++++-------- modules/sonobiAnalyticsAdapter.js | 2 +- src/auction.js | 2 + src/constants.json | 1 + test/spec/auctionmanager_spec.js | 33 ++++++- test/spec/modules/currency_spec.js | 86 ++++++++++++----- .../modules/prebidServerBidAdapter_spec.js | 7 +- .../modules/sonobiAnalyticsAdapter_spec.js | 6 +- 8 files changed, 158 insertions(+), 72 deletions(-) diff --git a/modules/currency.js b/modules/currency.js index 0ae8c8ad0a6..e42799c0864 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -7,20 +7,22 @@ import {getHook} from '../src/hook.js'; import {defer} from '../src/utils/promise.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; +import {on as onEvent, off as offEvent} from '../src/events.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; const CURRENCY_RATE_PRECISION = 4; -var bidResponseQueue = []; -var conversionCache = {}; -var currencyRatesLoaded = false; -var needToCallForCurrencyFile = true; -var adServerCurrency = 'USD'; +let ratesURL; +let bidResponseQueue = []; +let conversionCache = {}; +let currencyRatesLoaded = false; +let needToCallForCurrencyFile = true; +let adServerCurrency = 'USD'; export var currencySupportEnabled = false; export var currencyRates = {}; -var bidderCurrencyDefault = {}; -var defaultRates; +let bidderCurrencyDefault = {}; +let defaultRates; export let responseReady = defer(); @@ -57,7 +59,7 @@ export let responseReady = defer(); * there is an error loading the config.conversionRateFile. */ export function setConfig(config) { - let url = DEFAULT_CURRENCY_RATE_URL; + ratesURL = DEFAULT_CURRENCY_RATE_URL; if (typeof config.rates === 'object') { currencyRates.conversions = config.rates; @@ -79,14 +81,14 @@ export function setConfig(config) { adServerCurrency = config.adServerCurrency; if (config.conversionRateFile) { logInfo('currency using override conversionRateFile:', config.conversionRateFile); - url = config.conversionRateFile; + ratesURL = config.conversionRateFile; } // see if the url contains a date macro // this is a workaround to the fact that jsdelivr doesn't currently support setting a 24-hour HTTP cache header // So this is an approach to let the browser cache a copy of the file each day // We should remove the macro once the CDN support a day-level HTTP cache setting - const macroLocation = url.indexOf('$$TODAY$$'); + const macroLocation = ratesURL.indexOf('$$TODAY$$'); if (macroLocation !== -1) { // get the date to resolve the macro const d = new Date(); @@ -97,10 +99,10 @@ export function setConfig(config) { const todaysDate = `${d.getFullYear()}${month}${day}`; // replace $$TODAY$$ with todaysDate - url = `${url.substring(0, macroLocation)}${todaysDate}${url.substring(macroLocation + 9, url.length)}`; + ratesURL = `${ratesURL.substring(0, macroLocation)}${todaysDate}${ratesURL.substring(macroLocation + 9, ratesURL.length)}`; } - initCurrency(url); + initCurrency(); } else { // currency support is disabled, setting defaults logInfo('disabling currency support'); @@ -121,21 +123,11 @@ function errorSettingsRates(msg) { } } -function initCurrency(url) { - conversionCache = {}; - currencySupportEnabled = true; - - logInfo('Installing addBidResponse decorator for currency module', arguments); - - // Adding conversion function to prebid global for external module and on page use - getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); - getHook('addBidResponse').before(addBidResponseHook, 100); - getHook('responsesReady').before(responsesReadyHook); - - // call for the file if we haven't already +function loadRates() { if (needToCallForCurrencyFile) { needToCallForCurrencyFile = false; - ajax(url, + currencyRatesLoaded = false; + ajax(ratesURL, { success: function (response) { try { @@ -150,17 +142,37 @@ function initCurrency(url) { }, error: function (...args) { errorSettingsRates(...args); + currencyRatesLoaded = true; + processBidResponseQueue(); + needToCallForCurrencyFile = true; } } ); } } +function initCurrency() { + conversionCache = {}; + currencySupportEnabled = true; + + logInfo('Installing addBidResponse decorator for currency module', arguments); + + // Adding conversion function to prebid global for external module and on page use + getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); + getHook('addBidResponse').before(addBidResponseHook, 100); + getHook('responsesReady').before(responsesReadyHook); + onEvent(CONSTANTS.EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + onEvent(CONSTANTS.EVENTS.AUCTION_INIT, loadRates); + loadRates(); +} + function resetCurrency() { logInfo('Uninstalling addBidResponse decorator for currency module', arguments); getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); + offEvent(CONSTANTS.EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + offEvent(CONSTANTS.EVENTS.AUCTION_INIT, loadRates); delete getGlobal().convertCurrency; adServerCurrency = 'USD'; @@ -207,23 +219,25 @@ export const addBidResponseHook = timedBidResponseHook('currency', function addB if (bid.currency === adServerCurrency) { return fn.call(this, adUnitCode, bid, reject); } - - bidResponseQueue.push(wrapFunction(fn, this, [adUnitCode, bid, reject])); + bidResponseQueue.push([fn, this, adUnitCode, bid, reject]); if (!currencySupportEnabled || currencyRatesLoaded) { processBidResponseQueue(); } }); -function processBidResponseQueue() { - while (bidResponseQueue.length > 0) { - (bidResponseQueue.shift())(); - } - responseReady.resolve() +function rejectOnAuctionTimeout({auctionId}) { + bidResponseQueue = bidResponseQueue.filter(([fn, ctx, adUnitCode, bid, reject]) => { + if (bid.auctionId === auctionId) { + reject(CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY) + } else { + return true; + } + }); } -function wrapFunction(fn, context, params) { - return function() { - let bid = params[1]; +function processBidResponseQueue() { + while (bidResponseQueue.length > 0) { + const [fn, ctx, adUnitCode, bid, reject] = bidResponseQueue.shift(); if (bid !== undefined && 'currency' in bid && 'cpm' in bid) { let fromCurrency = bid.currency; try { @@ -234,12 +248,13 @@ function wrapFunction(fn, context, params) { } } catch (e) { logWarn('getCurrencyConversion threw error: ', e); - params[2](CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); - return; + reject(CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + continue; } } - return fn.apply(context, params); - }; + fn.call(ctx, adUnitCode, bid, reject); + } + responseReady.resolve(); } function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) { diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js index 0057944b201..04a855b5be6 100644 --- a/modules/sonobiAnalyticsAdapter.js +++ b/modules/sonobiAnalyticsAdapter.js @@ -6,7 +6,7 @@ import {ajaxBuilder} from '../src/ajax.js'; let ajax = ajaxBuilder(0); -const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; +export const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; const analyticsType = 'endpoint'; const QUEUE_TIMEOUT_DEFAULT = 200; const { diff --git a/src/auction.js b/src/auction.js index df1b2cdef55..efce69ea9f8 100644 --- a/src/auction.js +++ b/src/auction.js @@ -185,6 +185,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a function executeCallback(timedOut) { if (!timedOut) { clearTimeout(_timeoutTimer); + } else { + events.emit(CONSTANTS.EVENTS.AUCTION_TIMEOUT, getProperties()); } if (_auctionEnd === undefined) { let timedOutRequests = []; diff --git a/src/constants.json b/src/constants.json index c763090f6d0..ad0f5b3a71b 100644 --- a/src/constants.json +++ b/src/constants.json @@ -23,6 +23,7 @@ }, "EVENTS": { "AUCTION_INIT": "auctionInit", + "AUCTION_TIMEOUT": "auctionTimeout", "AUCTION_END": "auctionEnd", "BID_ADJUSTMENT": "bidAdjustment", "BID_TIMEOUT": "bidTimeout", diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index b43f630752b..137d009bd18 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -1156,7 +1156,7 @@ describe('auctionmanager.js', function () { return auction.end.then(() => { expect(auction.getBidsReceived().length).to.eql(1); }) - }) + }); }) it('sets bidResponse.ttlBuffer from adUnit.ttlBuffer', () => { @@ -1200,6 +1200,7 @@ describe('auctionmanager.js', function () { const spec2 = mockBidder(BIDDER_CODE1, [bids[1]]); registerBidder(spec2); auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: () => auctionDone(), cbTimeout: 20}); + indexAuctions = [auction]; }); afterEach(function () { @@ -1216,7 +1217,35 @@ describe('auctionmanager.js', function () { }); respondToRequest(0); return pm; - }) + }); + + describe('AUCTION_TIMEOUT event', () => { + let handler; + beforeEach(() => { + handler = sinon.spy(); + events.on(CONSTANTS.EVENTS.AUCTION_TIMEOUT, handler); + }) + afterEach(() => { + events.off(CONSTANTS.EVENTS.AUCTION_TIMEOUT, handler); + }); + + Object.entries({ + 'is fired on timeout': [true, [0]], + 'is NOT fired otherwise': [false, [0, 1]], + }).forEach(([t, [shouldFire, respond]]) => { + it(t, () => { + const pm = runAuction().then(() => { + if (shouldFire) { + sinon.assert.calledWith(handler, sinon.match({auctionId: auction.getAuctionId()})) + } else { + sinon.assert.notCalled(handler); + } + }); + respond.forEach(respondToRequest); + return pm; + }) + }); + }); it('should emit BID_TIMEOUT and AUCTION_END for timed out bids', function () { const pm = runAuction().then(() => { diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index ef80a17d2db..623194cbee9 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -15,6 +15,7 @@ import { import {createBid} from '../../../src/bidfactory.js'; import CONSTANTS from '../../../src/constants.json'; import {server} from '../../mocks/xhr.js'; +import * as events from 'src/events.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -286,32 +287,56 @@ describe('currency', function () { expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('1000.000'); }); - it('uses default rates when currency file fails to load', function () { - setConfig({}); - - setConfig({ - adServerCurrency: 'USD', - defaultRates: { - USD: { - JPY: 100 + describe('when rates fail to load', () => { + let bid, addBidResponse, reject; + beforeEach(() => { + bid = makeBid({cpm: 100, currency: 'JPY', bidder: 'rubicoin'}); + addBidResponse = sinon.spy(); + reject = sinon.spy(); + }) + it('uses default rates if specified', function () { + setConfig({ + adServerCurrency: 'USD', + defaultRates: { + USD: { + JPY: 100 + } } - } - }); - - // default response is 404 - fakeCurrencyFileServer.respond(); + }); - var bid = { cpm: 100, currency: 'JPY', bidder: 'rubicon' }; - var innerBid; + // default response is 404 + addBidResponseHook(addBidResponse, 'au', bid); + fakeCurrencyFileServer.respond(); + sinon.assert.calledWith(addBidResponse, 'au', sinon.match(innerBid => { + expect(innerBid.cpm).to.equal('1.0000'); + expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); + expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('100.000'); + return true; + })); + }); - addBidResponseHook(function(adCodeId, bid) { - innerBid = bid; - }, 'elementId', bid); + it('rejects bids if no default rates are specified', () => { + setConfig({ + adServerCurrency: 'USD', + }); + addBidResponseHook(addBidResponse, 'au', bid, reject); + fakeCurrencyFileServer.respond(); + sinon.assert.notCalled(addBidResponse); + sinon.assert.calledWith(reject, CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + }); - expect(innerBid.cpm).to.equal('1.0000'); - expect(typeof innerBid.getCpmInNewCurrency).to.equal('function'); - expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('100.000'); - }); + it('attempts to load rates again on the next auction', () => { + setConfig({ + adServerCurrency: 'USD', + }); + fakeCurrencyFileServer.respond(); + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {}); + addBidResponseHook(addBidResponse, 'au', bid, reject); + fakeCurrencyFileServer.respond(); + sinon.assert.calledWith(addBidResponse, 'au', bid, reject); + }) + }) }); describe('currency.addBidResponseDecorator bidResponseQueue', function () { @@ -415,6 +440,23 @@ describe('currency', function () { expect(reject.calledOnce).to.be.true; }); + it('should reject bid when rates have not loaded when the auction times out', () => { + fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); + setConfig({'adServerCurrency': 'JPY'}); + const bid = makeBid({cpm: 1, currency: 'USD', auctionId: 'aid'}); + const noConversionBid = makeBid({cpm: 1, currency: 'JPY', auctionId: 'aid'}); + const reject = sinon.spy(); + const addBidResponse = sinon.spy(); + addBidResponseHook(addBidResponse, 'au', bid, reject); + addBidResponseHook(addBidResponse, 'au', noConversionBid, reject); + events.emit(CONSTANTS.EVENTS.AUCTION_TIMEOUT, {auctionId: 'aid'}); + fakeCurrencyFileServer.respond(); + sinon.assert.calledOnce(addBidResponse); + sinon.assert.calledWith(addBidResponse, 'au', noConversionBid, reject); + sinon.assert.calledOnce(reject); + sinon.assert.calledWith(reject, CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + }) + it('should return 1 when currency support is enabled and same currency code is requested as is set to adServerCurrency', function () { fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); setConfig({ 'adServerCurrency': 'JPY' }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index b1667c1075f..5b591804b95 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -14,7 +14,6 @@ import {config} from 'src/config.js'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import {server} from 'test/mocks/xhr.js'; -import {createEidsArray} from 'modules/userId/eids.js'; import 'modules/appnexusBidAdapter.js'; // appnexus alias test import 'modules/rubiconBidAdapter.js'; // rubicon alias test import 'src/prebid.js'; // $$PREBID_GLOBAL$$.aliasBidder test @@ -34,11 +33,9 @@ import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; -import {syncAddFPDEnrichments, syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; -import {sandbox} from 'sinon'; import {ACTIVITY_TRANSMIT_UFPD} from '../../../src/activities/activities.js'; -import {activityParams} from '../../../src/activities/activityParams.js'; import {MODULE_TYPE_PREBID} from '../../../src/activities/modules.js'; let CONFIG = { @@ -2054,7 +2051,7 @@ describe('S2S Adapter', function () { const bidRequests = utils.deepClone(BID_REQUESTS); adapter.callBids(REQUEST, bidRequests, addBidResponse, done, ajax); - const parsedRequestBody = JSON.parse(server.requests[1].requestBody); + const parsedRequestBody = JSON.parse(server.requests.find(req => req.method === 'POST').requestBody); expect(parsedRequestBody.cur).to.deep.equal(['NZ']); }); diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js index 76ff88836d4..ed8ccd22eea 100644 --- a/test/spec/modules/sonobiAnalyticsAdapter_spec.js +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import sonobiAnalytics from 'modules/sonobiAnalyticsAdapter.js'; +import sonobiAnalytics, {DEFAULT_EVENT_URL} from 'modules/sonobiAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; let events = require('src/events'); @@ -76,8 +76,8 @@ describe('Sonobi Prebid Analytic', function () { events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); clock.tick(5000); - expect(server.requests).to.have.length(1); - expect(JSON.parse(server.requests[0].requestBody)).to.have.length(3) + const req = server.requests.find(req => req.url.indexOf(DEFAULT_EVENT_URL) !== -1); + expect(JSON.parse(req.requestBody)).to.have.length(3) done(); }); }); From 7dbfba405895b6bde93261dcf0d87397979cfe4a Mon Sep 17 00:00:00 2001 From: lvhuixin <42998776+lhxx121@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:23:25 +0800 Subject: [PATCH 181/760] discovery add refer stack (#10712) Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 1 + test/spec/modules/discoveryBidAdapter_spec.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index fff766d05a4..3b9f858e15c 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -254,6 +254,7 @@ function addImpExtParams(bidRequest = {}, bidderRequest = {}) { p_pos: params.position || '', screenSize: getScreenSize(), referrer: getReferrer(bidRequest, bidderRequest), + stack: deepAccess(bidRequest, 'refererInfo.stack', []), b_pos: deepAccess(bidRequest, 'mediaTypes.banner.pos', '', ''), ortbUser: deepAccess(bidRequest, 'ortb2.user', {}, {}), ortbSite: deepAccess(bidRequest, 'ortb2.site', {}, {}), diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index ecf97f1fed3..8957f8bbd40 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -19,6 +19,10 @@ describe('discovery:BidAdapterTests', function () { }, refererInfo: { page: 'https://discovery.popin.cc', + stack: [ + 'a.com', + 'b.com' + ] }, mediaTypes: { banner: { From f50e42cb07bd5a61ae360dd7abc11ac06c99b628 Mon Sep 17 00:00:00 2001 From: ryohamadaumt <105703275+ryohamadaumt@users.noreply.github.com> Date: Tue, 14 Nov 2023 00:12:54 +0900 Subject: [PATCH 182/760] adstir Bid Adapter: initial release (#10680) * add adstir bidder adapter * fix userIdAsEids typo --- modules/adstirBidAdapter.js | 91 +++++ modules/adstirBidAdapter.md | 36 ++ test/spec/modules/adstirBidAdapter_spec.js | 412 +++++++++++++++++++++ 3 files changed, 539 insertions(+) create mode 100644 modules/adstirBidAdapter.js create mode 100644 modules/adstirBidAdapter.md create mode 100644 test/spec/modules/adstirBidAdapter_spec.js diff --git a/modules/adstirBidAdapter.js b/modules/adstirBidAdapter.js new file mode 100644 index 00000000000..4b22d568785 --- /dev/null +++ b/modules/adstirBidAdapter.js @@ -0,0 +1,91 @@ +import * as utils from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'adstir'; +const ENDPOINT = 'https://ad.ad-stir.com/prebid' + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!(utils.isStr(bid.params.appId) && !utils.isEmptyStr(bid.params.appId) && utils.isInteger(bid.params.adSpaceNo)); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + const sua = utils.deepAccess(validBidRequests[0], 'ortb2.device.sua', null); + + const requests = validBidRequests.map((r) => { + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify({ + appId: r.params.appId, + adSpaceNo: r.params.adSpaceNo, + auctionId: r.auctionId, + transactionId: r.transactionId, + bidId: r.bidId, + mediaTypes: r.mediaTypes, + sizes: r.sizes, + ref: { + page: bidderRequest.refererInfo.page, + tloc: bidderRequest.refererInfo.topmostLocation, + referrer: bidderRequest.refererInfo.ref, + topurl: config.getConfig('pageUrl') ? false : bidderRequest.refererInfo.reachedTop, + }, + sua, + gdpr: utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies', false), + usp: (bidderRequest.uspConsent || '1---') !== '1---', + eids: utils.deepAccess(r, 'userIdAsEids', []), + schain: serializeSchain(utils.deepAccess(r, 'schain', null)), + pbVersion: '$prebid.version$', + }), + } + }); + + return requests; + }, + + interpretResponse: function (serverResponse, bidRequest) { + const seatbid = serverResponse.body.seatbid; + if (!utils.isArray(seatbid)) { + return []; + } + const bids = []; + seatbid.forEach((b) => { + const bid = b.bid || null; + if (!bid) { + return; + } + bids.push(bid); + }); + return bids; + }, +} + +function serializeSchain(schain) { + if (!schain) { + return null; + } + + let serializedSchain = `${schain.ver},${schain.complete}`; + + schain.nodes.map(node => { + serializedSchain += `!${encodeURIComponentForRFC3986(node.asi || '')},`; + serializedSchain += `${encodeURIComponentForRFC3986(node.sid || '')},`; + serializedSchain += `${encodeURIComponentForRFC3986(node.hp || '')},`; + serializedSchain += `${encodeURIComponentForRFC3986(node.rid || '')},`; + serializedSchain += `${encodeURIComponentForRFC3986(node.name || '')},`; + serializedSchain += `${encodeURIComponentForRFC3986(node.domain || '')}`; + }); + + return serializedSchain; +} + +function encodeURIComponentForRFC3986(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16)}`); +} + +registerBidder(spec); diff --git a/modules/adstirBidAdapter.md b/modules/adstirBidAdapter.md new file mode 100644 index 00000000000..7485375a09d --- /dev/null +++ b/modules/adstirBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: adstir Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@ad-stir.com +``` + +# Description + +Module that connects to adstir's demand sources + +# Test Parameters + +``` + var adUnits = [ + // Banner adUnit + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'adstir', + params: { + appId: 'TEST-MEDIA', + adSpaceNo: 1, + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/adstirBidAdapter_spec.js b/test/spec/modules/adstirBidAdapter_spec.js new file mode 100644 index 00000000000..290a6822f69 --- /dev/null +++ b/test/spec/modules/adstirBidAdapter_spec.js @@ -0,0 +1,412 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/adstirBidAdapter.js'; +import * as utils from 'src/utils.js'; +import { config } from 'src/config.js'; + +describe('AdstirAdapter', function () { + describe('isBidRequestValid', function () { + it('should return true if appId is non-empty string and adSpaceNo is integer', function () { + const bid = { + params: { + appId: 'MEDIA-XXXXXX', + adSpaceNo: 6, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false if appId is non-empty string, but adSpaceNo is not integer', function () { + const bid = { + params: { + appId: 'MEDIA-XXXXXX', + adSpaceNo: 'a', + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if appId is non-empty string, but adSpaceNo is null', function () { + const bid = { + params: { + appId: 'MEDIA-XXXXXX', + adSpaceNo: null, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if appId is non-empty string, but adSpaceNo is undefined', function () { + const bid = { + params: { + appId: 'MEDIA-XXXXXX' + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if adSpaceNo is integer, but appId is empty string', function () { + const bid = { + params: { + appId: '', + adSpaceNo: 6, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if adSpaceNo is integer, but appId is not string', function () { + const bid = { + params: { + appId: 123, + adSpaceNo: 6, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if adSpaceNo is integer, but appId is null', function () { + const bid = { + params: { + appId: null, + adSpaceNo: 6, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if adSpaceNo is integer, but appId is undefined', function () { + const bid = { + params: { + adSpaceNo: 6, + } + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false if params is empty', function () { + const bid = { + params: {} + } + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const validBidRequests = [ + { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'adstir', + bidId: 'bidid1111', + params: { + appId: 'MEDIA-XXXXXX', + adSpaceNo: 1, + }, + transactionId: 'transactionid-1111', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [336, 280], + ], + } + }, + sizes: [ + [300, 250], + [336, 280], + ], + }, + { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'adstir', + bidId: 'bidid2222', + params: { + appId: 'MEDIA-XXXXXX', + adSpaceNo: 2, + }, + transactionId: 'transactionid-2222', + mediaTypes: { + banner: { + sizes: [ + [320, 50], + [320, 100], + ], + } + }, + sizes: [ + [320, 50], + [320, 100], + ], + }, + ]; + + const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + refererInfo: { + page: 'https://ad-stir.com/contact', + topmostLocation: 'https://ad-stir.com/contact', + reachedTop: true, + ref: 'https://test.example/q=adstir', + isAmp: false, + numIframes: 0, + stack: [ + 'https://ad-stir.com/contact', + ], + }, + }; + + it('one entry in validBidRequests corresponds to one server request object.', function () { + const requests = spec.buildRequests(validBidRequests, bidderRequest); + expect(requests.length).to.equal(validBidRequests.length); + requests.forEach(function (r, i) { + expect(r.method).to.equal('POST'); + expect(r.url).to.equal('https://ad.ad-stir.com/prebid'); + const d = JSON.parse(r.data); + expect(d.auctionId).to.equal('b06c5141-fe8f-4cdf-9d7d-54415490a917'); + + const v = validBidRequests[i]; + expect(d.appId).to.equal(v.params.appId); + expect(d.adSpaceNo).to.equal(v.params.adSpaceNo); + expect(d.bidId).to.equal(v.bidId); + expect(d.transactionId).to.equal(v.transactionId); + expect(d.mediaTypes).to.deep.equal(v.mediaTypes); + expect(d.sizes).to.deep.equal(v.sizes); + expect(d.ref.page).to.equal(bidderRequest.refererInfo.page); + 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.gdpr).to.equal(false); + expect(d.usp).to.equal(false); + expect(d.schain).to.equal(null); + expect(d.eids).to.deep.equal([]); + }); + }); + + it('ref.page, ref.tloc and ref.referrer correspond to refererInfo', function () { + const [ request ] = spec.buildRequests([validBidRequests[0]], { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + refererInfo: { + page: null, + topmostLocation: 'https://adserver.example/iframe1.html', + reachedTop: false, + ref: null, + isAmp: false, + numIframes: 2, + stack: [ + null, + 'https://adserver.example/iframe1.html', + 'https://adserver.example/iframe2.html' + ], + }, + }); + + const { ref } = JSON.parse(request.data); + expect(ref.page).to.equal(null); + expect(ref.tloc).to.equal('https://adserver.example/iframe1.html'); + expect(ref.referrer).to.equal(null); + }); + + it('when config.pageUrl is not set, ref.topurl equals to refererInfo.reachedTop', function () { + let bidderRequestClone = utils.deepClone(bidderRequest); + [true, false].forEach(function (reachedTop) { + bidderRequestClone.refererInfo.reachedTop = reachedTop; + const requests = spec.buildRequests(validBidRequests, bidderRequestClone); + const d = JSON.parse(requests[0].data); + expect(d.ref.topurl).to.equal(reachedTop); + }); + }); + + describe('when config.pageUrl is set, ref.topurl is always false', function () { + before(function () { + config.setConfig({ pageUrl: 'https://ad-stir.com/register' }); + }); + after(function () { + config.resetConfig(); + }); + + it('ref.topurl should be false', function () { + let bidderRequestClone = utils.deepClone(bidderRequest); + [true, false].forEach(function (reachedTop) { + bidderRequestClone.refererInfo.reachedTop = reachedTop; + const requests = spec.buildRequests(validBidRequests, bidderRequestClone); + const d = JSON.parse(requests[0].data); + expect(d.ref.topurl).to.equal(false); + }); + }); + }); + + it('gdprConsent.gdprApplies is sent', function () { + let bidderRequestClone = utils.deepClone(bidderRequest); + [true, false].forEach(function (gdprApplies) { + bidderRequestClone.gdprConsent = { gdprApplies }; + const requests = spec.buildRequests(validBidRequests, bidderRequestClone); + const d = JSON.parse(requests[0].data); + expect(d.gdpr).to.equal(gdprApplies); + }); + }); + + it('includes in the request parameters whether CCPA applies', function () { + let bidderRequestClone = utils.deepClone(bidderRequest); + const cases = [ + { uspConsent: '1---', expected: false }, + { uspConsent: '1YYY', expected: true }, + { uspConsent: '1YNN', expected: true }, + { uspConsent: '1NYN', expected: true }, + { uspConsent: '1-Y-', expected: true }, + ]; + cases.forEach(function ({ uspConsent, expected }) { + bidderRequestClone.uspConsent = uspConsent; + const requests = spec.buildRequests(validBidRequests, bidderRequestClone); + const d = JSON.parse(requests[0].data); + expect(d.usp).to.equal(expected); + }); + }); + + it('should add schain if available', function() { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.example', + 'sid': '1234!abcd', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher, Inc.', + 'domain': 'publisher.example' + }, + { + 'asi': 'exchange2.example', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + 'name': 'intermediary', + 'domain': 'intermediary.example' + } + ] + }; + const serializedSchain = '1.0,1!exchange1.example,1234%21abcd,1,bid-request-1,publisher%2C%20Inc.,publisher.example!exchange2.example,abcd,1,bid-request-2,intermediary,intermediary.example'; + + const [ request ] = spec.buildRequests([utils.mergeDeep(utils.deepClone(validBidRequests[0]), { schain })], bidderRequest); + const d = JSON.parse(request.data); + expect(d.schain).to.deep.equal(serializedSchain); + }); + + it('should add schain even if some nodes params are blank', function() { + const schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.example', + 'sid': '1234!abcd', + 'hp': 1, + }, + { + }, + { + 'asi': 'exchange2.example', + 'sid': 'abcd', + 'hp': 1, + }, + ] + }; + const serializedSchain = '1.0,1!exchange1.example,1234%21abcd,1,,,!,,,,,!exchange2.example,abcd,1,,,'; + + const [ request ] = spec.buildRequests([utils.mergeDeep(utils.deepClone(validBidRequests[0]), { schain })], bidderRequest); + const d = JSON.parse(request.data); + expect(d.schain).to.deep.equal(serializedSchain); + }); + + it('should add UA client hints to payload if available', function () { + const sua = { + browsers: [ + { + brand: 'Not?A_Brand', + version: [ + '8', + '0', + '0', + '0' + ] + }, + { + version: [ + '108', + '0', + '5359', + '40' + ] + }, + { + brand: 'Google Chrome', + version: [ + '108', + '0', + '5359', + '40' + ] + } + ], + platform: { + brand: 'Android', + version: [ + '11' + ] + }, + mobile: 1, + architecture: '', + bitness: '64', + model: 'Pixel 5', + source: 2 + } + + const validBidRequestsClone = utils.deepClone(validBidRequests); + validBidRequestsClone[0] = utils.mergeDeep(validBidRequestsClone[0], { + ortb2: { + device: { sua }, + } + }); + + const requests = spec.buildRequests(validBidRequestsClone, bidderRequest); + requests.forEach(function (r) { + const d = JSON.parse(r.data); + expect(d.sua).to.deep.equal(sua); + }); + }); + }); + + describe('interpretResponse', function () { + it('return empty array when no content', function () { + const bids = spec.interpretResponse({ body: '' }); + expect(bids).to.deep.equal([]); + }); + it('return empty array when seatbid empty', function () { + const bids = spec.interpretResponse({ body: { seatbid: [] } }); + expect(bids).to.deep.equal([]); + }); + it('return valid bids when serverResponse is valid', function () { + const serverResponse = { + 'body': { + 'seatbid': [ + { + 'bid': { + 'ad': '
test response
', + 'cpm': 5250, + 'creativeId': '5_1234ABCD', + 'currency': 'JPY', + 'height': 250, + 'meta': { + 'advertiserDomains': [ + 'adv.example' + ], + 'mediaType': 'banner', + 'networkId': 5 + }, + 'netRevenue': true, + 'requestId': '22a9457aed98a4', + 'transactionId': 'f18c078e-4d2a-4ecb-a886-2a0c52187213', + 'ttl': 60, + 'width': 300, + } + } + ] + }, + 'headers': {} + }; + const bids = spec.interpretResponse(serverResponse); + expect(bids[0]).to.deep.equal(serverResponse.body.seatbid[0].bid); + }); + }); +}); From bdd4a40e705fe46a857069db7b8dbf1a7fa9bc96 Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:32:29 +0100 Subject: [PATCH 183/760] Criteo bid adapter: add a few missing video fields (#10720) Add some fields from bidRequest.mediaTypes.video to backend bid request --- modules/criteoBidAdapter.js | 15 +++++++++++- test/spec/modules/criteoBidAdapter_spec.js | 28 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 6de9e63f6d6..7c9411ccc2d 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -546,6 +546,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (hasVideoMediaType(bidRequest)) { const video = { + context: bidRequest.mediaTypes.video.context, playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), mimes: bidRequest.mediaTypes.video.mimes, protocols: bidRequest.mediaTypes.video.protocols, @@ -556,7 +557,19 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { minduration: bidRequest.mediaTypes.video.minduration, playbackmethod: bidRequest.mediaTypes.video.playbackmethod, startdelay: bidRequest.mediaTypes.video.startdelay, - plcmt: bidRequest.mediaTypes.video.plcmt + plcmt: bidRequest.mediaTypes.video.plcmt, + w: bidRequest.mediaTypes.video.w, + h: bidRequest.mediaTypes.video.h, + linearity: bidRequest.mediaTypes.video.linearity, + skipmin: bidRequest.mediaTypes.video.skipmin, + skipafter: bidRequest.mediaTypes.video.skipafter, + minbitrate: bidRequest.mediaTypes.video.minbitrate, + maxbitrate: bidRequest.mediaTypes.video.maxbitrate, + delivery: bidRequest.mediaTypes.video.delivery, + pos: bidRequest.mediaTypes.video.pos, + playbackend: bidRequest.mediaTypes.video.playbackend, + adPodDurationSec: bidRequest.mediaTypes.video.adPodDurationSec, + durationRangeSec: bidRequest.mediaTypes.video.durationRangeSec, }; const paramsVideo = bidRequest.params.video; if (paramsVideo !== undefined) { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index e333d0c6143..97b80ce95db 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1326,12 +1326,25 @@ describe('The Criteo bidding adapter', function () { sizes: [[640, 480]], mediaTypes: { video: { + context: 'instream', playerSize: [640, 480], mimes: ['video/mp4', 'video/x-flv'], maxduration: 30, api: [1, 2], protocols: [2, 3], - plcmt: 3 + plcmt: 3, + w: 640, + h: 480, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], } }, params: { @@ -1350,6 +1363,7 @@ describe('The Criteo bidding adapter', function () { expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; + expect(ortbRequest.slots[0].video.context).to.equal('instream'); expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); expect(ortbRequest.slots[0].sizes).to.deep.equal([]); expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480']); @@ -1362,6 +1376,18 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); expect(ortbRequest.slots[0].video.placement).to.equal(2); expect(ortbRequest.slots[0].video.plcmt).to.equal(3); + expect(ortbRequest.slots[0].video.w).to.equal(640); + expect(ortbRequest.slots[0].video.h).to.equal(480); + expect(ortbRequest.slots[0].video.linearity).to.equal(1); + expect(ortbRequest.slots[0].video.skipmin).to.equal(30); + expect(ortbRequest.slots[0].video.skipafter).to.equal(30); + expect(ortbRequest.slots[0].video.minbitrate).to.equal(10000); + expect(ortbRequest.slots[0].video.maxbitrate).to.equal(48000); + expect(ortbRequest.slots[0].video.delivery).to.deep.equal([1, 2, 3]); + expect(ortbRequest.slots[0].video.pos).to.equal(1); + expect(ortbRequest.slots[0].video.playbackend).to.equal(1); + expect(ortbRequest.slots[0].video.adPodDurationSec).to.equal(30); + expect(ortbRequest.slots[0].video.durationRangeSec).to.deep.equal([1, 30]); }); it('should properly build a video request with more than one player size', function () { From 76e967aaed85249c7ce4b044721e9fb7fd011f44 Mon Sep 17 00:00:00 2001 From: TPMN Admin Date: Tue, 14 Nov 2023 02:32:44 +0900 Subject: [PATCH 184/760] TPMN Bid Adapter: Change End-Point & Support Video (#10611) * TPMN Support Video * add video param plcmt * update ortb bcat,badv,bapp * instream video is sound on default, outstream video sound off default * remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS * fixed incorrect placement default format & update example * update test case (Your tests failed on CircleCI) * Revert "remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS" This reverts commit 2699115a76ed891ec7f911ad924206b399790ad9. * Remove unnecessary code to meet guidelines. * Remove unnecessary code to meet guidelines. * fix Build fail * delete logging code --------- Co-authored-by: changjun --- integrationExamples/gpt/tpmn_example.html | 168 ++++++ .../gpt/tpmn_serverless_example.html | 121 +++++ modules/tpmnBidAdapter.js | 346 +++++++----- modules/tpmnBidAdapter.md | 86 ++- test/spec/modules/tpmnBidAdapter_spec.js | 511 +++++++++++++----- 5 files changed, 958 insertions(+), 274 deletions(-) create mode 100644 integrationExamples/gpt/tpmn_example.html create mode 100644 integrationExamples/gpt/tpmn_serverless_example.html diff --git a/integrationExamples/gpt/tpmn_example.html b/integrationExamples/gpt/tpmn_example.html new file mode 100644 index 00000000000..f215181c7e0 --- /dev/null +++ b/integrationExamples/gpt/tpmn_example.html @@ -0,0 +1,168 @@ + + + + + Prebid.js Banner Example + + + + + + + + + + +

Prebid.js TPMN Banner Example

+ +
+

Prebid.js TPMN Video Example

+
+ +
+
+
+ diff --git a/integrationExamples/gpt/tpmn_serverless_example.html b/integrationExamples/gpt/tpmn_serverless_example.html new file mode 100644 index 00000000000..0acaefbeb9c --- /dev/null +++ b/integrationExamples/gpt/tpmn_serverless_example.html @@ -0,0 +1,121 @@ + + + + + + + + + + + + + +

Ad Serverless Test Page

+ + +
+
+ + diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index bac99d578c5..61f8a4c98af 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,164 +1,242 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseUrl, deepAccess } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { getStorageManager } from '../src/storageManager.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; -export const ADAPTER_VERSION = '1'; -const SUPPORTED_AD_TYPES = [BANNER]; const BIDDER_CODE = 'tpmn'; -const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn'; -const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe'; -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const DEFAULT_BID_TTL = 500; +const DEFAULT_CURRENCY = 'USD'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +// const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder'; +const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder'; +const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe'; +const COMMON_PARAMS = [ + 'battr' +]; +export const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +export const ADAPTER_VERSION = '2.0'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, /** - *Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function (bid) { - return 'params' in bid && - 'inventoryId' in bid.params && - 'publisherId' in bid.params && - !isNaN(Number(bid.params.inventoryId)) && - bid.params.inventoryId > 0 && - (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes - }, - - /** - * @param {BidRequest[]} bidRequests - * @param {*} bidderRequest - * @return {ServerRequest} + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction */ - buildRequests: (bidRequests, bidderRequest) => { - if (bidRequests.length === 0) { - return []; + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); } - const bids = bidRequests.map(bidToRequest); - const bidderApiUrl = URL; - const payload = { - 'bids': [...bids], - 'site': createSite(bidderRequest.refererInfo) - }; - return [{ - method: 'POST', - url: bidderApiUrl, - data: payload - }]; + } +} + +function isBidRequestValid(bid) { + return (isValidInventoryId(bid) && (isValidBannerRequest(bid) || isValidVideoRequest(bid))); +} + +function isValidInventoryId(bid) { + return 'params' in bid && 'inventoryId' in bid.params && utils.isNumber(bid.params.inventoryId); +} + +function isValidBannerRequest(bid) { + const bannerSizes = utils.deepAccess(bid, `mediaTypes.${BANNER}.sizes`); + return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function isValidVideoRequest(bid) { + const videoSizes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.playerSize`); + const videoMimes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.mimes`); + + const isValidVideoSize = utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); + const isValidVideoMimes = utils.isArray(videoMimes) && videoMimes.length > 0; + return isValidVideoSize && isValidVideoMimes; +} + +function buildRequests(validBidRequests, bidderRequest) { + let requests = []; + try { + if (validBidRequests.length === 0 || !bidderRequest) return []; + let bannerBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.banner')); + let videoBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.video')); + + bannerBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, BANNER)); + }); + + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + } catch (err) { + utils.logWarn('buildRequests', err); + } + + return requests; +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + const rtbData = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + + const bid = bidRequests.find((b) => b.params.inventoryId) + + if (bid.params.inventoryId) rtbData.ext = {}; + if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId + + const ortb2Data = bidderRequest?.ortb2 || {}; + const bcat = ortb2Data?.bcat || bid.params.bcat || []; + const badv = ortb2Data?.badv || bid.params.badv || []; + const bapp = ortb2Data?.bapp || bid.params.bapp || []; + + if (bcat.length > 0) { + rtbData.bcat = bcat; + } + if (badv.length > 0) { + rtbData.badv = badv; + } + if (badv.length > 0) { + rtbData.bapp = bapp; + } + + return { + method: 'POST', + url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, + data: rtbData, + options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + } +} + +function interpretResponse(response, request) { + return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids; +} + +registerBidder(spec); + +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {serverResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: function (serverResponse, serverRequest) { - if (!Array.isArray(serverResponse.body)) { - return []; + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + if (!imp.bidfloor && bidRequest.params.bidFloor) { + imp.bidfloor = bidRequest.params.bidFloor; } - // server response body is an array of bid results - const bidResults = serverResponse.body; - // our server directly returns the format needed by prebid.js so no more - // transformation is needed here. - return bidResults; - }, - - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncArr = []; - if (syncOptions.iframeEnabled) { - let policyParam = ''; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + [VIDEO, BANNER].forEach(namespace => { + COMMON_PARAMS.forEach(param => { + if (bidRequest.params.hasOwnProperty(param)) { + utils.deepSetValue(imp, `${namespace}.${param}`, bidRequest.params[param]) } + }) + }) + return imp; + }, + bidResponse(buildBidResponse, bid, context) { + const {bidRequest} = context; + const bidResponse = buildBidResponse(bid, context); + if (bidResponse.mediaType === BANNER) { + bidResponse.ad = bid.adm; + } else if (bidResponse.mediaType === VIDEO) { + if (bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.rendererUrl = VIDEO_RENDERER_URL; + bidResponse.renderer = createRenderer(bidRequest); } - if (uspConsent && uspConsent.consentString) { - policyParam += `&ccpa_consent=${uspConsent.consentString}`; - } - const coppa = config.getConfig('coppa') ? 1 : 0; - policyParam += `&coppa=${coppa}`; - syncArr.push({ - type: 'iframe', - url: IFRAMESYNC + policyParam - }); - } else { - syncArr.push({ - type: 'image', - url: 'https://x.bidswitch.net/sync?ssp=tpmn' - }); - syncArr.push({ - type: 'image', - url: 'https://gocm.c.appier.net/tpmn' - }); - syncArr.push({ - type: 'image', - url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' - }); - syncArr.push({ - type: 'image', - url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' - }); } - return syncArr; + return bidResponse; }, -}; + overrides: { + imp: { + video(orig, imp, bidRequest, context) { + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + }, + }, + } +}); -registerBidder(spec); +function createRenderer(bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: VIDEO_RENDERER_URL, + config: utils.deepAccess(bid, 'renderer.options'), + loaded: false, + adUnitCode: bid.adUnitCode + }); -/** - * Creates site description object - */ -function createSite(refInfo) { - let url = parseUrl(refInfo.page || ''); - let site = { - 'domain': url.hostname, - 'page': url.protocol + '://' + url.hostname + url.pathname - }; - if (refInfo.ref) { - site.ref = refInfo.ref - } - let keywords = document.getElementsByTagName('meta')['keywords']; - if (keywords && keywords.content) { - site.keywords = keywords.content; + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); } - return site; + return renderer; } -function parseSize(size) { - let sizeObj = {} - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - return sizeObj; -} +function outstreamRender(bid, doc) { + bid.renderer.push(() => { + const win = utils.getWindowFromDocument(doc) || window; + win.ANOutstreamVideo.renderAd({ + sizes: [bid.playerWidth, bid.playerHeight], + targetId: bid.adUnitCode, + rendererOptions: bid.renderer.getConfig(), + adResponse: { content: bid.vastXml } -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); - } - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) + }, handleOutstreamRendererEvents.bind(null, bid)); + }); } -function getBannerSizes(bidRequest) { - return parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); } -function bidToRequest(bid) { - const bidObj = {}; - bidObj.sizes = getBannerSizes(bid); - - bidObj.inventoryId = bid.params.inventoryId; - bidObj.publisherId = bid.params.publisherId; - bidObj.bidId = bid.bidId; - bidObj.adUnitCode = bid.adUnitCode; - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - bidObj.auctionId = bid.auctionId; - - return bidObj; +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncArr = []; + if (syncOptions.iframeEnabled) { + let policyParam = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + policyParam += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + policyParam += `&coppa=${coppa}`; + syncArr.push({ + type: 'iframe', + url: IFRAMESYNC + '?' + policyParam + }); + } else { + syncArr.push({ + type: 'image', + url: 'https://x.bidswitch.net/sync?ssp=tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://gocm.c.appier.net/tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' + }); + syncArr.push({ + type: 'image', + url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' + }); + } + return syncArr; } diff --git a/modules/tpmnBidAdapter.md b/modules/tpmnBidAdapter.md index 8387528bb0f..3b016d7e5b2 100644 --- a/modules/tpmnBidAdapter.md +++ b/modules/tpmnBidAdapter.md @@ -11,10 +11,27 @@ Maintainer: develop@tpmn.co.kr Connects to TPMN exchange for bids. NOTE: -- TPMN bid adapter only supports Banner at the moment. +- TPMN bid adapter only supports MediaType BANNER, VIDEO. - Multi-currency is not supported. +- Please contact the TPMN sales team via email for "inventoryId" issuance. -# Sample Ad Unit Config + +# Bid Parameters + +## bids.params (Banner, Video) +***Pay attention to the case sensitivity.*** + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| -------------- | ----------- | ------------------------------------------ | ------------- | ------------ | +| `inventoryId` | required | Ad Inventory id TPMN | 123 | Number | +| `bidFloor` | recommended | Minimum price in USD. bidFloor applies to a specific unit. | 1.50 | Number | +| `bcat` | optional | IAB 5.1 Content Categories | ['IAB7-39'] | [String] | +| `badv` | optional | IAB Block list of advertisers by their domains | ['example.com'] | [String] | +| `bapp` | optional | IAB Block list of applications | ['com.blocked'] | [String] | + + +# Banner Ad Unit Config ``` var adUnits = [{ // Banner adUnit @@ -22,16 +39,77 @@ NOTE: mediaTypes: { banner: { sizes: [[300, 250], [320, 50]], // banner size + battr: [1,2,3] // optional } }, bids: [ { bidder: 'tpmn', params: { - inventoryId: '1', - publisherId: 'TPMN' + inventoryId: 1, // required + bidFloor: 2.0, // recommended + ... // bcat, badv, bapp // optional } } ] }]; +``` + + +# mediaTypes Parameters + +## mediaTypes.banner + +The following banner parameters are supported here so publishers may fully declare their banner inventory: + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| `sizes` | required | Avalaible sizes supported for banner ad unit | [ [300, 250], [300, 600] ] | [[Integer, Integer], [Integer, Integer]] | +| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] | +## mediaTypes.video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video` + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| `context` | required | instream or outstream |'outstream' | string | +| `playerSize` | required | Avalaible sizes supported for video ad unit. | [[300, 250]] | [Integer, Integer] | +| `mimes` | required | List of content MIME types supported by the player. | ['video/mp4']| [String]| +| `protocols` | optional | Supported video bid response protocol values. | [2,3,5,6] | [integers]| +| `api` | optional | Supported API framework values. | [2] | [integers] | +| `maxduration` | optional | Maximum video ad duration in seconds. | 30 | Integer | +| `minduration` | optional | Minimum video ad duration in seconds. | 6 | Integer | +| `startdelay` | optional | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | 0 | Integer | +| `placement` | optional | Placement type for the impression. | 1 | Integer | +| `minbitrate` | optional | Minimum bit rate in Kbps. | 300 | Integer | +| `maxbitrate` | optional | Maximum bit rate in Kbps. | 9600 | Integer | +| `playbackmethod` | optional | Playback methods that may be in use. Only one method is typically used in practice. | [2]| [Integers] | +| `linearity` | optional | OpenRTB2 linearity. in-strea,overlay... | 1 | Integer | +| `skip` | optional | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes . | 1 | Integer | +| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] | + + +# Video Ad Unit Config +``` + var adUnits = [{ + code: 'video-div', + mediaTypes: { + video: { + context: 'instream', // required + mimes: ['video/mp4'], // required + playerSize: [[ 640, 480 ]], // required + ... // skippable, startdelay, battr.. // optional + } + }, + bids: [{ + bidder: 'tpmn', + params: { + inventoryId: 2, // required + bidFloor: 2.0, // recommended + ... // bcat, badv, bapp // optional + } + }] + }]; ``` \ No newline at end of file diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index e2b14b18f61..505bc9d878f 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -1,16 +1,130 @@ /* eslint-disable no-tabs */ -import {expect} from 'chai'; -import {spec, storage} from 'modules/tpmnBidAdapter.js'; -import {generateUUID} from '../../../src/utils.js'; -import {newBidder} from '../../../src/adapters/bidderFactory'; +import { spec, storage, VIDEO_RENDERER_URL, ADAPTER_VERSION } from 'modules/tpmnBidAdapter.js'; +import { generateUUID } from '../../../src/utils.js'; +import { expect } from 'chai'; +import * as utils from 'src/utils'; import * as sinon from 'sinon'; +import 'modules/consentManagement.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {mockGdprConsent} from '../../helpers/consentData.js'; + +const BIDDER_CODE = 'tpmn'; +const BANNER_BID = { + bidder: BIDDER_CODE, + params: { + inventoryId: 1 + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const VIDEO_BID = { + bidder: BIDDER_CODE, + params: { + inventoryId: 1 + }, + mediaTypes: { + video: { + context: 'outstream', + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[1024, 768]], + protocols: [3, 4, 7, 8, 10], + placement: 1, + plcmt: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const BIDDER_REQUEST = { + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', + bidderRequestId: 'bidderRequestId', + timeout: 500, + refererInfo: { + page: 'https://hello-world-page.com/', + domain: 'hello-world-page.com', + ref: 'http://example-domain.com/foo', + } +}; + +const BANNER_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidId': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'w': 300, + 'h': 250 + } + ] + } + ], + 'cur': 'USD' +}; + +const VIDEO_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidid': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 1.09, + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'h': 768, + 'w': 1024 + } + ] + } + ], + 'cur': 'USD' +}; describe('tpmnAdapterTests', function () { - const adapter = newBidder(spec); - const BIDDER_CODE = 'tpmn'; let sandbox = sinon.sandbox.create(); let getCookieStub; - beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { tpmn: { @@ -27,152 +141,277 @@ describe('tpmnAdapterTests', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }); - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) - }); - - describe('isBidRequestValid', function () { - let bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', - params: { - inventoryId: '1', - publisherId: 'TPMN' - }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }; - - it('should return true if a bid is valid banner bid request', function () { - expect(spec.isBidRequestValid(bid)).to.be.equal(true); - }); - - it('should return false where requried param is missing', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); - }); - - it('should return false when required param values have invalid type', function () { - let bid = Object.assign({}, bid); - bid.params = { - 'inventoryId': null, - 'publisherId': null - }; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); - }); - }); - - describe('buildRequests', function () { - it('should return an empty list if there are no bid requests', function () { - const emptyBidRequests = []; - const bidderRequest = {}; - const request = spec.buildRequests(emptyBidRequests, bidderRequest); - expect(request).to.be.an('array').that.is.empty; - }); - it('should generate a POST server request with bidder API url, data', function () { - const bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', + describe('isBidRequestValid()', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: BIDDER_CODE, params: { - inventoryId: '1', - publisherId: 'TPMN' + inventoryId: 123 }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', mediaTypes: { banner: { sizes: [[300, 250]] } } }; - const tempBidRequests = [bid]; - const tempBidderRequest = { - refererInfo: { - page: 'http://localhost/test', - site: { - domain: 'localhost', - page: 'http://localhost/test' - } - } + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reject requests without params', function () { + let bid = { + bidder: BIDDER_CODE, + params: {} }; - const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest); - - expect(builtRequest).to.have.lengthOf(1); - expect(builtRequest[0].method).to.equal('POST'); - expect(builtRequest[0].url).to.match(/ad.tpmn.co.kr\/prebidhb.tpmn/); - const apiRequest = builtRequest[0].data; - expect(apiRequest.site).to.deep.equal({ - domain: 'localhost', - page: 'http://localhost/test' - }); - expect(apiRequest.bids).to.have.lengthOf('1'); - expect(apiRequest.bids[0].inventoryId).to.equal('1'); - expect(apiRequest.bids[0].publisherId).to.equal('TPMN'); - expect(apiRequest.bids[0].bidId).to.equal('29092404798c9'); - expect(apiRequest.bids[0].adUnitCode).to.equal('temp-unitcode'); - expect(apiRequest.bids[0].auctionId).to.equal('da1d7a33-0260-4e83-a621-14674116f3f9'); - expect(apiRequest.bids[0].sizes).to.have.lengthOf('1'); - expect(apiRequest.bids[0].sizes[0]).to.deep.equal({ - width: 300, - height: 250 - }); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(BANNER_BID)).to.equal(true); + expect(spec.isBidRequestValid(VIDEO_BID)).to.equal(true); }); }); - describe('interpretResponse', function () { - const bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', - params: { - inventoryId: '1', - publisherId: 'TPMN' - }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', - mediaTypes: { - banner: { - sizes: [[300, 250]] + describe('buildRequests()', function () { + it('should have gdpr data if applicable', function () { + const bid = utils.deepClone(BANNER_BID); + + const req = syncAddFPDToBidderRequest(Object.assign({}, BIDDER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, } + })); + let request = spec.buildRequests([bid], req)[0]; + + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); + }); + + it('should properly forward ORTB blocking params', function () { + let bid = utils.deepClone(BANNER_BID); + bid = utils.mergeDeep(bid, { + params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'], battr: [1] }, + mediaTypes: { banner: { battr: [1] } } + }); + + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + + expect(request).to.exist.and.to.be.an('object'); + const payload = request.data; + expect(payload).to.have.deep.property('bcat', ['IAB1-1']); + expect(payload).to.have.deep.property('badv', ['example.com']); + expect(payload).to.have.deep.property('bapp', ['com.example']); + expect(payload.imp[0].banner).to.have.deep.property('battr', [1]); + }); + + context('when mediaType is banner', function () { + it('should build correct request for banner bid with both w, h', () => { + const bid = utils.deepClone(BANNER_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const requestData = request.data; + // expect(requestData.imp[0].banner).to.equal(null); + expect(requestData.imp[0].banner.format[0].w).to.equal(300); + expect(requestData.imp[0].banner.format[0].h).to.equal(250); + }); + + it('should create request data', function () { + const bid = utils.deepClone(BANNER_BID); + + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', bid.bidId); + }); + }); + + context('when mediaType is video', function () { + if (FEATURES.VIDEO) { + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); } - }; - const tempBidRequests = [bid]; - it('should return an empty aray to indicate no valid bids', function () { - const emptyServerResponse = {}; - const bidResponses = spec.interpretResponse(emptyServerResponse, tempBidRequests); - expect(bidResponses).is.an('array').that.is.empty; + if (FEATURES.VIDEO) { + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + } + if (FEATURES.VIDEO) { + it('when mediaType is Video - check', () => { + const bid = utils.deepClone(VIDEO_BID); + const check = { + w: 1024, + h: 768, + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + api: [1, 2, 4, 6], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0, + plcmt: 1 + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + const requests = spec.buildRequests([bid], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video).to.deep.include({...check}); + }); + } + + if (FEATURES.VIDEO) { + it('when mediaType New Video', () => { + const NEW_VIDEO_BID = { + 'bidder': 'tpmn', + 'params': {'inventoryId': 2, 'bidFloor': 2}, + 'userId': {'pubcid': '88a49ee6-beeb-4dd6-92ac-3b6060e127e1'}, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': ['video/mp4'], + 'playerSize': [[1024, 768]], + 'playbackmethod': [2, 4, 6], + 'protocols': [3, 4], + 'api': [1, 2, 3, 6], + 'placement': 1, + 'minduration': 0, + 'maxduration': 30, + 'startdelay': 0, + 'skip': 1, + 'plcmt': 4 + } + }, + }; + + const check = { + w: 1024, + h: 768, + mimes: [ 'video/mp4' ], + playbackmethod: [2, 4, 6], + api: [1, 2, 3, 6], + protocols: [3, 4], + placement: 1, + minduration: 0, + maxduration: 30, + startdelay: 0, + skip: 1, + plcmt: 4 + } + + expect(spec.isBidRequestValid(NEW_VIDEO_BID)).to.equal(true); + let requests = spec.buildRequests([NEW_VIDEO_BID], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video.w).to.equal(check.w); + expect(request.imp[0].video.h).to.equal(check.h); + expect(request.imp[0].video.placement).to.equal(check.placement); + expect(request.imp[0].video.minduration).to.equal(check.minduration); + expect(request.imp[0].video.maxduration).to.equal(check.maxduration); + expect(request.imp[0].video.startdelay).to.equal(check.startdelay); + expect(request.imp[0].video.skip).to.equal(check.skip); + expect(request.imp[0].video.plcmt).to.equal(check.plcmt); + expect(request.imp[0].video.mimes).to.deep.have.same.members(check.mimes); + expect(request.imp[0].video.playbackmethod).to.deep.have.same.members(check.playbackmethod); + expect(request.imp[0].video.api).to.deep.have.same.members(check.api); + expect(request.imp[0].video.protocols).to.deep.have.same.members(check.protocols); + }); + } + + if (FEATURES.VIDEO) { + it('should use bidder video params if they are set', () => { + let bid = utils.deepClone(VIDEO_BID); + const check = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + plcmt: 1, + minduration: 0, + maxduration: 30, + startdelay: 0, + w: 640, + h: 480 + + }; + bid.mediaTypes.video = {...check}; + bid.mediaTypes.video.context = 'instream'; + bid.mediaTypes.video.playerSize = [[640, 480]]; + + expect(spec.isBidRequestValid(bid)).to.equal(true); + const requests = spec.buildRequests([bid], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video).to.deep.include({...check}); + }); + } }); - it('should return an empty array to indicate no valid bids', function () { - const mockBidResult = { - requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8', - cpm: 10.0, - creativeId: '1', - width: 300, - height: 250, - netRevenue: true, - currency: 'USD', - ttl: 1800, - ad: '', - adType: 'banner' - }; - const testServerResponse = { - headers: [], - body: [mockBidResult] - }; - const bidResponses = spec.interpretResponse(testServerResponse, tempBidRequests); - expect(bidResponses).deep.equal([mockBidResult]); + }); + + describe('interpretResponse()', function () { + context('when mediaType is banner', function () { + it('should correctly interpret valid banner response', function () { + const bid = utils.deepClone(BANNER_BID); + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const response = utils.deepClone(BANNER_BID_RESPONSE); + + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('banner'); + expect(bids[0].burl).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].ad).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].creativeId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(500); + expect(bids[0].netRevenue).to.equal(true); + }); + + it('should handle empty bid response', function () { + const bid = utils.deepClone(BANNER_BID); + + let request = spec.buildRequests([bid], BIDDER_REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} }); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); }); + if (FEATURES.VIDEO) { + context('when mediaType is video', function () { + it('should correctly interpret valid instream video response', () => { + const bid = utils.deepClone(VIDEO_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].burl).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].vastXml).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].rendererUrl).to.equal(VIDEO_RENDERER_URL); + expect(bids[0].creativeId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(500); + expect(bids[0].netRevenue).to.equal(true); + }); + }); + } }); describe('getUserSync', function () { From 01654d0b9172bb3049f252ca90218012e8853f8f Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:35:35 +0100 Subject: [PATCH 185/760] Improve Digital PG flag (#10718) --- modules/improvedigitalBidAdapter.js | 4 ++++ test/spec/modules/improvedigitalBidAdapter_spec.js | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index b56cc56a186..b563faf52ac 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -182,6 +182,10 @@ export const CONVERTER = ortbConverter({ })(); const bidResponse = buildBidResponse(bid, context); const idExt = deepAccess(bid, `ext.${BIDDER_CODE}`, {}); + // Programmatic guaranteed flag + if (idExt.pg === 1) { + bidResponse.adserverTargeting = { hb_deal_type_improve: 'pg' }; + } Object.assign(bidResponse, { dealId: (typeof idExt.buying_type === 'string' && idExt.buying_type !== 'rtb') ? idExt.line_item_id : undefined, netRevenue: idExt.is_net || false, diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index f427f9e7624..a86b9be73e6 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1181,6 +1181,16 @@ describe('Improve Digital Adapter Tests', function () { expect(bids[0].dealId).to.equal(268515); }); + it('should set deal type targeting KV for PG', function () { + const request = makeRequest(bidderRequest); + const response = deepClone(serverResponse); + let bids; + + response.body.seatbid[0].bid[0].ext.improvedigital.pg = 1; + bids = spec.interpretResponse(response, request); + expect(bids[0].adserverTargeting.hb_deal_type_improve).to.equal('pg'); + }); + it('should set currency', function () { const response = deepClone(serverResponse); response.body.cur = 'EUR'; From af4936a64b233f0e630bd3c43e95379dbcc76633 Mon Sep 17 00:00:00 2001 From: macinjosh32 Date: Mon, 13 Nov 2023 14:00:48 -0500 Subject: [PATCH 186/760] 33Across Analytics Adapter: initial release (#10635) * initial 33across Analytics Adapter commit * refactoring + additional unit test coverage * Additional refactoring of 33x analytics unit tests * Apply CR feedback - Single responsibility for transaction manager - Send one report per auction - Clear pending events that were causing conflicts in specs * refactoring of "incomplete state" unit test * unit test coverage for duplicate transaction id * Add additional spec about non found transaction * obtain user ids via bid request, not via global fn. * Populate bid response fields for BID WON events * Prepare the cache for the bidresponse field population * populate bidResponse field * fix some typedef + remove unnecessary switch event cases * Populate bids based on BID_RESPONSE and not on the bidsReceived field of AUCTION_END * Populate floor CMP field * fix eslint errors * keep track of bids when bid_response event is received * Additional fn documentation * change jsdoc naming + spec refactoring * remote siteId, make endpoint specification optional (provide default), refactoring, fix tests * remove extraneous logs * simplify GAM logging * remove hardcoded version * general improvements to documentation with fixes to jsdoc types * update tests for style conformance * IDG-677/remove code related to GAM slotRenderEnded * IDG-677/remove typegen hint, and replace with jsdoc ref * IDG-677/replace bidsWon with hasWon field, add sendReport trigger on auctionEnd (in the case that auction ends AND not all bids have resolved), and general refactoring * IDG-677/update tests to ensure reports always match new spec, and general refactoring * IDG-677/add fallback to use adUnitCode as slotId if "ortb2Imp.ext.{gpid,data.pbadslot}" values are both missing * remove accidental file commits Ticket: IDG-677 * test that report is being sent as soon as all transactions for auction are completed Ticket: IDG-677 * remove extraneous transaction manager counting & status mechanism and track transactionIds directly Ticket: IDG-677 * set default transaction timeout to 10s to better accommodate slower auctions, reverse auctionEnd and bidWon as the default sequence in test event emission, test for wider range of auction scenarios, improvements to clarity/specificity (naming) Ticket: IDG-677 * fix invalid timeout test Ticket: IDG-677 * add support for usp Ticket: IDG-677 * pre-clear event emitter in case state not cleared by other test suites Ticket: IDG-677 * tests: switch pre-clearing of "events" state with initiating our own stub of "events" emitter Ticket: IDG-677 * adopt default endpoint without logging when endpoint value is unspecified Ticket: IDG-677 * update docs to better reflect analytics adapter standard use-case and behavior Ticket: IDG-677 * refactoring: renaming and reducing mocks to only event data used Ticket: IDG-677 * remove usp consent string invalid test Ticket: IDG-677 * log values as copy Ticket: IDG-829 * fix early send issue by removing auto-send on auctionEnd and using "all GAM slots rendered" as an indicator for "complete enough" report after short delay Ticket: IDG-829 * add gdpr, gpp, and coppa support Ticket: IDG-803 * send gdprConsent string even if gdprApplies is false. Use array instead of comma string for gpp applicableSections. Ticket: IDG-803 * fix tests to reflect updated bid.status => hasWon logic Ticket: IDG-829 * remove errant log Ticket: IDG-829 * remove extraneous call to isGptPubadsDefined Ticket: IDG-829 * alter scope of subscribeToGamSlots Ticket: IDG-829 * combine bidStatus / getCachedBid functions, add our own BidStatus enum to track possible choices Ticket: IDG-829 * refactoring: renaming, fix reversion, better cloning for logs Ticket: IDG-829 * test label revision Ticket: IDG-829 * fix logic for test, "when an AUCTION_END event is received before BID_WON events: sends a report with the bids that have won after timeout" Ticket: IDG-829 * reorganization of functions, fix to BIDDER_ERROR issue Ticket: IDG-829 * remove try/catch convenience Ticket: IDG-829 * test slotRenderEnded timeout Ticket: IDG-829 * enable remainder of tests Ticket: IDG-829 * added additional GAM tests Ticket: IDG-829 * remove conditional language from GAM tests Ticket: IDG-829 * Use floorValue instead of cpmAfterAdjustments as the price floor. Resolves IDG-1093. * Mark bid as won on impression only. Resolves IDG-1107. * Obtain rejected bid ID from requestId instead of bidId. Include rejected bid information in the report. Resolves IDG-1112. * 33Across Analytics: Update DEFAULT_ENDPOINT to match production endpoint * 33Across Analytics: Update docs - change pid type to string, plus other minor tweaks. * 33Across Analytics docs: Add UIM reference * Log a warning on ad slot ID mismatch instead of throwing an unhandled exception * 33Across Analytics: Allow slots to be by configured by element ID instead of GAM ad unit code. Resolves IDG-1163. * 33Across Analytics: Update contact e-mail; add instructions on enabling existing customers forAnalytics. * 33Across Analytics spec: Ensure that mock GPT remains enabled after the tests finish --------- Co-authored-by: Michael Scott-Nelson Co-authored-by: Carlos Felix Co-authored-by: Joshua Poritz --- modules/33acrossAnalyticsAdapter.js | 656 ++++++++++ modules/33acrossAnalyticsAdapter.md | 76 ++ .../modules/33acrossAnalyticsAdapter_spec.js | 1163 +++++++++++++++++ 3 files changed, 1895 insertions(+) create mode 100644 modules/33acrossAnalyticsAdapter.js create mode 100644 modules/33acrossAnalyticsAdapter.md create mode 100644 test/spec/modules/33acrossAnalyticsAdapter_spec.js diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js new file mode 100644 index 00000000000..e3539906b13 --- /dev/null +++ b/modules/33acrossAnalyticsAdapter.js @@ -0,0 +1,656 @@ +import { deepAccess, logInfo, logWarn, logError, deepClone } from '../src/utils.js'; +import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager, { coppaDataHandler, gdprDataHandler, gppDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; + +/** + * @typedef {typeof import('../src/constants.json').EVENTS} EVENTS + */ +const { EVENTS } = CONSTANTS; + +/** @typedef {'pending'|'available'|'targetingSet'|'rendered'|'timeout'|'rejected'|'noBid'|'error'} BidStatus */ +/** + * @type {Object} + */ +const BidStatus = { + PENDING: 'pending', + AVAILABLE: 'available', + TARGETING_SET: 'targetingSet', + RENDERED: 'rendered', + TIMEOUT: 'timeout', + REJECTED: 'rejected', + NOBID: 'noBid', + ERROR: 'error', +} + +const ANALYTICS_VERSION = '1.0.0'; +const PROVIDER_NAME = '33across'; +const GVLID = 58; +/** Time to wait for all transactions in an auction to complete before sending the report */ +const DEFAULT_TRANSACTION_TIMEOUT = 10000; +/** Time to wait after all GAM slots have registered before sending the report */ +export const POST_GAM_TIMEOUT = 500; +export const DEFAULT_ENDPOINT = 'https://analytics.33across.com/api/v1/event'; + +export const log = getLogger(); + +/** + * @typedef {Object} AnalyticsReport - Sent when all bids are complete (as determined by `bidWon` and `slotRenderEnded` events) + * @property {string} analyticsVersion - Version of the Prebid.js 33Across Analytics Adapter + * @property {string} pid - Partner ID + * @property {string} src - Source of the report ('pbjs') + * @property {string} pbjsVersion - Version of Prebid.js + * @property {Auction[]} auctions + */ + +/** + * @typedef {Object} AnalyticsCache + * @property {string} pid Partner ID + * @property {Object} auctions + * @property {string} [usPrivacy] + */ + +/** + * @typedef {Object} Auction - Parsed auction data + * @property {AdUnit[]} adUnits + * @property {string} auctionId + * @property {string[]} userIds + */ + +/** + * @typedef {`${number}x${number}`} AdUnitSize + */ + +/** + * @typedef {('banner'|'native'|'video')} AdUnitMediaType + */ + +/** + * @typedef {Object} BidResponse + * @property {number} cpm + * @property {string} cur + * @property {number} [cpmOrig] + * @property {number} cpmFloor + * @property {AdUnitMediaType} mediaType + * @property {AdUnitSize} size + */ + +/** + * @typedef {Object} Bid - Parsed bid data + * @property {string} bidder + * @property {string} bidId + * @property {string} source + * @property {string} status + * @property {BidResponse} [bidResponse] + * @property {1|0} [hasWon] + */ + +/** + * @typedef {Object} AdUnit - Parsed adUnit data + * @property {string} transactionId - Primary key for *this* auction/adUnit combination + * @property {string} adUnitCode + * @property {string} slotId - Equivalent to GPID. (Note that + * GPID supports adUnits where multiple units have the same `code` values + * by appending a `#UNIQUIFIER`. The value of the UNIQUIFIER is likely to be the div-id, + * but, if div-id is randomized / unavailable, may be something else like the media size) + * @property {Array} mediaTypes + * @property {Array} sizes + * @property {Array} bids + */ + +/** + * After the first transaction begins, wait until all transactions are complete + * before calling `onComplete`. If the timeout is reached before all transactions + * are complete, send the report anyway. + * + * Use this to track all transactions per auction, and send the report as soon + * as all adUnits have been won (or after timeout) even if other bid/auction + * activity is still happening. + */ +class TransactionManager { + /** + * Milliseconds between activity to allow until this collection automatically completes. + * @type {number} + */ + #sendTimeout; + #sendTimeoutId; + #transactionsPending = new Set(); + #transactionsCompleted = new Set(); + #onComplete; + + constructor({ timeout, onComplete }) { + this.#sendTimeout = timeout; + this.#onComplete = onComplete; + } + + status() { + return { + pending: [...this.#transactionsPending], + completed: [...this.#transactionsCompleted], + }; + } + + initiate(transactionId) { + this.#transactionsPending.add(transactionId); + this.#restartSendTimeout(); + } + + complete(transactionId) { + if (!this.#transactionsPending.has(transactionId)) { + log.warn(`transactionId "${transactionId}" was not found. No transaction to mark as complete.`); + return; + } + + this.#transactionsPending.delete(transactionId); + this.#transactionsCompleted.add(transactionId); + + if (this.#transactionsPending.size === 0) { + this.#flushTransactions(); + } + } + + #flushTransactions() { + this.#clearSendTimeout(); + this.#transactionsPending = new Set(); + this.#onComplete(); + } + + // gulp-eslint is using eslint 6, a version that doesn't support private method syntax + // eslint-disable-next-line no-dupe-class-members + #clearSendTimeout() { + return clearTimeout(this.#sendTimeoutId); + } + + // eslint-disable-next-line no-dupe-class-members + #restartSendTimeout() { + this.#clearSendTimeout(); + + this.#sendTimeoutId = setTimeout(() => { + if (this.#sendTimeout !== 0) { + log.warn(`Timed out waiting for ad transactions to complete. Sending report.`); + } + + this.#flushTransactions(); + }, this.#sendTimeout); + } +} + +/** + * Initialized during `enableAnalytics`. Exported for testing purposes. + */ +export const locals = { + /** @type {Object} - one manager per auction */ + transactionManagers: {}, + /** @type {AnalyticsCache} */ + cache: { + auctions: {}, + pid: '', + }, + /** @type {Object} */ + adUnitMap: {}, + reset() { + this.transactionManagers = {}; + this.cache = { + auctions: {}, + pid: '', + }; + this.adUnitMap = {}; + } +} + +/** + * @typedef {Object} AnalyticsAdapter + * @property {function} track + * @property {function} enableAnalytics + * @property {function} disableAnalytics + * @property {function} [originEnableAnalytics] + * @property {function} [originDisableAnalytics] + * @property {function} [_oldEnable] + */ + +/** + * @type {AnalyticsAdapter} + */ +const analyticsAdapter = Object.assign( + buildAdapter({ analyticsType: 'endpoint' }), + { track: analyticEventHandler } +); + +analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; +analyticsAdapter.enableAnalytics = enableAnalyticsWrapper; + +/** + * @typedef {Object} AnalyticsConfig + * @property {string} provider - set by pbjs at module registration time + * @property {Object} options + * @property {string} options.pid - Publisher/Partner ID + * @property {string} [options.endpoint=DEFAULT_ENDPOINT] - Endpoint to send analytics data + * @property {number} [options.timeout=DEFAULT_TRANSACTION_TIMEOUT] - Timeout for sending analytics data + */ + +/** + * @param {AnalyticsConfig} config Analytics module configuration + */ +function enableAnalyticsWrapper(config) { + const { options } = config; + + const pid = options.pid; + if (!pid) { + log.error('No partnerId provided for "options.pid". No analytics will be sent.'); + + return; + } + + const endpoint = calculateEndpoint(options.endpoint); + this.getUrl = () => endpoint; + + const timeout = calculateTransactionTimeout(options.timeout); + this.getTimeout = () => timeout; + + locals.cache = { + pid, + auctions: {}, + }; + + window.googletag = window.googletag || { cmd: [] }; + window.googletag.cmd.push(subscribeToGamSlots); + + analyticsAdapter.originEnableAnalytics(config); +} + +/** + * @param {string} [endpoint] + * @returns {string} + */ +function calculateEndpoint(endpoint = DEFAULT_ENDPOINT) { + if (typeof endpoint === 'string' && endpoint.startsWith('http')) { + return endpoint; + } + + log.info(`Invalid endpoint provided for "options.endpoint". Using default endpoint.`); + + return DEFAULT_ENDPOINT; +} +/** + * @param {number} [configTimeout] + * @returns {number} Transaction Timeout + */ +function calculateTransactionTimeout(configTimeout = DEFAULT_TRANSACTION_TIMEOUT) { + if (typeof configTimeout === 'number' && configTimeout >= 0) { + return configTimeout; + } + + log.info(`Invalid timeout provided for "options.timeout". Using default timeout of ${DEFAULT_TRANSACTION_TIMEOUT}ms.`); + + return DEFAULT_TRANSACTION_TIMEOUT; +} + +function subscribeToGamSlots() { + window.googletag.pubads().addEventListener('slotRenderEnded', event => { + setTimeout(() => { + const { transactionId, auctionId } = + getAdUnitMetadata(event.slot.getAdUnitPath(), event.slot.getSlotElementId()); + if (!transactionId || !auctionId) { + const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; + log.warn('Could not find configured ad unit matching GAM render of slot:', { slotName }); + return; + } + + locals.transactionManagers[auctionId] && + locals.transactionManagers[auctionId].complete(transactionId); + }, POST_GAM_TIMEOUT); + }); +} + +function getAdUnitMetadata(adUnitPath, adSlotElementId) { + const adUnitMeta = locals.adUnitMap[adUnitPath] || locals.adUnitMap[adSlotElementId]; + if (adUnitMeta && adUnitMeta.length > 0) { + return adUnitMeta[adUnitMeta.length - 1]; + } + return {}; +} + +/** necessary for testing */ +analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; +analyticsAdapter.disableAnalytics = function () { + analyticsAdapter._oldEnable = enableAnalyticsWrapper; + locals.reset(); + analyticsAdapter.originDisableAnalytics(); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: analyticsAdapter, + code: PROVIDER_NAME, + gvlid: GVLID, +}); + +export default analyticsAdapter; + +/** + * @param {AnalyticsCache} analyticsCache + * @param {string} completedAuctionId value of auctionId + * @return {AnalyticsReport} Analytics report + */ +function createReportFromCache(analyticsCache, completedAuctionId) { + const { pid, auctions } = analyticsCache; + + const report = { + pid, + src: 'pbjs', + analyticsVersion: ANALYTICS_VERSION, + pbjsVersion: '$prebid.version$', // Replaced by build script + auctions: [ auctions[completedAuctionId] ], + } + if (uspDataHandler.getConsentData()) { + report.usPrivacy = uspDataHandler.getConsentData(); + } + + if (gdprDataHandler.getConsentData()) { + report.gdpr = Number(Boolean(gdprDataHandler.getConsentData().gdprApplies)); + report.gdprConsent = gdprDataHandler.getConsentData().consentString || ''; + } + + if (gppDataHandler.getConsentData()) { + report.gpp = gppDataHandler.getConsentData().gppString; + report.gppSid = gppDataHandler.getConsentData().applicableSections; + } + + if (coppaDataHandler.getCoppa()) { + report.coppa = Number(coppaDataHandler.getCoppa()); + } + + return report; +} + +function getCachedBid(auctionId, bidId) { + const auction = locals.cache.auctions[auctionId]; + for (let adUnit of auction.adUnits) { + for (let bid of adUnit.bids) { + if (bid.bidId === bidId) { + return bid; + } + } + } + log.error(`Cannot find bid "${bidId}" in auction "${auctionId}".`); +}; + +/** + * @param {Object} args + * @param {Object} args.args Event data + * @param {EVENTS[keyof EVENTS]} args.eventType + */ +function analyticEventHandler({ eventType, args }) { + if (!locals.cache) { + log.error('Something went wrong. Analytics cache is not initialized.'); + return; + } + + switch (eventType) { + case EVENTS.AUCTION_INIT: + onAuctionInit(args); + break; + case EVENTS.BID_REQUESTED: // BidStatus.PENDING + onBidRequested(args); + break; + case EVENTS.BID_TIMEOUT: + for (let bid of args) { + setCachedBidStatus(bid.auctionId, bid.bidId, BidStatus.TIMEOUT); + } + break; + case EVENTS.BID_RESPONSE: + onBidResponse(args); + break; + case EVENTS.BID_REJECTED: + onBidRejected(args); + break; + case EVENTS.NO_BID: + case EVENTS.SEAT_NON_BID: + setCachedBidStatus(args.auctionId, args.bidId, BidStatus.NOBID); + break; + case EVENTS.BIDDER_ERROR: + if (args.bidderRequest && args.bidderRequest.bids) { + for (let bid of args.bidderRequest.bids) { + setCachedBidStatus(args.bidderRequest.auctionId, bid.bidId, BidStatus.ERROR); + } + } + break; + case EVENTS.AUCTION_END: + onAuctionEnd(args); + break; + case EVENTS.BID_WON: // BidStatus.TARGETING_SET | BidStatus.RENDERED | BidStatus.ERROR + onBidWon(args); + break; + default: + break; + } +} + +/**************** + * AUCTION_INIT * + ***************/ +function onAuctionInit({ adUnits, auctionId, bidderRequests }) { + if (typeof auctionId !== 'string' || !Array.isArray(bidderRequests)) { + log.error('Analytics adapter failed to parse auction.'); + return; + } + + locals.cache.auctions[auctionId] = { + auctionId, + adUnits: adUnits.map(au => { + setAdUnitMap(au.code, auctionId, au.transactionId); + + return { + transactionId: au.transactionId, + adUnitCode: au.code, + // Note: GPID supports adUnits that have matching `code` values by appending a `#UNIQUIFIER`. + // The value of the UNIQUIFIER is likely to be the div-id, + // but, if div-id is randomized / unavailable, may be something else like the media size) + slotId: deepAccess(au, 'ortb2Imp.ext.gpid') || deepAccess(au, 'ortb2Imp.ext.data.pbadslot', au.code), + mediaTypes: Object.keys(au.mediaTypes), + sizes: au.sizes.map(size => size.join('x')), + bids: [], + } + }), + userIds: Object.keys(deepAccess(bidderRequests, '0.bids.0.userId', {})), + }; + + locals.transactionManagers[auctionId] ||= + new TransactionManager({ + timeout: analyticsAdapter.getTimeout(), + onComplete() { + sendReport( + createReportFromCache(locals.cache, auctionId), + analyticsAdapter.getUrl() + ); + delete locals.transactionManagers[auctionId]; + } + }); +} + +function setAdUnitMap(adUnitCode, auctionId, transactionId) { + if (!locals.adUnitMap[adUnitCode]) { + locals.adUnitMap[adUnitCode] = []; + } + + locals.adUnitMap[adUnitCode].push({ auctionId, transactionId }); +} + +/***************** + * BID_REQUESTED * + ****************/ +function onBidRequested({ auctionId, bids }) { + for (let { bidder, bidId, transactionId, src } of bids) { + const auction = locals.cache.auctions[auctionId]; + const adUnit = auction.adUnits.find(adUnit => adUnit.transactionId === transactionId); + if (!adUnit) return; + adUnit.bids.push({ + bidder, + bidId, + status: BidStatus.PENDING, + hasWon: 0, + source: src, + }); + + // if there is no manager for this auction, then the auction has already been completed + locals.transactionManagers[auctionId] && + locals.transactionManagers[auctionId].initiate(transactionId); + } +} + +/**************** + * BID_RESPONSE * + ***************/ +function onBidResponse({ requestId, auctionId, cpm, currency, originalCpm, floorData, mediaType, size, status, source }) { + const bid = getCachedBid(auctionId, requestId); + if (!bid) return; + + setBidStatus(bid, status); + Object.assign(bid, + { + bidResponse: { + cpm, + cur: currency, + cpmOrig: originalCpm, + cpmFloor: floorData?.floorValue, + mediaType, + size + }, + source + } + ); +} + +/**************** + * BID_REJECTED * + ***************/ +function onBidRejected({ requestId, auctionId, cpm, currency, originalCpm, floorData, mediaType, width, height, source }) { + const bid = getCachedBid(auctionId, requestId); + if (!bid) return; + + setBidStatus(bid, BidStatus.REJECTED); + Object.assign(bid, + { + bidResponse: { + cpm, + cur: currency, + cpmOrig: originalCpm, + cpmFloor: floorData?.floorValue, + mediaType, + size: `${width}x${height}` + }, + source + } + ); +} + +/*************** + * AUCTION_END * + **************/ +/** + * @param {Object} args + * @param {{requestId: string, status: string}[]} args.bidsReceived + * @param {string} args.auctionId + * @returns {void} + */ +function onAuctionEnd({ bidsReceived, auctionId }) { + for (let bid of bidsReceived) { + setCachedBidStatus(auctionId, bid.requestId, bid.status); + } +} + +/*********** + * BID_WON * + **********/ +function onBidWon(bidWon) { + const { auctionId, requestId, transactionId } = bidWon; + const bid = getCachedBid(auctionId, requestId); + if (!bid) { + return; + } + + setBidStatus(bid, bidWon.status ?? BidStatus.ERROR); + + locals.transactionManagers[auctionId] && + locals.transactionManagers[auctionId].complete(transactionId); +} + +/** + * @param {Bid} bid + * @param {BidStatus} [status] + * @returns {void} + */ +function setBidStatus(bid, status = BidStatus.AVAILABLE) { + const statusStates = { + pending: { + next: [BidStatus.AVAILABLE, BidStatus.TARGETING_SET, BidStatus.RENDERED, BidStatus.TIMEOUT, BidStatus.REJECTED, BidStatus.NOBID, BidStatus.ERROR], + }, + available: { + next: [BidStatus.TARGETING_SET, BidStatus.RENDERED, BidStatus.TIMEOUT, BidStatus.REJECTED, BidStatus.NOBID, BidStatus.ERROR], + }, + targetingSet: { + next: [BidStatus.RENDERED, BidStatus.ERROR, BidStatus.TIMEOUT], + }, + rendered: { + next: [], + }, + timeout: { + next: [], + }, + rejected: { + next: [], + }, + noBid: { + next: [], + }, + error: { + next: [BidStatus.TARGETING_SET, BidStatus.RENDERED, BidStatus.TIMEOUT, BidStatus.REJECTED, BidStatus.NOBID, BidStatus.ERROR], + }, + } + + const winningStatuses = [BidStatus.RENDERED]; + + if (statusStates[bid.status].next.includes(status)) { + bid.status = status; + if (winningStatuses.includes(status)) { + // occassionally we can detect a bidWon before prebid reports it as such + bid.hasWon = 1; + } + } +} + +function setCachedBidStatus(auctionId, bidId, status) { + const bid = getCachedBid(auctionId, bidId); + if (!bid) return; + setBidStatus(bid, status); +} + +/** + * Guarantees sending of data without waiting for response, even after page is left/closed + * + * @param {AnalyticsReport} report Request payload + * @param {string} endpoint URL + */ +function sendReport(report, endpoint) { + if (navigator.sendBeacon(endpoint, JSON.stringify(report))) { + log.info(`Analytics report sent to ${endpoint}`, report); + + return; + } + + log.error('Analytics report exceeded User-Agent data limits and was not sent.', report); +} + +/** + * Encapsulate certain logger functions and add a prefix to the final messages. + * + * @return {Object} New logger functions + */ +function getLogger() { + const LPREFIX = `${PROVIDER_NAME} Analytics: `; + + return { + info: (msg, ...args) => logInfo(`${LPREFIX}${msg}`, ...deepClone(args)), + warn: (msg, ...args) => logWarn(`${LPREFIX}${msg}`, ...deepClone(args)), + error: (msg, ...args) => logError(`${LPREFIX}${msg}`, ...deepClone(args)), + } +} diff --git a/modules/33acrossAnalyticsAdapter.md b/modules/33acrossAnalyticsAdapter.md new file mode 100644 index 00000000000..c56059e5526 --- /dev/null +++ b/modules/33acrossAnalyticsAdapter.md @@ -0,0 +1,76 @@ +# Overview + +```txt +Module Name: 33Across Analytics Adapter +Module Type: Analytics Adapter +Maintainer: analytics_support@33across.com +``` + +#### About + +This analytics adapter collects data about the performance of your ad slots +for each auction run on your site. It also provides insight into how identifiers +from the +[33Across User ID Sub-module](https://docs.prebid.org/dev-docs/modules/userid-submodules/33across.html) +and other user ID sub-modules improve your monetization. The data is sent at +the earliest opportunity for each auction to provide a more complete picture of +your ad performance. + +The analytics adapter is free to use! +However, the publisher must work with our account management team to obtain a +Publisher/Partner ID (PID) and enable Analytics for their account. +To get a PID and to have the publisher account enabled for Analytics, +you can reach out to our team at the following email - analytics_support@33across.com + +If you are an existing publisher and you already use a 33Across PID, +you can reach out to analytics_support@33across.com +to have your account enabled for analytics. + +The 33Across privacy policy is at . + +#### Analytics Options + +| Name | Scope | Example | Type | Description | +|-----------|----------|---------|----------|-------------| +| `pid` | required | abc123 | `string` | 33Across Publisher ID | +| `timeout` | optional | 10000 | `int` | Milliseconds to wait after last seen auction transaction before sending report (default 10000). | + +#### Configuration + +The data is sent at the earliest opportunity for each auction to provide +a more complete picture of your ad performance, even if the auction is interrupted +by a page navigation. At the latest, the adapter will always send the report +when the page is unloaded, at the end of the auction, or after the timeout, +whichever comes first. + +In order to guarantee consistent reports of your ad slot behavior, we recommend +including the GPT Pre-Auction Module, `gptPreAuction`. This module is included +by default when Prebid is downloaded. If you are compiling from source, +this might look something like: + +```sh +gulp bundle --modules=gptPreAuction,consentManagement,consentManagementGpp,consentManagementUsp,enrichmentFpdModule,gdprEnforcement,33acrossBidAdapter,33acrossIdSystem,33acrossAnalyticsAdapter +``` + +Enable the 33Across Analytics Adapter in Prebid.js using the analytics provider `33across` +and options as seen in the example below. + +#### Example Configuration + +```js +pbjs.enableAnalytics({ + provider: '33across', + options: { + /** + * The 33Across Publisher ID. + */ + pid: 'abc123', + /** + * Timeout in milliseconds after which an auction report + * will be sent regardless of auction state. + * [optional] + */ + timeout: 10000 + } +}); +``` diff --git a/test/spec/modules/33acrossAnalyticsAdapter_spec.js b/test/spec/modules/33acrossAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9e0d928cd97 --- /dev/null +++ b/test/spec/modules/33acrossAnalyticsAdapter_spec.js @@ -0,0 +1,1163 @@ +// @ts-nocheck +import analyticsAdapter from 'modules/33acrossAnalyticsAdapter.js'; +import { log } from 'modules/33acrossAnalyticsAdapter.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; +import * as events from 'src/events.js'; +import * as faker from 'faker'; +import CONSTANTS from 'src/constants.json'; +import { gdprDataHandler, gppDataHandler, uspDataHandler } from '../../../src/adapterManager'; +import { DEFAULT_ENDPOINT, POST_GAM_TIMEOUT, locals } from '../../../modules/33acrossAnalyticsAdapter'; +const { EVENTS, BID_STATUS } = CONSTANTS; + +describe('33acrossAnalyticsAdapter:', function () { + let sandbox; + let assert = getLocalAssert(); + + beforeEach(function () { + mockGpt.reset(); + + sandbox = sinon.createSandbox({ + useFakeTimers: { + now: new Date(2023, 3, 3, 0, 1, 33, 425), + }, + }); + + sandbox.stub(events, 'getEvents').returns([]); + + sandbox.spy(log, 'info'); + sandbox.spy(log, 'warn'); + sandbox.spy(log, 'error'); + + sandbox.stub(navigator, 'sendBeacon').callsFake(function (url, data) { + const json = JSON.parse(data); + assert.isValidAnalyticsReport(json); + + return true; + }); + }); + + afterEach(function () { + analyticsAdapter.disableAnalytics(); + mockGpt.enable(); + sandbox.restore(); + }); + + describe('enableAnalytics:', function () { + context('When pid is given', function () { + context('but endpoint is not', function () { + it('uses the default endpoint', function () { + analyticsAdapter.enableAnalytics({ + options: { + pid: 'test-pid', + }, + }); + + assert.equal(analyticsAdapter.getUrl(), DEFAULT_ENDPOINT); + }); + }); + + context('but the endpoint is invalid', function () { + it('logs an info message', function () { + analyticsAdapter.enableAnalytics({ + options: { + pid: 'test-pid', + endpoint: 'foo' + }, + }); + + assert.calledWithExactly(log.info, 'Invalid endpoint provided for "options.endpoint". Using default endpoint.'); + }); + }); + }); + + context('When endpoint is given', function () { + context('but pid is not', function () { + it('logs an error message', function () { + analyticsAdapter.enableAnalytics({ + options: { + endpoint: faker.internet.url() + }, + }); + + assert.calledWithExactly(log.error, 'No partnerId provided for "options.pid". No analytics will be sent.'); + }); + }); + }); + + context('When pid and endpoint are given', function () { + context('and an invalid timeout config value is given', function () { + it('logs an info message', function () { + [null, 'foo', -1].forEach(timeout => { + analyticsAdapter.enableAnalytics({ + options: { + pid: 'test-pid', + endpoint: 'http://test-endpoint', + timeout + }, + }); + analyticsAdapter.disableAnalytics(); + + assert.calledWithExactly(log.info, 'Invalid timeout provided for "options.timeout". Using default timeout of 10000ms.'); + log.info.resetHistory(); + }); + }); + }); + }); + }); + + // check that upcoming tests are derived from a valid report + describe('Report Mocks', function () { + it('the report should have the correct format', function () { + assert.isValidAnalyticsReport(createReportWithThreeBidWonEvents()); + }); + }); + + describe('Event Handling', function () { + beforeEach(function () { + this.defaultTimeout = 10000; + this.enableAnalytics = (options) => { + analyticsAdapter.enableAnalytics({ + options: { + endpoint: 'http://test-endpoint', + pid: 'test-pid', + timeout: this.defaultTimeout, + ...options + }, + }); + window.googletag.cmd.forEach(cmd => cmd()); + } + }); + + context('when an auction is complete', function () { + context('and the AnalyticsReport is sent successfully to the given endpoint', function () { + it('calls "sendBeacon" with all won bids', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + const [url, jsonString] = navigator.sendBeacon.firstCall.args; + const { auctions } = JSON.parse(jsonString); + + assert.lengthOf(mapToBids(auctions).filter(bid => bid.hasWon), 3); + }); + + it('calls "sendBeacon" with the correct report string', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, endpoint, createReportWithThreeBidWonEvents()); + }); + + it('logs an info message containing the report', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())) + .returns(true); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledWithExactly(log.info, `Analytics report sent to ${endpoint}`, createReportWithThreeBidWonEvents()); + }); + + it('calls "sendBeacon" as soon as all values are available (before timeout)', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + performStandardAuction(); + sandbox.clock.tick(1); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, endpoint, createReportWithThreeBidWonEvents()); + }); + }); + + context('and a valid US Privacy configuration is present', function () { + ['1YNY', '1---', '1NY-', '1Y--', '1--Y', '1N--', '1--N', '1NNN'].forEach(consent => { + it(`calls "sendBeacon" with a report containing the "${consent}" privacy string`, function () { + sandbox.stub(uspDataHandler, 'getConsentData').returns(consent); + this.enableAnalytics(); + + const reportWithConsent = { + ...createReportWithThreeBidWonEvents(), + usPrivacy: consent + }; + navigator.sendBeacon + .withArgs('http://test-endpoint', reportWithConsent); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, 'http://test-endpoint', reportWithConsent); + }); + }); + }); + + context('and a GDPR Privacy configuration is present', function () { + it('it calls "sendBeacon" with a report containing the GDPR consent string', function () { + sandbox.stub(gdprDataHandler, 'getConsentData').returns({ + consentString: 'foo', + gdprApplies: true + }); + this.enableAnalytics(); + + const reportWithConsent = { + ...createReportWithThreeBidWonEvents(), + gdpr: 1, + gdprConsent: 'foo' + }; + navigator.sendBeacon + .withArgs('http://test-endpoint', reportWithConsent); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, 'http://test-endpoint', reportWithConsent); + }); + }); + }); + + context('when an auction is complete and a GPP configuration is present', function () { + it('it calls "sendBeacon" with a report containing the GPP consent string', function () { + sandbox.stub(gppDataHandler, 'getConsentData').returns({ + gppString: 'gppString', + applicableSections: [7] + }); + this.enableAnalytics(); + + const reportWithConsent = { + ...createReportWithThreeBidWonEvents(), + gpp: 'gppString', + gppSid: [7] + }; + navigator.sendBeacon + .withArgs('http://test-endpoint', reportWithConsent); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, 'http://test-endpoint', reportWithConsent); + }); + }); + + context('when an error occurs while sending the AnalyticsReport', function () { + it('logs an error', function () { + this.enableAnalytics(); + navigator.sendBeacon.returns(false); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledWithExactly(log.error, 'Analytics report exceeded User-Agent data limits and was not sent.', createReportWithThreeBidWonEvents()); + }); + }); + + context('when an auction report was already sent', function () { + context('and a new bid won event is returned after the report completes', function () { + it('finishes the auction without error', function () { + const incompleteAnalyticsReport = createReportWithThreeBidWonEvents(); + incompleteAnalyticsReport.auctions.forEach(auction => { + auction.adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + delete bid.bidResponse; + bid.hasWon = 0; + bid.status = 'pending'; + }); + }); + }); + + this.enableAnalytics(); + const { prebid: [auction] } = getMockEvents(); + + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + for (let bidRequestedEvent of auction.BID_REQUESTED) { + events.emit(EVENTS.BID_REQUESTED, bidRequestedEvent); + }; + + sandbox.clock.tick(this.defaultTimeout + 1000); + + for (let bidResponseEvent of auction.BID_RESPONSE) { + events.emit(EVENTS.BID_RESPONSE, bidResponseEvent); + }; + for (let bidWonEvent of auction.BID_WON) { + events.emit(EVENTS.BID_WON, bidWonEvent); + }; + + events.emit(EVENTS.AUCTION_END, auction.AUCTION_END); + + sandbox.clock.tick(1); + + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, 'http://test-endpoint', incompleteAnalyticsReport); + }); + }); + + context('and another auction completes after that', function () { + it('sends the new report', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledTwice(navigator.sendBeacon); + }); + }); + }); + + context('when two auctions overlap', function() { + it('sends a report for each auction', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + performStandardAuction(); + performStandardAuction(); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledTwice(navigator.sendBeacon); + }); + }); + + context('when an AUCTION_END event is received before BID_WON events', function () { + it('sends a report with the bids that have won after all bids are won', function () { + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + navigator.sendBeacon + .withArgs(endpoint, JSON.stringify(createReportWithThreeBidWonEvents())); + + const { prebid: [auction] } = getMockEvents(); + + performStandardAuction({ exclude: [EVENTS.BID_WON] }); + + assert.notCalled(navigator.sendBeacon); + for (let bidWon of auction.BID_WON) { + events.emit(EVENTS.BID_WON, bidWon); + } + assert.calledOnceWithStringJsonEquivalent(navigator.sendBeacon, endpoint, createReportWithThreeBidWonEvents()); + }); + }); + + context('when a BID_WON event is received', function () { + context('and there is no record of that bid being requested', function () { + it('logs a warning message', function () { + this.enableAnalytics(); + + const mockEvents = getMockEvents(); + const { prebid } = mockEvents; + const [auction] = prebid; + + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + + const fakeBidWonEvent = Object.assign(auction.BID_WON[0], { + transactionId: 'foo' + }) + + events.emit(EVENTS.BID_WON, fakeBidWonEvent); + + const { auctionId, requestId } = fakeBidWonEvent; + assert.calledWithExactly(log.error, `Cannot find bid "${requestId}" in auction "${auctionId}".`); + }); + }); + }); + + context('when a BID_REJECTED event is received', function () { + it(`marks the rejected bid as "rejected"`, function () { + this.enableAnalytics(); + + const auction = getMockEvents().prebid[0]; + + // Start the auction + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + for (let bidRequestedEvent of auction.BID_REQUESTED) { + events.emit(EVENTS.BID_REQUESTED, bidRequestedEvent); + }; + + // Reject first bid + const bidToReject = auction.BID_REQUESTED[0].bids[0]; + events.emit(EVENTS.BID_REJECTED, auction.BID_REJECTED[0]); + + // Accept remaining bids + for (let i = 1; i < auction.BID_RESPONSE.length; ++i) { + events.emit(EVENTS.BID_RESPONSE, auction.BID_RESPONSE[i]); + }; + + // Complete the auction + events.emit(EVENTS.AUCTION_END, auction.AUCTION_END); + + sandbox.clock.tick(this.defaultTimeout + 1); + + // Verify that we detected that the first bid was rejected + const expectedRejectedBid = JSON.parse(navigator.sendBeacon.firstCall.args[1]).auctions[0].adUnits[0].bids[0]; + assert.strictEqual(expectedRejectedBid.status, 'rejected'); + }); + }); + + context('when a transaction does not reach its complete state', function () { + context('and a timeout config value has been given', function () { + context('and the timeout value has elapsed', function () { + it('logs a warning', function () { + const timeout = 2000; + this.enableAnalytics({ timeout }); + + performStandardAuction({exclude: ['bidWon', 'slotRenderEnded', 'auctionEnd']}); + + sandbox.clock.tick(timeout + 1000); + + assert.calledWithExactly(log.warn, 'Timed out waiting for ad transactions to complete. Sending report.'); + }); + + it(`marks timed out bids as "timeout"`, function () { + const timeout = 2000; + this.enableAnalytics({ timeout }); + const request = getMockEvents().prebid[0].BID_REQUESTED[0]; + const bidToTimeout = request.bids[0]; + + performStandardAuction({exclude: ['bidWon', 'slotRenderEnded', 'auctionEnd']}); + sandbox.clock.tick(1); + events.emit(EVENTS.BID_TIMEOUT, [{ + auctionId: request.auctionId, + bidId: bidToTimeout.bidId, + transactionId: bidToTimeout.transactionId, + }]); + sandbox.clock.tick(timeout + 1000); + + const timeoutBid = JSON.parse(navigator.sendBeacon.firstCall.args[1]).auctions[0].adUnits[0].bids[0]; + assert.strictEqual(timeoutBid.status, 'timeout'); + }); + }); + }); + + context('and a timeout config value has not been given', function () { + context('and the default timeout has elapsed', function () { + it('logs an error', function () { + this.enableAnalytics(); + + performStandardAuction({exclude: ['bidWon', 'slotRenderEnded', 'auctionEnd']}); + + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledWithExactly(log.warn, 'Timed out waiting for ad transactions to complete. Sending report.'); + }); + }) + }); + + context('and the `slotRenderEnded` event fired for all bids, but not all bids have won', function () { + context('and the GAM slot IDs are configured as the ad unit codes', function () { + it('sends a report after the all `slotRenderEnded` events have fired and timed out', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({ timeout }); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + + assert.strictEqual(navigator.sendBeacon.callCount, 1); + }); + }); + + context('and the slot element IDs are configured as the ad unit codes', function () { + it('sends a report after the all `slotRenderEnded` events have fired and timed out', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({ timeout }); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd'], useSlotElementIds: true}); + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + + assert.strictEqual(navigator.sendBeacon.callCount, 1); + }); + }); + + it('does NOT send a report if not all `slotRenderEnded` events have timed out', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({ timeout }); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); + sandbox.clock.tick(POST_GAM_TIMEOUT - 1); + + assert.strictEqual(navigator.sendBeacon.callCount, 0); + }); + }); + + context('and the `slotRenderEnded` event has fired for an unknown slot code', function () { + it('logs a warning message', function () { + this.enableAnalytics(); + + const { prebid: [auction], gam } = getMockEvents(); + auction.AUCTION_INIT.adUnits[0].code = 'INVALID_AD_UNIT_CODE'; + + const slotRenderEnded = gam.slotRenderEnded[0]; + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + mockGpt.emitEvent('slotRenderEnded', slotRenderEnded); + + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + + assert.calledWithExactly(log.warn, + 'Could not find configured ad unit matching GAM render of slot:', + { slotName: `${adUnitCodes[0]} - ${adSlotElementIds[0]}` }); + }); + }); + + context('and the incomplete report has been sent successfully', function () { + it('sends a report string with any bids with rendered status set to hasWon: 1', function () { + navigator.sendBeacon.returns(true); + + this.enableAnalytics(); + + performStandardAuction({exclude: ['auctionEnd']}); + sandbox.clock.tick(this.defaultTimeout + 1000); + + const incompleteSentBid = JSON.parse(navigator.sendBeacon.firstCall.args[1]).auctions[0].adUnits[1].bids[0]; + assert.strictEqual(incompleteSentBid.hasWon, 1); + }); + + it('reports bids with only targetingSet status as hasWon: 0', function () { + navigator.sendBeacon.returns(true); + + this.enableAnalytics(); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); + sandbox.clock.tick(this.defaultTimeout + 1000); + + const incompleteSentBid = JSON.parse(navigator.sendBeacon.firstCall.args[1]).auctions[0].adUnits[1].bids[0]; + assert.strictEqual(incompleteSentBid.hasWon, 0); + }); + + it('logs an info message', function () { + navigator.sendBeacon.returns(true); + + const endpoint = faker.internet.url(); + this.enableAnalytics({ endpoint }); + + performStandardAuction({exclude: ['bidWon', 'auctionEnd']}); + sandbox.clock.tick(this.defaultTimeout + 1000); + + assert.calledWith(log.info, `Analytics report sent to ${endpoint}`); + }); + }); + }); + + context('when the transaction manager has open transactions', function () { + it('reports those transactions as pending', function () { + this.enableAnalytics(); + + const { prebid: [auction] } = getMockEvents(); + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + + const manager = locals.transactionManagers[auction.AUCTION_INIT.auctionId]; + assert.equal(manager.status().pending.length, auction.BID_REQUESTED[0].bids.length); + }); + + context('and a single bidWon event has triggered', function () { + it('completes the transaction', function () { + this.enableAnalytics(); + + const { prebid: [auction] } = getMockEvents(); + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + events.emit(EVENTS.BID_WON, auction.BID_WON[0]); + + const manager = locals.transactionManagers[auction.AUCTION_INIT.auctionId]; + assert.deepEqual({ + completed: manager.status().completed.length, + pending: manager.status().pending.length + }, { + completed: 1, + pending: auction.BID_REQUESTED[0].bids.length - 1 + }); + }); + }); + + context('and a single slotRenderEnded event has triggered', function () { + context('and the Google Ad Manager timeout has not elapsed', function () { + it('does NOT complete the transaction', function () { + this.enableAnalytics(); + + const { prebid: [auction], gam } = getMockEvents(); + const slotRenderEnded = gam.slotRenderEnded[0]; + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + mockGpt.emitEvent('slotRenderEnded', slotRenderEnded); + + const manager = locals.transactionManagers[auction.AUCTION_INIT.auctionId]; + assert.deepEqual({ + completed: manager.status().completed.length, + pending: manager.status().pending.length + }, { + completed: 0, + pending: auction.BID_REQUESTED[0].bids.length + }); + }); + }); + + context('and the Google Ad Manager timeout has elapsed', function () { + it('completes the transaction', function () { + const timeout = POST_GAM_TIMEOUT + 2000; + this.enableAnalytics({timeout}); + + const { prebid: [auction], gam } = getMockEvents(); + const slotRenderEnded = gam.slotRenderEnded[0]; + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, auction.BID_REQUESTED[0]); + mockGpt.emitEvent('slotRenderEnded', slotRenderEnded); + + sandbox.clock.tick(POST_GAM_TIMEOUT + 1); + const manager = locals.transactionManagers[auction.AUCTION_INIT.auctionId]; + assert.deepEqual({ + completed: manager.status().completed.length, + pending: manager.status().pending.length + }, { + completed: 1, + pending: auction.BID_REQUESTED[0].bids.length - 1 + }); + }); + }); + }); + }); + }); +}); + +const adUnitCodes = ['/19968336/header-bid-tag-0', '/19968336/header-bid-tag-1', '/17118521/header-bid-tag-2']; +const adSlotElementIds = ['ad-slot-div-0', 'ad-slot-div-1', 'ad-slot-div-2']; + +function performStandardAuction({ exclude = [], useSlotElementIds = false } = {}) { + const mockEvents = getMockEvents(); + const { prebid, gam } = mockEvents; + const [auction] = prebid; + + if (!exclude.includes(EVENTS.AUCTION_INIT)) { + if (useSlotElementIds) { + // With this option, identify the ad units by slot element IDs instead of GAM paths + auction.AUCTION_INIT.adUnits.forEach((adUnit, i) => { + adUnit.code = adSlotElementIds[i]; + }); + } + events.emit(EVENTS.AUCTION_INIT, auction.AUCTION_INIT); + } + + if (!exclude.includes(EVENTS.BID_REQUESTED)) { + for (let bidRequestedEvent of auction.BID_REQUESTED) { + events.emit(EVENTS.BID_REQUESTED, bidRequestedEvent); + }; + } + + if (!exclude.includes(EVENTS.BID_RESPONSE)) { + for (let bidResponseEvent of auction.BID_RESPONSE) { + events.emit(EVENTS.BID_RESPONSE, bidResponseEvent); + }; + } + + if (!exclude.includes(EVENTS.AUCTION_END)) { + events.emit(EVENTS.AUCTION_END, auction.AUCTION_END); + } + + if (!exclude.includes('slotRenderEnded')) { + for (let gEvent of gam.slotRenderEnded) { + mockGpt.emitEvent('slotRenderEnded', gEvent); + } + } + + if (!exclude.includes(EVENTS.BID_WON)) { + for (let bidWonEvent of auction.BID_WON) { + events.emit(EVENTS.BID_WON, bidWonEvent); + }; + } +} + +function mapToBids(auctions) { + return auctions.flatMap( + auction => auction.adUnits.flatMap( + au => au.bids + ) + ); +} + +function getLocalAssert() { + function isValidAnalyticsReport(report) { + assert.containsAllKeys(report, ['analyticsVersion', 'pid', 'src', 'pbjsVersion', 'auctions']); + if ('usPrivacy' in report) { + assert.match(report.usPrivacy, /[0|1][Y|N|-]{3}/); + } + if ('gdpr' in report) { + assert.oneOf(report.gdpr, [0, 1]); + } + if (report.gdpr === 1) { + assert.isString(report.gdprConsent); + } + if ('gpp' in report) { + assert.isString(report.gpp); + assert.isArray(report.gppSid); + } + if ('coppa' in report) { + assert.oneOf(report.coppa, [0, 1]); + } + + assert.equal(report.analyticsVersion, '1.0.0'); + assert.isString(report.pid); + assert.isString(report.src); + assert.equal(report.pbjsVersion, '$prebid.version$'); + assert.isArray(report.auctions); + assert.isAbove(report.auctions.length, 0); + report.auctions.forEach(isValidAuction); + } + function isValidAuction(auction) { + assert.hasAllKeys(auction, ['adUnits', 'auctionId', 'userIds']); + assert.isArray(auction.adUnits); + assert.isString(auction.auctionId); + assert.isArray(auction.userIds); + auction.adUnits.forEach(isValidAdUnit); + } + function isValidAdUnit(adUnit) { + assert.hasAllKeys(adUnit, ['transactionId', 'adUnitCode', 'slotId', 'mediaTypes', 'sizes', 'bids']); + assert.isString(adUnit.transactionId); + assert.isString(adUnit.adUnitCode); + assert.isString(adUnit.slotId); + assert.isArray(adUnit.mediaTypes); + assert.isArray(adUnit.sizes); + assert.isArray(adUnit.bids); + adUnit.mediaTypes.forEach(isValidMediaType); + adUnit.sizes.forEach(isValidSizeString); + adUnit.bids.forEach(isValidBid); + } + function isValidBid(bid) { + assert.containsAllKeys(bid, ['bidder', 'bidId', 'source', 'status', 'hasWon']); + if ('bidResponse' in bid) { + isValidBidResponse(bid.bidResponse); + } + assert.isString(bid.bidder); + assert.isString(bid.bidId); + assert.isString(bid.source); + assert.oneOf(bid.status, ['pending', 'timeout', 'targetingSet', 'rendered', 'success', 'rejected', 'no-bid', 'error']); + assert.oneOf(bid.hasWon, [0, 1]); + } + function isValidBidResponse(bidResponse) { + assert.containsAllKeys(bidResponse, ['mediaType', 'size', 'cur', 'cpm', 'cpmFloor']); + if ('cpmOrig' in bidResponse) { + assert.isNumber(bidResponse.cpmOrig); + } + isValidMediaType(bidResponse.mediaType); + isValidSizeString(bidResponse.size); + assert.isString(bidResponse.cur); + assert.isNumber(bidResponse.cpm); + assert.isNumber(bidResponse.cpmFloor); + } + function isValidMediaType(mediaType) { + assert.oneOf(mediaType, ['banner', 'video', 'native']); + } + function isValidSizeString(size) { + assert.match(size, /[0-9]+x[0-9]+/); + } + + function calledOnceWithStringJsonEquivalent(sinonSpy, ...args) { + sinon.assert.calledOnce(sinonSpy); + args.forEach((arg, i) => { + const stubCallArgs = sinonSpy.firstCall.args[i] + + if (typeof arg === 'object') { + assert.deepEqual(JSON.parse(stubCallArgs), arg); + } else { + assert.strictEqual(stubCallArgs, arg); + } + }); + } + + sinon.assert.expose(assert, { prefix: '' }); + return { + ...assert, + calledOnceWithStringJsonEquivalent, + isValidAnalyticsReport, + isValidAuction, + isValidAdUnit, + isValidBid, + isValidBidResponse, + isValidMediaType, + isValidSizeString, + } +}; + +function createReportWithThreeBidWonEvents() { + return { + pid: 'test-pid', + src: 'pbjs', + analyticsVersion: '1.0.0', + pbjsVersion: '$prebid.version$', + auctions: [{ + adUnits: [{ + transactionId: 'ef947609-7b55-4420-8407-599760d0e373', + adUnitCode: adUnitCodes[0], + slotId: adUnitCodes[0], + mediaTypes: ['banner'], + sizes: ['300x250', '300x600'], + bids: [{ + bidder: 'bidder0', + bidId: '20661fc5fbb5d9b', + source: 'client', + status: 'rendered', + bidResponse: { + cpm: 1.5, + cur: 'USD', + cpmOrig: 1.5, + cpmFloor: 1, + mediaType: 'banner', + size: '300x250' + }, + hasWon: 1 + }] + }, { + transactionId: 'abab4423-d962-41aa-adc7-0681f686c330', + adUnitCode: adUnitCodes[1], + slotId: adUnitCodes[1], + mediaTypes: ['banner'], + sizes: ['728x90', '970x250'], + bids: [{ + bidder: 'bidder0', + bidId: '21ad295f40dd7ab', + source: 'client', + status: 'rendered', + bidResponse: { + cpm: 1.5, + cur: 'USD', + cpmOrig: 1.5, + cpmFloor: 1, + mediaType: 'banner', + size: '728x90' + }, + hasWon: 1 + }] + }, { + transactionId: 'b43e7487-0a52-4689-a0f7-d139d08b1f9f', + adUnitCode: adUnitCodes[2], + slotId: adUnitCodes[2], + mediaTypes: ['banner'], + sizes: ['300x250'], + bids: [{ + bidder: 'bidder0', + bidId: '22108ac7b778717', + source: 'client', + status: 'rendered', + bidResponse: { + cpm: 1.5, + cur: 'USD', + cpmOrig: 1.5, + cpmFloor: 1, + mediaType: 'banner', + size: '728x90' + }, + hasWon: 1 + }] + }], + auctionId: 'auction-000', + userIds: ['33acrossId'] + }], + }; +} + +function getMockEvents() { + const auctionId = 'auction-000'; + const userId = { + '33acrossId': { + envelope: 'v1.0014', + }, + }; + + return { + gam: { + slotRenderEnded: [ + { + serviceName: 'publisher_ads', + slot: mockGpt.makeSlot({ code: adUnitCodes[0], divId: adSlotElementIds[0] }), + isEmpty: true, + slotContentChanged: true, + size: null, + advertiserId: null, + campaignId: null, + creativeId: null, + creativeTemplateId: null, + labelIds: null, + lineItemId: null, + isBackfill: false, + }, + { + serviceName: 'publisher_ads', + slot: mockGpt.makeSlot({ code: adUnitCodes[1], divId: adSlotElementIds[1] }), + isEmpty: false, + slotContentChanged: true, + size: [1, 1], + advertiserId: 12345, + campaignId: 400000001, + creativeId: 6789, + creativeTemplateId: null, + labelIds: null, + lineItemId: 1011, + isBackfill: false, + yieldGroupIds: null, + companyIds: null, + }, + { + serviceName: 'publisher_ads', + slot: mockGpt.makeSlot({ code: adUnitCodes[2], divId: adSlotElementIds[2] }), + isEmpty: false, + slotContentChanged: true, + size: [728, 90], + advertiserId: 12346, + campaignId: 299999000, + creativeId: 6790, + creativeTemplateId: null, + labelIds: null, + lineItemId: 1012, + isBackfill: false, + yieldGroupIds: null, + companyIds: null, + }, + ], + }, + prebid: [{ + AUCTION_INIT: { + auctionId, + adUnits: [ + { + code: adUnitCodes[0], + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bids: [ + { + bidder: 'bidder0', + userId, + }, + ], + sizes: [ + [300, 250], + [300, 600], + ], + transactionId: 'ef947609-7b55-4420-8407-599760d0e373', + ortb2Imp: { + ext: { + gpid: adUnitCodes[0], + }, + }, + }, + { + code: adUnitCodes[1], + mediaTypes: { + banner: { + sizes: [ + [728, 90], + [970, 250], + ], + }, + }, + bids: [ + { + bidder: 'bidder0', + userId, + }, + ], + sizes: [ + [728, 90], + [970, 250], + ], + transactionId: 'abab4423-d962-41aa-adc7-0681f686c330', + ortb2Imp: { + ext: { + gpid: adUnitCodes[1], + }, + }, + }, + { + code: adUnitCodes[2], + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: '33across', + userId, + }, + { + bidder: 'bidder0', + userId, + }, + ], + sizes: [[300, 250]], + transactionId: 'b43e7487-0a52-4689-a0f7-d139d08b1f9f', + ortb2Imp: { + ext: { + gpid: adUnitCodes[2], + }, + }, + }, + ], + bidderRequests: [ + { + bids: [ + { userId }, + ], + } + ], + }, + BID_REQUESTED: [ + { + auctionId, + bids: [ + { + bidder: 'bidder0', + transactionId: 'ef947609-7b55-4420-8407-599760d0e373', + bidId: '20661fc5fbb5d9b', + src: 'client', + }, + { + bidder: 'bidder0', + transactionId: 'abab4423-d962-41aa-adc7-0681f686c330', + bidId: '21ad295f40dd7ab', + src: 'client', + }, + { + bidder: 'bidder0', + transactionId: 'b43e7487-0a52-4689-a0f7-d139d08b1f9f', + bidId: '22108ac7b778717', + src: 'client', + }, + ], + }], + BID_RESPONSE: [{ + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '20661fc5fbb5d9b', + size: '300x250', + source: 'client', + status: 'targetingSet' + }, + { + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '21ad295f40dd7ab', + size: '728x90', + source: 'client', + status: 'targetingSet', + }, + { + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '22108ac7b778717', + size: '728x90', + source: 'client', + status: 'targetingSet', + }], + BID_WON: [{ + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '20661fc5fbb5d9b', + size: '300x250', + source: 'client', + status: 'rendered', + transactionId: 'ef947609-7b55-4420-8407-599760d0e373', + }, + { + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '21ad295f40dd7ab', + size: '728x90', + source: 'client', + status: 'rendered', + transactionId: 'abab4423-d962-41aa-adc7-0681f686c330', + }, + { + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 1 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '22108ac7b778717', + size: '728x90', + source: 'client', + status: 'rendered', + transactionId: 'b43e7487-0a52-4689-a0f7-d139d08b1f9f', + }], + BID_REJECTED: [{ + auctionId, + cpm: 1.5, + currency: 'USD', + floorData: { + floorValue: 2 + }, + mediaType: 'banner', + originalCpm: 1.5, + requestId: '20661fc5fbb5d9b', + width: 300, + height: 250, + source: 'client', + transactionId: 'ef947609-7b55-4420-8407-599760d0e373', + statusMessage: 'Bid available', + rejectionReason: 'Bid does not meet price floor', + }], + AUCTION_END: { + auctionId, + }, + }], + }; +} From c8870c1e8e624f2206bb2a3723235ef37a94e620 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 13 Nov 2023 11:14:02 -0800 Subject: [PATCH 187/760] Rubicon Bid Adapter: Pass SUA data if available (#10721) * rubicon pass client hints * linttt --- modules/rubiconBidAdapter.js | 31 ++++- test/spec/modules/rubiconBidAdapter_spec.js | 120 ++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index de6ddf0618c..bff7661455a 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -17,7 +17,9 @@ import { logMessage, logWarn, mergeDeep, - parseSizesInput, _each + parseSizesInput, + pick, + _each } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; @@ -973,6 +975,33 @@ function applyFPD(bidRequest, mediaType, data) { if (data['tg_i.pbadslot']) { delete data['tg_i.dfp_ad_unit_code']; } + + // High Entropy stuff -> sua object is the ORTB standard (default to pass unless specifically disabled) + const clientHints = deepAccess(fpd, 'device.sua'); + if (clientHints && rubiConf.chEnabled !== false) { + // pick out client hints we want to send (any that are undefined or empty will NOT be sent) + pick(clientHints, [ + 'architecture', arch => data.m_ch_arch = arch, + 'bitness', bitness => data.m_ch_bitness = bitness, + 'browsers', browsers => { + if (!Array.isArray(browsers)) return; + // reduce down into ua and full version list attributes + const [ua, fullVer] = browsers.reduce((accum, browserData) => { + accum[0].push(`"${browserData?.brand}"|v="${browserData?.version?.[0]}"`); + accum[1].push(`"${browserData?.brand}"|v="${browserData?.version?.join?.('.')}"`); + return accum; + }, [[], []]); + data.m_ch_ua = ua?.join?.(','); + data.m_ch_full_ver = fullVer?.join?.(','); + }, + 'mobile', isMobile => data.m_ch_mobile = `?${isMobile}`, + 'model', model => data.m_ch_model = model, + 'platform', platform => { + data.m_ch_platform = platform?.brand; + data.m_ch_platform_ver = platform?.version?.join?.('.'); + } + ]) + } } else { if (Object.keys(impExt).length) { mergeDeep(data.imp[0].ext, impExt); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index c0bc35d161e..3f3cbfbb46c 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1818,6 +1818,126 @@ describe('the rubicon adapter', function () { expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); }); }); + + describe('client hints', function () { + let standardSuaObject; + beforeEach(function () { + standardSuaObject = { + source: 2, + platform: { + brand: 'macOS', + version: [ + '12', + '6', + '0' + ] + }, + browsers: [ + { + brand: 'Not.A/Brand', + version: [ + '8', + '0', + '0', + '0' + ] + }, + { + brand: 'Chromium', + version: [ + '114', + '0', + '5735', + '198' + ] + }, + { + brand: 'Google Chrome', + version: [ + '114', + '0', + '5735', + '198' + ] + } + ], + mobile: 0, + model: '', + bitness: '64', + architecture: 'x86' + } + }); + it('should send m_ch_* params if ortb2.device.sua object is there', function () { + let bidRequestSua = utils.deepClone(bidderRequest); + bidRequestSua.bids[0].ortb2 = { device: { sua: standardSuaObject } }; + + // How should fastlane query be constructed with default SUA + let expectedValues = { + m_ch_arch: 'x86', + m_ch_bitness: '64', + m_ch_ua: `"Not.A/Brand"|v="8","Chromium"|v="114","Google Chrome"|v="114"`, + m_ch_full_ver: `"Not.A/Brand"|v="8.0.0.0","Chromium"|v="114.0.5735.198","Google Chrome"|v="114.0.5735.198"`, + m_ch_mobile: '?0', + m_ch_platform: 'macOS', + m_ch_platform_ver: '12.6.0' + } + + // Build Fastlane call + let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); + let data = parseQuery(request.data); + + // Loop through expected values and if they do not match push an error + const errors = Object.entries(expectedValues).reduce((accum, [key, val]) => { + if (data[key] !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) + return accum; + }, []); + + // should be no errors + expect(errors).to.deep.equal([]); + }); + it('should not send invalid values for m_ch_*', function () { + let bidRequestSua = utils.deepClone(bidderRequest); + + // Alter input SUA object + // send model + standardSuaObject.model = 'Suface Duo'; + // send mobile = 1 + standardSuaObject.mobile = 1; + + // make browsers not an array + standardSuaObject.browsers = 'My Browser'; + + // make platform not have version + delete standardSuaObject.platform.version; + + // delete architecture + delete standardSuaObject.architecture; + + // add SUA to bid + bidRequestSua.bids[0].ortb2 = { device: { sua: standardSuaObject } }; + + // Build Fastlane request + let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); + let data = parseQuery(request.data); + + // should show new names + expect(data.m_ch_model).to.equal('Suface Duo'); + expect(data.m_ch_mobile).to.equal('?1'); + + // should still send platform + expect(data.m_ch_platform).to.equal('macOS'); + + // platform version not sent + expect(data).to.not.haveOwnProperty('m_ch_platform_ver'); + + // both ua and full_ver not sent because browsers not array + expect(data).to.not.haveOwnProperty('m_ch_ua'); + expect(data).to.not.haveOwnProperty('m_ch_full_ver'); + + // arch not sent + expect(data).to.not.haveOwnProperty('m_ch_arch'); + }); + }); }); if (FEATURES.VIDEO) { From 683b5e6b23507f4808b06929170bcdb4d07d6f36 Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 14 Nov 2023 14:35:21 +0200 Subject: [PATCH 188/760] Adttelligent Bid Adapter : add Indicue alias (#10719) * Add indicue adapter * getter --- modules/adtelligentBidAdapter.js | 4 +++- test/spec/modules/adtelligentBidAdapter_spec.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 04bca21c60f..8e6aeecdd75 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -21,7 +21,8 @@ const HOST_GETTERS = { janet: () => 'ghb.bidder.jmgads.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', '9dotsmedia': () => 'ghb.platform.audiodots.com', - copper6: () => 'ghb.app.copper6.com' + copper6: () => 'ghb.app.copper6.com', + indicue: () => 'ghb.console.indicue.com', } const getUri = function (bidderCode) { let bidderWithoutSuffix = bidderCode.split('_')[0]; @@ -44,6 +45,7 @@ export const spec = { { code: 'ocm', gvlid: 1148 }, '9dotsmedia', 'copper6', + 'indicue', ], supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index f271f638e98..0acbaa06f5b 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -17,6 +17,7 @@ const aliasEP = { 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', 'copper6': 'https://ghb.app.copper6.com/v2/auction/', + 'indicue': 'https://ghb.console.indicue.com/v2/auction/', }; const DEFAULT_ADATPER_REQ = { bidderCode: 'adtelligent' }; From 9c6cd7345c30569d33f2b28b29bbccebbae2c257 Mon Sep 17 00:00:00 2001 From: mcajlakovic <141749900+mcajlakovic@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:37:34 -0500 Subject: [PATCH 189/760] Media Filter RTD Provider : initial modul release (#10418) * Added Media Filter Real Time Data Provider Module * fixed eslint errors * Modified MEDIAFILTER_EVENT_TYPE in unit tests * re-trigger pipeline * removed blank space * re-trigger pipelines * updated readme * added mediafilter to adloader.js and submodules.json --- modules/.submodules.json | 3 +- modules/mediafilterRtdProvider.js | 94 +++++++++++ modules/mediafilterRtdProvider.md | 37 +++++ src/adloader.js | 1 + .../modules/mediafilterRtdProvider_spec.js | 147 ++++++++++++++++++ 5 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 modules/mediafilterRtdProvider.js create mode 100644 modules/mediafilterRtdProvider.md create mode 100644 test/spec/modules/mediafilterRtdProvider_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index e7488b9ddb2..830d3c9b2f3 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -65,7 +65,8 @@ "blueconicRtdProvider", "brandmetricsRtdProvider", "browsiRtdProvider", - "cleanioRtdProvider", + "captifyRtdProvider", + "mediafilterRtdProvider", "confiantRtdProvider", "dgkeywordRtdProvider", "experianRtdProvider", diff --git a/modules/mediafilterRtdProvider.js b/modules/mediafilterRtdProvider.js new file mode 100644 index 00000000000..277b19c10b2 --- /dev/null +++ b/modules/mediafilterRtdProvider.js @@ -0,0 +1,94 @@ +/** + * This module adds the Media Filter real-time ad monitoring and protection module. + * + * The {@link module:modules/realTimeData} module is required + * + * For more information, visit {@link https://www.themediatrust.com The Media Trust}. + * + * @author Mirnes Cajlakovic + * @module modules/mediafilterRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { logError, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; + +/** @const {string} MEDIAFILTER_EVENT_TYPE - The event type for Media Filter. */ +export const MEDIAFILTER_EVENT_TYPE = 'com.mediatrust.pbjs.'; +/** @const {string} MEDIAFILTER_BASE_URL - The base URL for Media Filter scripts. */ +export const MEDIAFILTER_BASE_URL = 'https://scripts.webcontentassessor.com/scripts/'; + +export const MediaFilter = { + /** + * Registers the Media Filter as a submodule of real-time data. + */ + register: function() { + submodule('realTimeData', { + 'name': 'mediafilter', + 'init': this.generateInitHandler() + }); + }, + + /** + * Sets up the Media Filter by initializing event listeners and loading the external script. + * @param {object} configuration - The configuration object. + */ + setup: function(configuration) { + this.setupEventListener(configuration.configurationHash); + this.setupScript(configuration.configurationHash); + }, + + /** + * Sets up an event listener for Media Filter messages. + * @param {string} configurationHash - The configuration hash. + */ + setupEventListener: function(configurationHash) { + window.addEventListener('message', this.generateEventHandler(configurationHash)); + }, + + /** + * Loads the Media Filter script based on the provided configuration hash. + * @param {string} configurationHash - The configuration hash. + */ + setupScript: function(configurationHash) { + loadExternalScript(MEDIAFILTER_BASE_URL.concat(configurationHash), 'mediafilter', () => {}); + }, + + /** + * Generates an event handler for Media Filter messages. + * @param {string} configurationHash - The configuration hash. + * @returns {function} The generated event handler. + */ + generateEventHandler: function(configurationHash) { + return (windowEvent) => { + if (windowEvent.data.type === MEDIAFILTER_EVENT_TYPE.concat('.', configurationHash)) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + 'billingId': generateUUID(), + 'configurationHash': configurationHash, + 'type': 'impression', + 'vendor': 'mediafilter', + }); + } + }; + }, + + /** + * Generates an initialization handler for Media Filter. + * @returns {function} The generated init handler. + */ + generateInitHandler: function() { + return (configuration) => { + try { + this.setup(configuration); + } catch (error) { + logError(`Error in initialization: ${error.message}`); + } + }; + } +}; + +// Register the module +MediaFilter.register(); diff --git a/modules/mediafilterRtdProvider.md b/modules/mediafilterRtdProvider.md new file mode 100644 index 00000000000..469479f8d0b --- /dev/null +++ b/modules/mediafilterRtdProvider.md @@ -0,0 +1,37 @@ +## Overview + +**Module:** The Media Filter +**Type: **Real Time Data Module + +As malvertising, scams, and controversial and offensive ad content proliferate across the digital media ecosystem, publishers need advanced controls to both shield audiences from malware attacks and ensure quality site experience. With the market’s fastest and most comprehensive real-time ad quality tool, The Media Trust empowers publisher Ad/Revenue Operations teams to block a wide range of malware, high-risk ad platforms, heavy ads, ads with sensitive or objectionable content, and custom lists (e.g., competitors). Customizable replacement code calls for a new ad to ensure impressions are still monetized. + +[![IMAGE ALT TEXT](http://img.youtube.com/vi/VBHRiirge7s/0.jpg)](http://www.youtube.com/watch?v=VBHRiirge7s "Publishers' Ultimate Avenger: Media Filter") + +To start using this module, please contact [The Media Trust](https://mediatrust.com/how-we-help/media-filter/ "The Media Trust") to get a script and configuration hash for module configuration. + +## Integration + +1. Build Prebid bundle with The Media Filter module included. + +``` +gulp build --modules=mediafilterRtdProvider +``` + +2. Inlcude the bundled script in your application. + +## Configuration + +Add configuration entry to `realTimeData.dataProviders` for The Media Filter module. + +``` +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'mediafilter', + params: { + configurationHash: '', + } + }] + } +}); +``` diff --git a/src/adloader.js b/src/adloader.js index d69d2032b40..cd4ad771774 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -27,6 +27,7 @@ const _approvedLoadExternalJSList = [ 'clean.io', 'a1Media', 'geoedge', + 'mediafilter', 'qortex' ] diff --git a/test/spec/modules/mediafilterRtdProvider_spec.js b/test/spec/modules/mediafilterRtdProvider_spec.js new file mode 100644 index 00000000000..3395c7be691 --- /dev/null +++ b/test/spec/modules/mediafilterRtdProvider_spec.js @@ -0,0 +1,147 @@ +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js' +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; + +import { + MediaFilter, + MEDIAFILTER_EVENT_TYPE, + MEDIAFILTER_BASE_URL +} from '../../../modules/mediafilterRtdProvider.js'; + +describe('The Media Filter RTD module', function () { + describe('register()', function() { + let submoduleSpy, generateInitHandlerSpy; + + beforeEach(function () { + submoduleSpy = sinon.spy(hook, 'submodule'); + generateInitHandlerSpy = sinon.spy(MediaFilter, 'generateInitHandler'); + }); + + afterEach(function () { + submoduleSpy.restore(); + generateInitHandlerSpy.restore(); + }); + + it('should register and call the submodule function(s)', function () { + MediaFilter.register(); + + expect(submoduleSpy.calledOnceWithExactly('realTimeData', sinon.match.object)).to.be.true; + expect(submoduleSpy.called).to.be.true; + expect(generateInitHandlerSpy.called).to.be.true; + }); + }); + + describe('setup()', function() { + let setupEventListenerSpy, setupScriptSpy; + + beforeEach(function() { + setupEventListenerSpy = sinon.spy(MediaFilter, 'setupEventListener'); + setupScriptSpy = sinon.spy(MediaFilter, 'setupScript'); + }); + + afterEach(function() { + setupEventListenerSpy.restore(); + setupScriptSpy.restore(); + }); + + it('should call setupEventListener and setupScript function(s)', function() { + MediaFilter.setup({ configurationHash: 'abc123' }); + + expect(setupEventListenerSpy.called).to.be.true; + expect(setupScriptSpy.called).to.be.true; + }); + }); + + describe('setupEventListener()', function() { + let setupEventListenerSpy, addEventListenerSpy; + + beforeEach(function() { + setupEventListenerSpy = sinon.spy(MediaFilter, 'setupEventListener'); + addEventListenerSpy = sinon.spy(window, 'addEventListener'); + }); + + afterEach(function() { + setupEventListenerSpy.restore(); + addEventListenerSpy.restore(); + }); + + it('should call addEventListener function(s)', function() { + MediaFilter.setupEventListener(); + expect(addEventListenerSpy.called).to.be.true; + expect(addEventListenerSpy.calledWith('message', sinon.match.func)).to.be.true; + }); + }); + + describe('generateInitHandler()', function() { + let generateInitHandlerSpy, setupMock, logErrorSpy; + + beforeEach(function() { + generateInitHandlerSpy = sinon.spy(MediaFilter, 'generateInitHandler'); + setupMock = sinon.stub(MediaFilter, 'setup').throws(new Error('Mocked error!')); + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(function() { + generateInitHandlerSpy.restore(); + setupMock.restore(); + logErrorSpy.restore(); + }); + + it('should handle errors in the catch block when setup throws an error', function() { + const initHandler = MediaFilter.generateInitHandler(); + initHandler({}); + + expect(logErrorSpy.calledWith('Error in initialization: Mocked error!')).to.be.true; + }); + }); + + describe('generateEventHandler()', function() { + let generateEventHandlerSpy, eventsEmitSpy; + + beforeEach(function() { + generateEventHandlerSpy = sinon.spy(MediaFilter, 'generateEventHandler'); + eventsEmitSpy = sinon.spy(events, 'emit'); + }); + + afterEach(function() { + generateEventHandlerSpy.restore(); + eventsEmitSpy.restore(); + }); + + it('should emit a billable event when the event type matches', function() { + const configurationHash = 'abc123'; + const eventHandler = MediaFilter.generateEventHandler(configurationHash); + + const mockEvent = { + data: { + type: MEDIAFILTER_EVENT_TYPE.concat('.', configurationHash) + } + }; + + eventHandler(mockEvent); + + expect(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BILLABLE_EVENT, { + 'billingId': sinon.match.string, + 'configurationHash': configurationHash, + 'type': 'impression', + 'vendor': 'mediafilter', + })).to.be.true; + }); + + it('should not emit a billable event when the event type does not match', function() { + const configurationHash = 'abc123'; + const eventHandler = MediaFilter.generateEventHandler(configurationHash); + + const mockEvent = { + data: { + type: 'differentEventType' + } + }; + + eventHandler(mockEvent); + + expect(eventsEmitSpy.called).to.be.false; + }); + }); +}); From 9ce9b50158d7dd43a8c847cc07dc2dab03874c89 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Tue, 14 Nov 2023 16:53:21 +0100 Subject: [PATCH 190/760] Onetag Bid Adapter: address auctionId/transactionId leak (#10726) Co-authored-by: francesco --- modules/onetagBidAdapter.js | 5 ++--- test/spec/modules/onetagBidAdapter_spec.js | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index 801bb747e34..eb381727a3b 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -267,9 +267,8 @@ function setGeneralInfo(bidRequest) { this['adUnitCode'] = bidRequest.adUnitCode; this['bidId'] = bidRequest.bidId; this['bidderRequestId'] = bidRequest.bidderRequestId; - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - this['auctionId'] = bidRequest.auctionId; - this['transactionId'] = bidRequest.ortb2Imp?.ext?.tid; + this['auctionId'] = deepAccess(bidRequest, 'ortb2.source.tid'); + this['transactionId'] = deepAccess(bidRequest, 'ortb2Imp.ext.tid'); this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index df6456db82e..6d43dd2e40e 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -15,9 +15,14 @@ describe('onetag', function () { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', - ortb2Imp: { - ext: { - tid: 'qwerty123' + 'ortb2Imp': { + 'ext': { + 'tid': '0000' + } + }, + 'ortb2': { + 'source': { + 'tid': '1111' } }, 'schain': { @@ -256,6 +261,15 @@ describe('onetag', function () { expect(dataObj.bids).to.be.an('array').that.is.empty; } catch (e) { } }); + it('Should pick each bid\'s auctionId and transactionId from ortb2 related fields', function () { + const serverRequest = spec.buildRequests([bannerBid]); + const payload = JSON.parse(serverRequest.data); + + expect(payload).to.exist; + expect(payload.bids).to.exist.and.to.have.length(1); + expect(payload.bids[0].auctionId).to.equal(bannerBid.ortb2.source.tid); + expect(payload.bids[0].transactionId).to.equal(bannerBid.ortb2Imp.ext.tid); + }); it('should send GDPR consent data', function () { let consentString = 'consentString'; let bidderRequest = { From 83390b9c803a971df80f3ce0f9e9f51d595401e1 Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Tue, 14 Nov 2023 16:55:45 +0100 Subject: [PATCH 191/760] Use gppApplicableSections and gppString In LiveIntent's Prebid User Id Module (#10713) * forward gppSections and gppString to LC * fix package-lock --- modules/liveIntentIdSystem.js | 8 +++++-- package-lock.json | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 23 ++++++++++++++++---- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 900b0c41119..cc43f6f6f84 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -8,7 +8,7 @@ import { triggerPixel, logError } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports -import { gdprDataHandler, uspDataHandler } from '../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; @@ -127,7 +127,11 @@ function initializeLiveConnect(configParams) { liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; liveConnectConfig.gdprConsent = gdprConsent.consentString; } - + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + liveConnectConfig.gppString = gppConsent.gppString; + liveConnectConfig.gppApplicableSections = gppConsent.applicableSections; + } // The second param is the storage object, LS & Cookie manipulation uses PBJS // The third param is the ajax and pixel object, the ajax and pixel use PBJS liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); diff --git a/package-lock.json b/package-lock.json index 2c49e7b218f..138cd45fc61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.21.0-pre", + "version": "8.24.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index a51082f26bd..9f57a5aa405 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,6 +1,6 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; import * as utils from 'src/utils.js'; -import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') @@ -12,6 +12,7 @@ describe('LiveIntentId', function() { let logErrorStub; let uspConsentDataStub; let gdprConsentDataStub; + let gppConsentDataStub; let getCookieStub; let getDataFromLocalStorageStub; let imgStub; @@ -24,6 +25,7 @@ describe('LiveIntentId', function() { logErrorStub = sinon.stub(utils, 'logError'); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); }); afterEach(function() { @@ -33,6 +35,7 @@ describe('LiveIntentId', function() { logErrorStub.restore(); uspConsentDataStub.restore(); gdprConsentDataStub.restore(); + gppConsentDataStub.restore(); resetLiveIntentIdSubmodule(); }); @@ -42,11 +45,15 @@ describe('LiveIntentId', function() { gdprApplies: true, consentString: 'consentDataString' }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1, 2] + }) let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*/); + expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1%2C2.*/); const response = { unifiedId: 'a_unified_id', segments: [123, 234] @@ -65,9 +72,13 @@ describe('LiveIntentId', function() { gdprApplies: true, consentString: 'consentDataString' }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1] + }) liveIntentIdSubmodule.getId(defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString.*/); + expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 200); }); @@ -123,9 +134,13 @@ describe('LiveIntentId', function() { gdprApplies: false, consentString: 'consentDataString' }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1] + }) liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*/); + expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 200); }); From 35c6b60fa61d1e855b5dde5fa64410db5fdc0179 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 14 Nov 2023 11:32:22 -0500 Subject: [PATCH 192/760] Revert "NoBid Bid Adapter : add support for first party user id (#10519)" (#10728) This reverts commit 7600b38cebde8d11000916f74d9cc5f5842ebb8e. --- modules/nobidBidAdapter.js | 123 +-------------------- test/spec/modules/nobidBidAdapter_spec.js | 129 +++++++--------------- 2 files changed, 40 insertions(+), 212 deletions(-) diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index fb052a99695..68010b32b37 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -8,17 +8,12 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.4.1'; +window.nobidVersion = '1.3.3'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; window.nobid.bidWonTotal = 0; window.nobid.refreshCount = 0; -window.nobid.firstPartyIds = null; -window.nobid.firstPartyIdEnabled = false; -const FIRST_PARTY_KEY = 'fppcid.nobid.io'; -const FIRST_PARTY_SOURCE_KEY = 'fpid.nobid.io'; -const FIRST_PARTY_DATA_EXPIRY_DAYS = 7 * 24 * 3600 * 1000; function log(msg, obj) { logInfo('-NoBid- ' + msg, obj) } @@ -140,10 +135,8 @@ function nobidBuildRequests(bids, bidderRequest) { src.push({source: eid.source, uids: ids}); } }); - if (window.nobid.firstPartyIds && window.nobid.firstPartyIds) src.push({source: FIRST_PARTY_SOURCE_KEY, uids: [{id: window.nobid.firstPartyIds.ids}]}); return src; } - if (window.nobid.firstPartyIds && window.nobid.firstPartyIds.ids) return [{source: FIRST_PARTY_SOURCE_KEY, uids: [{id: window.nobid.firstPartyIds.ids}]}]; } var state = {}; state['sid'] = siteId; @@ -293,12 +286,6 @@ function nobidInterpretResponse(response, bidRequest) { var setRefreshLimit = function(response) { if (response && typeof response.rlimit !== 'undefined') window.nobid.refreshLimit = response.rlimit; } - var setFirstPartyIdEnabled = function(response) { - if (response && typeof response.fpid !== 'undefined') window.nobid.firstPartyIdEnabled = response.fpid; - if (window?.nobid?.firstPartyIdEnabled) { - nobidFirstPartyData.loadOrCreateFirstPartyData(); - } - } var setUserBlock = function(response) { if (response && typeof response.ublock !== 'undefined') { nobidSetCookie('_ublock', '1', response.ublock); @@ -306,7 +293,6 @@ function nobidInterpretResponse(response, bidRequest) { } setRefreshLimit(response); setUserBlock(response); - setFirstPartyIdEnabled(response); var bidResponses = []; for (var i = 0; response.bids && i < response.bids.length; i++) { var bid = response.bids[i]; @@ -373,113 +359,6 @@ window.addEventListener('message', function (event) { } } }, false); -const nobidFirstPartyData = { - isJson: function (str) { - return str && str.startsWith('{') && str.endsWith('}'); - }, - hasLocalStorage: function () { - try { - return window.localStorage; - } catch (error) { - logWarn('Local storage api disabled', error); - } - return false; - }, - readFirstPartyDataIds: function () { - try { - if (this.hasLocalStorage()) { - const idsStr = window.localStorage.getItem(FIRST_PARTY_SOURCE_KEY); - if (this.isJson(idsStr)) { - const idsObj = JSON.parse(idsStr); - if (idsObj.ts + FIRST_PARTY_DATA_EXPIRY_DAYS < Date.now()) return { pid: idsObj.pid }; // expired? - return idsObj; - } - return null; - } - } catch (error) { - logWarn('Local storage api disabled', error); - } - return null; - }, - loadOrCreateFirstPartyData: function () { - const storeFirstPartyDataIds = function ({ids: theIds, pid: thePid}) { - try { - if (nobidFirstPartyData.hasLocalStorage()) { - window.localStorage.setItem(FIRST_PARTY_SOURCE_KEY, JSON.stringify({ids: theIds, pid: thePid, ts: Date.now()})); - } - } catch (error) { - logWarn('Local storage api disabled', error); - } - }; - const readFirstPartyId = function () { - try { - if (nobidFirstPartyData.hasLocalStorage()) { - const idStr = window.localStorage.getItem(FIRST_PARTY_KEY); - if (nobidFirstPartyData.isJson(idStr)) { - return JSON.parse(idStr); - } - return null; - } - } catch (error) { - logWarn('Local storage api disabled', error); - } - return null; - }; - const storeFirstPartyId = function (theId) { - try { - if (nobidFirstPartyData.hasLocalStorage()) { - window.localStorage.setItem(FIRST_PARTY_KEY, JSON.stringify(theId)); - } - } catch (error) { - logWarn('Local storage api disabled', error); - } - }; - const _loadOrCreateFirstPartyData = function () { - const generateGUID = function () { - let d = new Date().getTime(); - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = (d + Math.random() * 16) % 16 | 0; - d = Math.floor(d / 16); - return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); - }); - }; - const ajaxGet = function (ajaxParams, callback) { - const ajax = new XMLHttpRequest(); - ajax.withCredentials = false; - ajax.timeout = ajaxParams.timeout; - ajax.open('GET', ajaxParams.url, true); - ajax.onreadystatechange = function () { - if (this.readyState === XMLHttpRequest.DONE) { - callback(this.response); - } - }; - ajax.send(ajaxParams.data); - }; - let firstPartyIdObj = readFirstPartyId(); - if (!firstPartyIdObj || !firstPartyIdObj.id || !firstPartyIdObj.ts) { - const firstPartyId = generateGUID(); - firstPartyIdObj = {id: firstPartyId, ts: Date.now()}; - storeFirstPartyId(firstPartyIdObj); - } - let firstPartyIds = nobidFirstPartyData.readFirstPartyDataIds(); - if (firstPartyIdObj?.ts && !firstPartyIds?.ids) { - const pid = firstPartyIds?.pid || ''; - const pdate = firstPartyIdObj.ts; - const firstPartyId = firstPartyIdObj.id; - const url = `https://api.intentiq.com/profiles_engine/ProfilesEngineServlet?at=39&mi=10&pt=17&dpn=1&iiqidtype=2&dpi=430542822&iiqpcid=${firstPartyId}&iiqpciddate=${pdate}&pid=${pid}`; - if (window.nobid.firstPartyRequestInProgress) return; - window.nobid.firstPartyRequestInProgress = true; - ajaxGet({ url: url }, function (response) { - response = JSON.parse(response); - if (response?.data) storeFirstPartyDataIds({ ids: response.data, pid: response.pid }); - }); - } - }; - window.nobid.firstPartyIds = this.readFirstPartyDataIds(); - if (window.nobid.firstPartyIdEnabled && !window.nobid.firstPartyIds?.ids) _loadOrCreateFirstPartyData(); - } -}; -window.nobid.firstPartyIds = nobidFirstPartyData.readFirstPartyDataIds(); export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index f2059900a2e..b1e303bde6e 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -57,26 +57,27 @@ describe('Nobid Adapter', function () { 'auctionId': '1d1a030790a475', }; - it('should return true when required params found 1', function () { + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when required params found 2', function () { - let mybid = Object.assign({}, bid); - delete mybid.params; - mybid.params = { + it('should return true when required params found', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { 'siteId': 2 }; - expect(spec.isBidRequestValid(mybid)).to.equal(true); + + expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let mybid = Object.assign({}, bid); - delete mybid.params; - mybid.params = { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { 'siteId': 0 }; - expect(spec.isBidRequestValid(mybid)).to.equal(false); + expect(spec.isBidRequestValid(bid)).to.equal(false); }); }); @@ -252,12 +253,12 @@ describe('Nobid Adapter', function () { }); it('sends bid request to site id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.a).to.exist; - expect(payload.a[0].sid).to.equal(2); - expect(payload.a[0].at).to.equal('banner'); - expect(payload.a[0].params.siteId).to.equal(2); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].sid).to.equal(2); + expect(payload.a[0].at).to.equal('banner'); + expect(payload.a[0].params.siteId).to.equal(2); }); it('sends bid request to ad type', function () { @@ -422,58 +423,6 @@ describe('Nobid Adapter', function () { }); }); - describe('First Party ID Test', function () { - const CREATIVE_ID_300x250 = 'CREATIVE-100'; - const ADUNIT_300x250 = 'ADUNIT-1'; - const ADMARKUP_300x250 = 'ADMARKUP-300x250'; - const PRICE_300x250 = 0.51; - const REQUEST_ID = '3db3773286ee59'; - const DEAL_ID = 'deal123'; - let response = { - country: 'US', - ip: '68.83.15.75', - device: 'COMPUTER', - site: 2, - fpid: true, - bids: [ - {id: 1, - bdrid: 101, - divid: ADUNIT_300x250, - creativeid: CREATIVE_ID_300x250, - size: {'w': 300, 'h': 250}, - adm: ADMARKUP_300x250, - price: '' + PRICE_300x250 - } - ] - }; - - it('first party ID', function () { - const bidderRequest = { - bids: [{ - bidId: REQUEST_ID, - adUnitCode: ADUNIT_300x250 - }] - } - const bidRequests = [ - { - 'bidder': 'nobid', - 'params': { - 'siteId': 2 - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - } - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.method).to.equal('POST'); - spec.interpretResponse({ body: response }, {bidderRequest: bidderRequest}); - expect(window.nobid.firstPartyIdEnabled).to.equal(true); - }); - }); - describe('isVideoBidRequestValid', function () { let bid = { bidder: 'nobid', @@ -686,12 +635,12 @@ describe('Nobid Adapter', function () { }); it('sends bid request to site id', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.a).to.exist; - expect(payload.a[0].sid).to.equal(2); - expect(payload.a[0].at).to.equal('banner'); - expect(payload.a[0].params.siteId).to.equal(2); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.a).to.exist; + expect(payload.a[0].sid).to.equal(2); + expect(payload.a[0].at).to.equal('banner'); + expect(payload.a[0].params.siteId).to.equal(2); }); it('sends bid request to ad type', function () { @@ -990,19 +939,19 @@ describe('Nobid Adapter', function () { auctionId: '1d1a030790a475', coppa: true, schain: { - validation: 'strict', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - name: 'name.com', - hp: 1 - } - ] - } + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + name: 'name.com', + hp: 1 + } + ] + } } } ]; @@ -1052,7 +1001,7 @@ describe('Nobid Adapter', function () { ] }; - it('Limit should be respected', function () { + it('should ULimit be respected', function () { const bidderRequest = { bids: [{ bidId: REQUEST_ID, @@ -1111,8 +1060,8 @@ describe('Nobid Adapter', function () { }); it('should get correct user sync when !iframeEnabled', function () { - let pixel = spec.getUserSyncs({}) - expect(pixel.length).to.equal(0); + let pixel = spec.getUserSyncs({}) + expect(pixel.length).to.equal(0); }); }); From 2691a5065262d75489386e25341898960c16a360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Rotta?= Date: Tue, 14 Nov 2023 20:29:06 +0100 Subject: [PATCH 193/760] Adagio Analytics Adapter: handle bid cache usecases (#10699) --- modules/adagioAnalyticsAdapter.js | 38 +- .../modules/adagioAnalyticsAdapter_spec.js | 517 +++++++++++++----- 2 files changed, 421 insertions(+), 134 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index f9b79639073..9c4c0e8fea7 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -27,6 +27,15 @@ const cache = { ...this.auctions[auctionId][adUnitCode], ...values }; + }, + + // Map prebid auction id to adagio auction id + auctionIdReferences: {}, + addPrebidAuctionIdRef(auctionId, adagioAuctionId) { + this.auctionIdReferences[auctionId] = adagioAuctionId; + }, + getAdagioAuctionId(auctionId) { + return this.auctionIdReferences[auctionId]; } }; const enc = window.encodeURIComponent; @@ -125,6 +134,10 @@ function sendNewBeacon(auctionId, adUnitCode) { sendRequest(cache.getAuction(auctionId, adUnitCode)); }; +function getTargetedAuctionId(bid) { + return deepAccess(bid, 'latestTargetedAuctionId') || deepAccess(bid, 'auctionId'); +} + /** * END UTILS FUNCTIONS */ @@ -196,6 +209,9 @@ function handlerAuctionInit(event) { // We assume that all Adagio bids for a same adunit have the same params. const params = adagioAdUnitBids[0].params; + const adagioAuctionId = params.adagioAuctionId; + cache.addPrebidAuctionIdRef(prebidAuctionId, adagioAuctionId); + // Get all media types requested for Adagio. const adagioMediaTypes = removeDuplicates( adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), @@ -208,7 +224,7 @@ function handlerAuctionInit(event) { org_id: params.organizationId, site: params.site, pv_id: params.pageviewId, - auct_id: params.adagioAuctionId, + auct_id: adagioAuctionId, adu_code: adUnitCode, url_dmn: w.location.hostname, dvc: params.environment, @@ -247,7 +263,9 @@ function handlerBidResponse(event) { }; function handlerBidWon(event) { - if (!guard.bidTracked(event.auctionId, event.adUnitCode)) { + let auctionId = getTargetedAuctionId(event); + + if (!guard.bidTracked(auctionId, event.adUnitCode)) { return; } @@ -266,7 +284,12 @@ function handlerBidWon(event) { logError('Error on Adagio Analytics Adapter - handlerBidWon', error); } - cache.updateAuction(event.auctionId, event.adUnitCode, { + const adagioAuctionCacheId = ( + (event.latestTargetedAuctionId && event.latestTargetedAuctionId !== event.auctionId) + ? cache.getAdagioAuctionId(event.auctionId) + : null); + + cache.updateAuction(auctionId, event.adUnitCode, { win_bdr: getAdapterNameForAlias(event.bidder), win_mt: getMediaTypeAlias(event.mediaType), win_ban_sz: event.mediaType === BANNER ? `${event.width}x${event.height}` : null, @@ -280,12 +303,17 @@ function handlerBidWon(event) { og_cpm: event.originalCpm, og_cur: event.originalCurrency, og_cur_rate: ogCurRateToUSD, + + // cache bid id + auct_id_c: adagioAuctionCacheId, }); - sendNewBeacon(event.auctionId, event.adUnitCode); + sendNewBeacon(auctionId, event.adUnitCode); }; function handlerAdRender(event, isSuccess) { - const { auctionId, adUnitCode } = event.bid; + const { adUnitCode } = event.bid; + let auctionId = getTargetedAuctionId(event.bid); + if (!guard.bidTracked(auctionId, adUnitCode)) { return; } diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 39fb5d2d068..8359a34baa0 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -178,6 +178,9 @@ describe('adagio analytics adapter - adagio.js', () => { }); const AUCTION_ID = '25c6d7f5-699a-4bfc-87c9-996f915341fa'; +const AUCTION_ID_ADAGIO = '6fc53663-bde5-427b-ab63-baa9ed296f47' +const AUCTION_ID_CACHE = 'b43d24a0-13d4-406d-8176-3181402bafc4'; +const AUCTION_ID_CACHE_ADAGIO = 'a9cae98f-efb5-477e-9259-27350044f8db'; const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { bidder: 'adagio', @@ -242,6 +245,11 @@ const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { } }); +const BID_CACHED = Object.assign({}, BID_ADAGIO, { + auctionId: AUCTION_ID_CACHE, + latestTargetedAuctionId: BID_ADAGIO.auctionId, +}); + const PARAMS_ADG = { organizationId: '1001', site: 'test-com', @@ -251,146 +259,288 @@ const PARAMS_ADG = { placement: 'pave_top' }; -const MOCK = { - SET_TARGETING: { - [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, - [BID_ANOTHER.adUnitCode]: BID_ANOTHER.adserverTargeting - }, - AUCTION_INIT: { +const AUCTION_INIT_ANOTHER = { + 'auctionId': AUCTION_ID, + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 100 + ] + ] + } + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + }, { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG + }, + }, ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + }, { + 'code': '/19968336/footer-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ] + ] + } + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } ], + 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'another', 'auctionId': AUCTION_ID, - 'timestamp': 1519767010567, - 'auctionStatus': 'inProgress', - 'adUnits': [ { - 'code': '/19968336/header-bid-tag-1', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001', + }, 'mediaTypes': { 'banner': { - 'sizes': [ - [ - 640, - 480 - ], - [ - 640, - 100 - ] - ] + 'sizes': [[640, 480]] } }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], - 'bids': [ { - 'bidder': 'another', - 'params': { - 'publisherId': '1001' - }, - }, { - 'bidder': 'adagio', - 'params': { - ...PARAMS_ADG - }, - }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 }, { - 'code': '/19968336/footer-bid-tag-1', + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, 'mediaTypes': { 'banner': { - 'sizes': [ - [ - 640, - 480 - ] - ] + 'sizes': [[640, 480]] } }, + 'adUnitCode': '/19968336/footer-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', 'sizes': [[640, 480]], - 'bids': [ { - 'bidder': 'another', - 'params': { - 'publisherId': '1001' - }, - } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' - } ], - 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], - 'bidderRequests': [ { - 'bidderCode': 'another', + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + }, { + 'bidderCode': 'adagio', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG, + adagioAuctionId: AUCTION_ID_ADAGIO + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', 'bidderRequestId': '1be65d7958826a', - 'bids': [ { - 'bidder': 'another', - 'params': { - 'publisherId': '1001', - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - }, { - 'bidder': 'another', - 'params': { - 'publisherId': '1001' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/footer-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 +}; + +const AUCTION_INIT_CACHE = { + 'auctionId': AUCTION_ID_CACHE, + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 100 + ] + ] } - ], - 'timeout': 3000, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + }, { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG + }, + }, ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + }, { + 'code': '/19968336/footer-bid-tag-1', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ] + ] } + }, + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } ], + 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'another', + 'auctionId': AUCTION_ID_CACHE, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'another', + 'params': { + 'publisherId': '1001', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID_CACHE, + 'src': 'client', + 'bidRequestsCount': 1 }, { - 'bidderCode': 'adagio', - 'auctionId': AUCTION_ID, + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/footer-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', 'bidderRequestId': '1be65d7958826a', - 'bids': [ { - 'bidder': 'adagio', - 'params': { - ...PARAMS_ADG, - adagioAuctionId: '6fc53663-bde5-427b-ab63-baa9ed296f47' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } + 'auctionId': AUCTION_ID_CACHE, + 'src': 'client', + 'bidRequestsCount': 1 } ], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 3000 + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + }, { + 'bidderCode': 'adagio', + 'auctionId': AUCTION_ID_CACHE, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG, + adagioAuctionId: AUCTION_ID_CACHE_ADAGIO + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID_CACHE, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 +}; + +const MOCK = { + SET_TARGETING: { + [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, + [BID_ANOTHER.adUnitCode]: BID_ANOTHER.adserverTargeting + }, + AUCTION_INIT: { + another: AUCTION_INIT_ANOTHER, + bidcached: AUCTION_INIT_CACHE }, BID_RESPONSE: { adagio: BID_ADAGIO, @@ -402,12 +552,22 @@ const MOCK = { }), another: Object.assign({}, BID_ANOTHER, { 'status': 'rendered' - }) + }), + bidcached: Object.assign({}, BID_CACHED, { + 'status': 'rendered' + }), }, AD_RENDER_SUCCEEDED: { - ad: '
ad
', - adId: 'fake_ad_id_2', - bid: BID_ANOTHER + another: { + ad: '
ad
', + adId: 'fake_ad_id_2', + bid: BID_ANOTHER + }, + bidcached: { + ad: '
ad
', + adId: 'fake_ad_id_2', + bid: BID_CACHED + } }, }; @@ -453,11 +613,11 @@ describe('adagio analytics adapter', () => { return cpm * (convKeys[`${from}-${to}`] || 1); }; - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); - events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED); + events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); expect(server.requests.length).to.equal(3); { @@ -467,7 +627,7 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -488,7 +648,7 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); - expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.adg_sid).to.equal('42'); expect(search.win_bdr).to.equal('another'); @@ -508,7 +668,106 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal('6fc53663-bde5-427b-ab63-baa9ed296f47'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.rndr).to.equal('1'); + } + }); + + it('builds and sends auction data with a cached bid win', () => { + getGlobal().convertCurrency = (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + }; + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.bidcached); + events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.bidcached); + + expect(server.requests.length).to.equal(4); + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.auct_id).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.dvc).to.equal('desktop'); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal('pave_top'); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x100,640x480'); + expect(search.bdrs).to.equal('adagio,another'); + expect(search.adg_mts).to.equal('ban'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.dvc).to.equal('desktop'); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal('pave_top'); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x100,640x480'); + expect(search.bdrs).to.equal('adagio,another'); + expect(search.adg_mts).to.equal('ban'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.adg_sid).to.equal('42'); + expect(search.win_bdr).to.equal('adagio'); + expect(search.win_mt).to.equal('ban'); + expect(search.win_ban_sz).to.equal('728x90'); + expect(search.win_cpm).to.equal('1.42'); + expect(search.cur).to.equal('USD'); + expect(search.cur_rate).to.equal('1'); + expect(search.og_cpm).to.equal('1.42'); + expect(search.og_cur).to.equal('USD'); + expect(search.og_cur_rate).to.equal('1'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('3'); + expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.rndr).to.equal('1'); } From c02706150b3d728d2a237dfe24e45f6b9daa672b Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Wed, 15 Nov 2023 14:40:30 +0200 Subject: [PATCH 194/760] Missena Bid Adapter: floor implementation (#10534) * Missena: floor implementation * Missena: only floor, tests * Missena: pass adServerCurrency to server * Remove the custom calculation of the size parameter in getFloor() --------- Co-authored-by: youssef --- modules/missenaBidAdapter.js | 42 ++++++++++++++++++-- test/spec/modules/missenaBidAdapter_spec.js | 44 ++++++++++++++++++--- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 33fa6857e85..1ab6d0c6945 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,4 +1,11 @@ -import { buildUrl, formatQS, logInfo, triggerPixel } from '../src/utils.js'; +import { + buildUrl, + formatQS, + isFn, + logInfo, + triggerPixel, +} from '../src/utils.js'; +import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -7,6 +14,22 @@ const ENDPOINT_URL = 'https://bid.missena.io/'; const EVENTS_DOMAIN = 'events.missena.io'; const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; +/* Get Floor price information */ +function getFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return {}; + } + + const bidFloors = bidRequest.getFloor({ + currency: 'USD', + mediaType: BANNER + }); + + if (!isNaN(bidFloors.floor)) { + return bidFloors; + } +} + export const spec = { aliases: ['msna'], code: BIDDER_CODE, @@ -61,6 +84,12 @@ export const spec = { payload.is_internal = bidRequest.params.isInternal; } payload.userEids = bidRequest.userIdAsEids || []; + + const bidFloor = getFloor(bidRequest); + payload.floor = bidFloor?.floor; + payload.floor_currency = bidFloor?.currency; + payload.currency = config.getConfig('currency.adServerCurrency') || 'EUR'; + return { method: 'POST', url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), @@ -89,7 +118,7 @@ export const spec = { syncOptions, serverResponses, gdprConsent, - uspConsent + uspConsent, ) { if (!syncOptions.iframeEnabled) { return []; @@ -128,8 +157,13 @@ export const spec = { protocol: 'https', hostname, pathname: '/v1/bidsuccess', - search: { t: bid.params[0].apiKey, provider: bid.meta?.networkName, cpm: bid.cpm, currency: bid.currency }, - }) + search: { + t: bid.params[0].apiKey, + provider: bid.meta?.networkName, + cpm: bid.originalCpm, + currency: bid.originalCurrency, + }, + }), ); logInfo('Missena - Bid won', bid); }, diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index f61987298e8..e874aabbc70 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER } from '../../../src/mediaTypes.js'; describe('Missena Adapter', function () { const adapter = newBidder(spec); @@ -11,6 +12,28 @@ describe('Missena Adapter', function () { bidder: 'missena', bidId: bidId, sizes: [[1, 1]], + mediaTypes: { banner: { sizes: [[1, 1]] } }, + params: { + apiKey: 'PA-34745704', + placement: 'sticky', + formats: ['sticky-banner'], + }, + getFloor: (inputParams) => { + if (inputParams.mediaType === BANNER) { + return { + currency: 'EUR', + floor: 3.5, + }; + } else { + return {}; + } + }, + }; + const bidWithoutFloor = { + bidder: 'missena', + bidId: bidId, + sizes: [[1, 1]], + mediaTypes: { banner: { sizes: [[1, 1]] } }, params: { apiKey: 'PA-34745704', placement: 'sticky', @@ -31,13 +54,13 @@ describe('Missena Adapter', function () { it('should return false if the apiKey is missing', function () { expect( - spec.isBidRequestValid(Object.assign(bid, { params: {} })) + spec.isBidRequestValid(Object.assign(bid, { params: {} })), ).to.equal(false); }); it('should return false if the apiKey is an empty string', function () { expect( - spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })) + spec.isBidRequestValid(Object.assign(bid, { params: { apiKey: '' } })), ).to.equal(false); }); }); @@ -56,9 +79,10 @@ describe('Missena Adapter', function () { }, }; - const requests = spec.buildRequests([bid, bid], bidderRequest); + const requests = spec.buildRequests([bid, bidWithoutFloor], bidderRequest); const request = requests[0]; const payload = JSON.parse(request.data); + const payloadNoFloor = JSON.parse(requests[1].data); it('should return as many server requests as bidder requests', function () { expect(requests.length).to.equal(2); @@ -89,6 +113,14 @@ describe('Missena Adapter', function () { expect(payload.consent_string).to.equal(consentString); expect(payload.consent_required).to.equal(true); }); + it('should send floor data', function () { + expect(payload.floor).to.equal(3.5); + expect(payload.floor_currency).to.equal('EUR'); + }); + it('should not send floor data if not available', function () { + expect(payloadNoFloor.floor).to.equal(undefined); + expect(payloadNoFloor.floor_currency).to.equal(undefined); + }); }); describe('interpretResponse', function () { @@ -121,14 +153,14 @@ describe('Missena Adapter', function () { expect(result.length).to.equal(1); expect(Object.keys(result[0])).to.have.members( - Object.keys(serverResponse) + Object.keys(serverResponse), ); }); it('should return an empty response when the server answers with a timeout', function () { const result = spec.interpretResponse( { body: serverTimeoutResponse }, - bid + bid, ); expect(result).to.deep.equal([]); }); @@ -136,7 +168,7 @@ describe('Missena Adapter', function () { it('should return an empty response when the server answers with an empty ad', function () { const result = spec.interpretResponse( { body: serverEmptyAdResponse }, - bid + bid, ); expect(result).to.deep.equal([]); }); From 67975d75572016efce323807260419ce80372460 Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:41:18 +0800 Subject: [PATCH 195/760] FreeWheel SSP Adapter: public vastXml in bidResponse for all media types (#10730) * FreeWheel add floor price * FreeWheel code update * FreeWheel-SSP-Adapter: Update to use Vast 4.2 by default * FreeWheel-SSP-Adapter add userIdAsEids support * Freewheel-SSP-Adapter add test for eids * Freewheel SSP Adapter: add prebid version in request * code cleanup * FreeWheel SSP Bid Adapter: support video context and placement * update test * FreeWheel SSP Bid Adapter: add GPP support * Freewheel SSP Bid Adapter: test update * FreeWheel SSP Adapter: update the default value for video placement and context * update test * FreeWheel SSP Adapter: add support for video.plcmt * FreeWheel Adapter: support content object in config * code update * FreeWheel SSP Adapter: add gvlid in spec * FreeWheel SSP Adapter: update code for site.content * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: code update * FreeWheel SSP Adapter: public vastXml in bidResponse for all media types --- modules/freewheel-sspBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 5e6cee71630..ba819abd687 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -543,10 +543,11 @@ export const spec = { }; if (bidrequest.mediaTypes.video) { - bidResponse.vastXml = serverResponse; bidResponse.mediaType = 'video'; } + bidResponse.vastXml = serverResponse; + bidResponse.ad = formatAdHTML(bidrequest, playerSize); bidResponses.push(bidResponse); } From be9338a770121c30e63270e251880087739ffba2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 15 Nov 2023 08:06:51 -0800 Subject: [PATCH 196/760] Log unhandled errors and rejections (#10723) --- test/test_deps.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_deps.js b/test/test_deps.js index 35713106f8c..c8a3bcc9426 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -4,6 +4,16 @@ window.process = { } }; +window.addEventListener('error', function (ev) { + // eslint-disable-next-line no-console + console.error('Uncaught exception:', ev.error, ev.error?.stack); +}) + +window.addEventListener('unhandledrejection', function (ev) { + // eslint-disable-next-line no-console + console.error('Unhandled rejection:', ev.reason); +}) + require('test/helpers/consentData.js'); require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); From 24ff9a65c7191530d767cfb36e754a7cf55000e3 Mon Sep 17 00:00:00 2001 From: Doceree-techStack <143162581+Doceree-techStack@users.noreply.github.com> Date: Wed, 15 Nov 2023 21:40:24 +0530 Subject: [PATCH 197/760] Doceree AdManager Bid Adapter: initial release (#10684) * added DocereeAdManager bid Adapter * adapter prod api added * new configs added * md file changes * double quotes to singlequotes * removed unwanted code * empty commit * bidder name lower cased and few configs added * md file changes * spec file changes * empty commit * empty commit * issues resolved * empty commit * issue fixes * minor change * empty commit * minor change * empty commit * conditional changes * removed unwanted code --------- Co-authored-by: sagar4596 <132884303+sagar4596@users.noreply.github.com> --- modules/docereeAdManagerBidAdapter.js | 128 ++++++++++++++++++ modules/docereeAdManagerBidAdapter.md | 68 ++++++++++ .../docereeAdManagerBidAdapter_spec.js | 125 +++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 modules/docereeAdManagerBidAdapter.js create mode 100644 modules/docereeAdManagerBidAdapter.md create mode 100644 test/spec/modules/docereeAdManagerBidAdapter_spec.js diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js new file mode 100644 index 00000000000..d3765f5a130 --- /dev/null +++ b/modules/docereeAdManagerBidAdapter.js @@ -0,0 +1,128 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +const BIDDER_CODE = 'docereeadmanager'; +const END_POINT = 'https://dai.doceree.com/drs/quest'; + +export const spec = { + code: BIDDER_CODE, + url: '', + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bid) => { + const { placementId } = bid.params; + return !!placementId; + }, + isGdprConsentPresent: (bid) => { + const { gdpr, gdprconsent } = bid.params; + if (gdpr == '1') { + return !!gdprconsent; + } + return true; + }, + buildRequests: (validBidRequests) => { + const serverRequests = []; + const { data } = config.getConfig('docereeadmanager.user') || {}; + + validBidRequests.forEach(function (validBidRequest) { + const payload = getPayload(validBidRequest, data); + + if (!payload) { + return; + } + + serverRequests.push({ + method: 'POST', + url: END_POINT, + data: JSON.stringify(payload.data), + options: { + contentType: 'application/json', + withCredentials: true, + }, + }); + }); + + return serverRequests; + }, + interpretResponse: (serverResponse) => { + const responseJson = serverResponse ? serverResponse.body : {}; + const bidResponse = { + ad: responseJson.ad, + width: Number(responseJson.width), + height: Number(responseJson.height), + requestId: responseJson.requestId, + netRevenue: true, + ttl: 30, + cpm: responseJson.cpm, + currency: responseJson.currency, + mediaType: BANNER, + creativeId: responseJson.creativeId, + meta: { + advertiserDomains: + Array.isArray(responseJson.meta.advertiserDomains) && + responseJson.meta.advertiserDomains.length > 0 + ? responseJson.meta.advertiserDomains + : [], + }, + }; + + return [bidResponse]; + }, +}; + +function getPayload(bid, userData) { + if (!userData || !bid) { + return false; + } + + const { bidId, params } = bid; + const { placementId } = params; + const { + userid, + email, + firstname, + lastname, + specialization, + hcpid, + gender, + city, + state, + zipcode, + hashedNPI, + hashedhcpid, + hashedemail, + hashedmobile, + country, + organization, + dob, + } = userData; + + const data = { + userid: userid || '', + email: email || '', + firstname: firstname || '', + lastname: lastname || '', + specialization: specialization || '', + hcpid: hcpid || '', + gender: gender || '', + city: city || '', + state: state || '', + zipcode: zipcode || '', + hashedNPI: hashedNPI || '', + pb: 1, + adunit: placementId || '', + requestId: bidId || '', + hashedhcpid: hashedhcpid || '', + hashedemail: hashedemail || '', + hashedmobile: hashedmobile || '', + country: country || '', + organization: organization || '', + dob: dob || '', + userconsent: 1, + }; + return { + data, + }; +} + +registerBidder(spec); diff --git a/modules/docereeAdManagerBidAdapter.md b/modules/docereeAdManagerBidAdapter.md new file mode 100644 index 00000000000..bedbf57b179 --- /dev/null +++ b/modules/docereeAdManagerBidAdapter.md @@ -0,0 +1,68 @@ +# Overview + +``` +Module Name: Doceree AdManager Bidder Adapter +Module Type: Bidder Adapter +Maintainer: tech.stack@doceree.com +``` + + + +Connects to Doceree demand source to fetch bids. +Please use `docereeadmanager` as the bidder code. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'DOC-397-1', + sizes: [ + [300, 250] + ], + bids: [ + { + bidder: 'docereeadmanager', + params: { + placementId: 'DOC-19-1', //required + publisherUrl: document.URL || window.location.href, //optional + gdpr: '1', //optional + gdprconsent:'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', //optional + } + } + ] + } +]; +``` + +```javascript +pbjs.setBidderConfig({ + bidders: ['docereeadmanager'], + config: { + docereeadmanager: { + user: { + data: { + email: 'XXX.XXX@GMAIL.COM', + firstname: 'DR. XXX', + lastname: 'XXX', + mobile: '981234XXXX', + specialization: 'Internal Medicine', + organization: 'Max Lifecare', + hcpid: '199291XXXX', + dob: '1987-08-27', + gender: 'Female', + city: 'Oildale', + state: 'California', + country: 'California', + hashedhcpid: '', + hashedemail: '', + hashedmobile: '', + userid: '7d26d8ca-233a-46c2-9d36-7c5d261e151d', + zipcode: '', + userconsent: '1', + }, + }, + }, + }, +}); +``` diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js new file mode 100644 index 00000000000..26b054f4e29 --- /dev/null +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -0,0 +1,125 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/docereeAdManagerBidAdapter.js'; +import { config } from '../../../src/config.js'; + +describe('docereeadmanager', function () { + config.setConfig({ + docereeadmanager: { + user: { + data: { + email: '', + firstname: '', + lastname: '', + mobile: '', + specialization: '', + organization: '', + hcpid: '', + dob: '', + gender: '', + city: '', + state: '', + country: '', + hashedhcpid: '', + hashedemail: '', + hashedmobile: '', + userid: '', + zipcode: '', + userconsent: '', + }, + }, + }, + }); + let bid = { + bidId: 'testing', + bidder: 'docereeadmanager', + params: { + placementId: 'DOC-19-1', + gdpr: '1', + gdprconsent: + 'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', + }, + }; + + describe('isBidRequestValid', function () { + it('Should return true if placementId is present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if placementId is not present', function () { + delete bid.params.placementId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('isGdprConsentPresent', function () { + it('Should return true if gdpr consent is present', function () { + expect(spec.isGdprConsentPresent(bid)).to.be.true; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests([bid]); + serverRequest = serverRequest[0]; + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://dai.doceree.com/drs/quest'); + }); + }); + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: { + cpm: 3.576, + currency: 'USD', + width: 250, + height: 300, + ad: '

I am an ad

', + ttl: 30, + creativeId: 'div-1', + netRevenue: false, + bidderCode: '123', + dealId: 232, + requestId: '123', + meta: { + brandId: null, + advertiserDomains: ['https://dai.doceree.com/drs/quest'], + }, + }, + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys( + 'requestId', + 'cpm', + 'width', + 'height', + 'ad', + 'ttl', + 'netRevenue', + 'currency', + 'mediaType', + 'creativeId', + 'meta' + ); + expect(dataItem.requestId).to.equal('123'); + expect(dataItem.cpm).to.equal(3.576); + expect(dataItem.width).to.equal(250); + expect(dataItem.height).to.equal(300); + expect(dataItem.ad).to.equal('

I am an ad

'); + expect(dataItem.ttl).to.equal(30); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.creativeId).to.equal('div-1'); + expect(dataItem.meta.advertiserDomains).to.be.an('array').that.is.not + .empty; + }); + }); +}); From 31747cf0dd52528ac2643eab7f2725e12c243522 Mon Sep 17 00:00:00 2001 From: Christian <98148000+duduchristian@users.noreply.github.com> Date: Thu, 16 Nov 2023 00:40:07 +0800 Subject: [PATCH 198/760] Operaads Bid Adapter: provide a way to build bid requests for applications (#10675) Co-authored-by: hongxingp --- modules/operaadsBidAdapter.js | 40 +++++++-- modules/operaadsBidAdapter.md | 56 ++++++------ test/spec/modules/operaadsBidAdapter_spec.js | 89 ++++++++++++++++++++ 3 files changed, 152 insertions(+), 33 deletions(-) diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index b28a24ef57a..131ba0bc1f2 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -232,13 +232,6 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { test: config.getConfig('debug') ? 1 : 0, imp: createImp(bidRequest), device: getDevice(), - site: { - id: String(deepAccess(bidRequest, 'params.publisherId')), - // TODO: does the fallback make sense here? - domain: bidderRequest?.refererInfo?.domain || window.location.host, - page: bidderRequest?.refererInfo?.page, - ref: bidderRequest?.refererInfo?.ref || '', - }, at: 1, bcat: getBcat(bidRequest), cur: [DEFAULT_CURRENCY], @@ -250,6 +243,7 @@ function buildOpenRtbBidRequest(bidRequest, bidderRequest) { buyeruid: getUserId(bidRequest) } } + fulfillInventoryInfo(payload, bidRequest, bidderRequest); const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); if (!!gdprConsent && gdprConsent.gdprApplies) { @@ -764,6 +758,38 @@ function getDevice() { return device; } +/** + * Fulfill inventory info + * + * @param payload + * @param bidRequest + * @param bidderRequest + */ +function fulfillInventoryInfo(payload, bidRequest, bidderRequest) { + let info = deepAccess(bidRequest, 'params.site'); + // 1.If the inventory info for site specified, use the site object provided in params. + let key = 'site'; + if (!isPlainObject(info)) { + info = deepAccess(bidRequest, 'params.app'); + if (isPlainObject(info)) { + // 2.If the inventory info for app specified, use the app object provided in params. + key = 'app'; + } else { + // 3.Otherwise, we use site by default. + info = {}; + } + } + // Fulfill key parameters. + info.id = String(deepAccess(bidRequest, 'params.publisherId')); + info.domain = info.domain || bidderRequest?.refererInfo?.domain || window.location.host; + if (key === 'site') { + info.ref = info.ref || bidderRequest?.refererInfo?.ref || ''; + info.page = info.page || bidderRequest?.refererInfo?.page; + } + + payload[key] = info; +} + /** * Get browser language * diff --git a/modules/operaadsBidAdapter.md b/modules/operaadsBidAdapter.md index 6c5a4646dd0..6f13eebd7d5 100644 --- a/modules/operaadsBidAdapter.md +++ b/modules/operaadsBidAdapter.md @@ -14,41 +14,43 @@ Module that connects to OperaAds's demand sources ## Bid Parameters -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `placementId` | required | String | The Placement Id provided by Opera Ads. | `s5340077725248` -| `endpointId` | required | String | The Endpoint Id provided by Opera Ads. | `ep3425464070464` -| `publisherId` | required | String | The Publisher Id provided by Opera Ads. | `pub3054952966336` -| `bcat` | optional | String or String[] | The bcat value. | `IAB9-31` +| Name | Scope | Type | Description | Example | +|---------------|----------|--------------------|-----------------------------------------|-------------------------------------------------| +| `placementId` | required | String | The Placement Id provided by Opera Ads. | `s5340077725248` | +| `endpointId` | required | String | The Endpoint Id provided by Opera Ads. | `ep3425464070464` | +| `publisherId` | required | String | The Publisher Id provided by Opera Ads. | `pub3054952966336` | +| `bcat` | optional | String or String[] | The bcat value. | `IAB9-31` | +| `site` | optional | Object | The site information. | `{"name": "my_site", "domain": "www.test.com"}` | +| `app` | optional | Object | The app information. | `{"name": "my_app", "ver": "1.1.0"}` | ### Bid Video Parameters Set these parameters to `bid.mediaTypes.video`. -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `context` | optional | String | `instream` or `outstream`. | `instream` -| `mimes` | optional | String[] | Content MIME types supported. | `['video/mp4']` -| `playerSize` | optional | Number[] or Number[][] | Video player size in device independent pixels | `[[640, 480]]` -| `protocols` | optional | Number[] | Array of supported video protocls. | `[1, 2, 3, 4, 5, 6, 7, 8]` -| `startdelay` | optional | Number | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | `0` -| `skip` | optional | Number | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes. | `1` -| `playbackmethod` | optional | Number[] | Playback methods that may be in use. | `[2]` -| `delivery` | optional | Number[] | Supported delivery methods. | `[1]` -| `api` | optional | Number[] | List of supported API frameworks for this impression. | `[1, 2, 5]` +| Name | Scope | Type | Description | Example | +|------------------|----------|------------------------|------------------------------------------------------------------------------------------|----------------------------| +| `context` | optional | String | `instream` or `outstream`. | `instream` | +| `mimes` | optional | String[] | Content MIME types supported. | `['video/mp4']` | +| `playerSize` | optional | Number[] or Number[][] | Video player size in device independent pixels | `[[640, 480]]` | +| `protocols` | optional | Number[] | Array of supported video protocls. | `[1, 2, 3, 4, 5, 6, 7, 8]` | +| `startdelay` | optional | Number | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | `0` | +| `skip` | optional | Number | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes. | `1` | +| `playbackmethod` | optional | Number[] | Playback methods that may be in use. | `[2]` | +| `delivery` | optional | Number[] | Supported delivery methods. | `[1]` | +| `api` | optional | Number[] | List of supported API frameworks for this impression. | `[1, 2, 5]` | ### Bid Native Parameters Set these parameters to `bid.nativeParams` or `bid.mediaTypes.native`. -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `title` | optional | Object | Config for native asset title. | `{required: true, len: 25}` -| `image` | optional | Object | Config for native asset image. | `{required: true, sizes: [[300, 250]], aspect_ratios: [{min_width: 300, min_height: 250, ratio_width: 1, ratio_height: 1}]}` -| `icon` | optional | Object | Config for native asset icon. | `{required: true, sizes: [[60, 60]], aspect_ratios: [{min_width: 60, min_height: 60, ratio_width: 1, ratio_height: 1}]}}` -| `sponsoredBy` | optional | Object | Config for native asset sponsoredBy. | `{required: true, len: 20}` -| `body` | optional | Object | Config for native asset body. | `{required: true, len: 200}` -| `cta` | optional | Object | Config for native asset cta. | `{required: true, len: 20}` +| Name | Scope | Type | Description | Example | +|---------------|----------|--------|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| `title` | optional | Object | Config for native asset title. | `{required: true, len: 25}` | +| `image` | optional | Object | Config for native asset image. | `{required: true, sizes: [[300, 250]], aspect_ratios: [{min_width: 300, min_height: 250, ratio_width: 1, ratio_height: 1}]}` | +| `icon` | optional | Object | Config for native asset icon. | `{required: true, sizes: [[60, 60]], aspect_ratios: [{min_width: 60, min_height: 60, ratio_width: 1, ratio_height: 1}]}}` | +| `sponsoredBy` | optional | Object | Config for native asset sponsoredBy. | `{required: true, len: 20}` | +| `body` | optional | Object | Config for native asset body. | `{required: true, len: 200}` | +| `cta` | optional | Object | Config for native asset cta. | `{required: true, len: 20}` | ## Example @@ -127,7 +129,9 @@ var adUnits = [{ params: { placementId: 's5340077725248', endpointId: 'ep3425464070464', - publisherId: 'pub3054952966336' + publisherId: 'pub3054952966336', + // You might want to specify some application information here if the bid requests are from an application instead of a browser. + app: { 'name': 'my_app', 'bundle': 'test_bundle', 'store_url': 'www.some-store.com', 'ver': '1.1.0' } } }] }]; diff --git a/test/spec/modules/operaadsBidAdapter_spec.js b/test/spec/modules/operaadsBidAdapter_spec.js index 37d4a2c7bc0..9a8981235d5 100644 --- a/test/spec/modules/operaadsBidAdapter_spec.js +++ b/test/spec/modules/operaadsBidAdapter_spec.js @@ -266,6 +266,95 @@ describe('Opera Ads Bid Adapter', function () { } }); + describe('test fulfilling inventory information', function () { + const bidRequest = { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'operaads', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: {sizes: [[300, 250]]} + }, + params: { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678' + } + } + + const getRequest = function () { + let reqs; + expect(function () { + reqs = spec.buildRequests([bidRequest], bidderRequest); + }).to.not.throw(); + return JSON.parse(reqs[0].data); + } + + it('test default case', function () { + let requestData = getRequest(); + expect(requestData.site).to.be.an('object'); + expect(requestData.site.id).to.equal(bidRequest.params.publisherId); + expect(requestData.site.domain).to.not.be.empty; + expect(requestData.site.page).to.equal(bidderRequest.refererInfo.page); + }); + + it('test a case with site information specified', function () { + bidRequest.params = { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678', + site: { + name: 'test-site-1', + domain: 'www.test.com' + } + } + let requestData = getRequest(); + expect(requestData.site).to.be.an('object'); + expect(requestData.site.id).to.equal(bidRequest.params.publisherId); + expect(requestData.site.name).to.equal('test-site-1'); + expect(requestData.site.domain).to.equal('www.test.com'); + expect(requestData.site.page).to.equal(bidderRequest.refererInfo.page); + }); + + it('test a case with app information specified', function () { + bidRequest.params = { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678', + app: { + name: 'test-app-1' + } + } + let requestData = getRequest(); + expect(requestData.app).to.be.an('object'); + expect(requestData.app.id).to.equal(bidRequest.params.publisherId); + expect(requestData.app.name).to.equal('test-app-1'); + expect(requestData.app.domain).to.not.be.empty; + }); + + it('test a case with both site and app information specified', function () { + bidRequest.params = { + placementId: 's12345678', + publisherId: 'pub12345678', + endpointId: 'ep12345678', + site: { + name: 'test-site-2', + page: 'test-page' + }, + app: { + name: 'test-app-1' + } + } + let requestData = getRequest(); + expect(requestData.site).to.be.an('object'); + expect(requestData.site.id).to.equal(bidRequest.params.publisherId); + expect(requestData.site.name).to.equal('test-site-2'); + expect(requestData.site.page).to.equal('test-page'); + expect(requestData.site.domain).to.not.be.empty; + }); + }); + it('test getBidFloor', function() { const bidRequests = [ { From 97d3daa3fad1949587cc76e04987a370bd1b8617 Mon Sep 17 00:00:00 2001 From: Desvillettes <30619957+AurelienMozoo@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:48:38 +0100 Subject: [PATCH 199/760] Ogury Bid Adapter: support userId and userIdAsEids (#10669) * get userId and userIdasEids if exist * unit tests updated and refactor --- modules/oguryBidAdapter.js | 9 ++- test/spec/modules/oguryBidAdapter_spec.js | 92 +++++++++++++++++++++-- 2 files changed, 92 insertions(+), 9 deletions(-) diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index c1c8376de87..9937391f6e7 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -12,7 +12,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.5.0'; +const ADAPTER_VERSION = '1.6.0'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -122,6 +122,13 @@ function buildRequests(validBidRequests, bidderRequest) { openRtbBidRequestBanner.site.id = bidRequest.params.assetKey; const floor = getFloor(bidRequest); + if (bidRequest.userId) { + openRtbBidRequestBanner.user.ext.uids = bidRequest.userId + } + if (bidRequest.userIdAsEids) { + openRtbBidRequestBanner.user.ext.eids = bidRequest.userIdAsEids + } + openRtbBidRequestBanner.imp.push({ id: bidRequest.bidId, tagid: bidRequest.params.adUnitId, diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index ed358af19b6..aad753571a8 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -42,7 +42,21 @@ describe('OguryBidAdapter', function () { return floorResult; }, - transactionId: 'transactionId' + transactionId: 'transactionId', + userId: { + pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' + }, + userIdAsEids: [ + { + source: 'pubcid.org', + uids: [ + { + id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', + atype: 1 + } + ] + } + ] }, { adUnitCode: 'adUnitCode2', @@ -407,12 +421,26 @@ describe('OguryBidAdapter', function () { }, user: { ext: { - consent: bidderRequest.gdprConsent.consentString + consent: bidderRequest.gdprConsent.consentString, + uids: { + pubcid: '2abb10e5-c4f6-4f70-9f45-2200e4487714' + }, + eids: [ + { + source: 'pubcid.org', + uids: [ + { + id: '2abb10e5-c4f6-4f70-9f45-2200e4487714', + atype: 1 + } + ] + } + ], }, }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.5.0' + adapterversion: '1.6.0' }, device: { w: stubbedWidth, @@ -637,7 +665,9 @@ describe('OguryBidAdapter', function () { }, user: { ext: { - consent: '' + consent: '', + uids: expectedRequestObject.user.ext.uids, + eids: expectedRequestObject.user.ext.eids }, } }; @@ -663,7 +693,9 @@ describe('OguryBidAdapter', function () { }, user: { ext: { - consent: '' + consent: '', + uids: expectedRequestObject.user.ext.uids, + eids: expectedRequestObject.user.ext.eids }, } }; @@ -689,7 +721,9 @@ describe('OguryBidAdapter', function () { }, user: { ext: { - consent: '' + consent: '', + uids: expectedRequestObject.user.ext.uids, + eids: expectedRequestObject.user.ext.eids }, } }; @@ -701,6 +735,48 @@ describe('OguryBidAdapter', function () { expect(request.data.regs.ext.gdpr).to.be.a('number'); }); + it('should should not add uids infos if userId is undefined', () => { + const expectedRequestWithUndefinedUserId = { + ...expectedRequestObject, + user: { + ext: { + consent: expectedRequestObject.user.ext.consent, + eids: expectedRequestObject.user.ext.eids + } + } + }; + + const validBidRequests = utils.deepClone(bidRequests); + validBidRequests[0] = { + ...validBidRequests[0], + userId: undefined + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserId); + }); + + it('should should not add uids infos if userIdAsEids is undefined', () => { + const expectedRequestWithUndefinedUserIdAsEids = { + ...expectedRequestObject, + user: { + ext: { + consent: expectedRequestObject.user.ext.consent, + uids: expectedRequestObject.user.ext.uids + } + } + }; + + const validBidRequests = utils.deepClone(bidRequests); + validBidRequests[0] = { + ...validBidRequests[0], + userIdAsEids: undefined + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedUserIdAsEids); + }); + it('should handle bidFloor undefined', () => { const expectedRequestWithUndefinedFloor = { ...expectedRequestObject @@ -814,7 +890,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.5.0', + adapterVersion: '1.6.0', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -831,7 +907,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.5.0', + adapterVersion: '1.6.0', prebidVersion: '$prebid.version$' }] From c424d5835307ca53afe27b5105923aa36b36df15 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 16 Nov 2023 20:02:11 +0000 Subject: [PATCH 200/760] Prebid 8.24.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 138cd45fc61..a05ab54b862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.24.0-pre", + "version": "8.24.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 75481237d1b..2e8822cd0be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.24.0-pre", + "version": "8.24.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 055a483f52c1a5ac1e374158f0aa59983a058f41 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 16 Nov 2023 20:02:11 +0000 Subject: [PATCH 201/760] Increment version to 8.25.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a05ab54b862..caee07c67f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.24.0", + "version": "8.25.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 2e8822cd0be..7076d012285 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.24.0", + "version": "8.25.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 879b9be7f0284ce9d1958065ecaf981efe428106 Mon Sep 17 00:00:00 2001 From: Denis Anoykin Date: Thu, 16 Nov 2023 21:57:07 +0100 Subject: [PATCH 202/760] AsterioBid Analytics Adapter : initial release (#10652) --- modules/asteriobidAnalyticsAdapter.js | 336 ++++++++++++++++++ modules/asteriobidAnalyticsAdapter.md | 41 +++ .../asteriobidAnalyticsAdapter_spec.js | 151 ++++++++ 3 files changed, 528 insertions(+) create mode 100644 modules/asteriobidAnalyticsAdapter.js create mode 100644 modules/asteriobidAnalyticsAdapter.md create mode 100644 test/spec/modules/asteriobidAnalyticsAdapter_spec.js diff --git a/modules/asteriobidAnalyticsAdapter.js b/modules/asteriobidAnalyticsAdapter.js new file mode 100644 index 00000000000..516a3a65667 --- /dev/null +++ b/modules/asteriobidAnalyticsAdapter.js @@ -0,0 +1,336 @@ +import { generateUUID, getParameterByName, logError, logInfo, parseUrl } from '../src/utils.js' +import { ajaxBuilder } from '../src/ajax.js' +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' +import adapterManager from '../src/adapterManager.js' +import { getStorageManager } from '../src/storageManager.js' +import CONSTANTS from '../src/constants.json' +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js' +import {getRefererInfo} from '../src/refererDetection.js'; + +/** + * asteriobidAnalyticsAdapter.js - analytics adapter for AsterioBid + */ +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'asteriobid' }) +const DEFAULT_EVENT_URL = 'https://endpt.asteriobid.com/endpoint' +const analyticsType = 'endpoint' +const analyticsName = 'AsterioBid Analytics' +const utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'] +const _VERSION = 1 + +let ajax = ajaxBuilder(20000) +let initOptions +let auctionStarts = {} +let auctionTimeouts = {} +let sampling +let pageViewId +let flushInterval +let eventQueue = [] +let asteriobidAnalyticsEnabled = false + +let asteriobidAnalytics = Object.assign(adapter({ url: DEFAULT_EVENT_URL, analyticsType }), { + track({ eventType, args }) { + handleEvent(eventType, args) + } +}) + +asteriobidAnalytics.originEnableAnalytics = asteriobidAnalytics.enableAnalytics +asteriobidAnalytics.enableAnalytics = function (config) { + initOptions = config.options || {} + + pageViewId = initOptions.pageViewId || generateUUID() + sampling = initOptions.sampling || 1 + + if (Math.floor(Math.random() * sampling) === 0) { + asteriobidAnalyticsEnabled = true + flushInterval = setInterval(flush, 1000) + } else { + logInfo(`${analyticsName} isn't enabled because of sampling`) + } + + asteriobidAnalytics.originEnableAnalytics(config) +} + +asteriobidAnalytics.originDisableAnalytics = asteriobidAnalytics.disableAnalytics +asteriobidAnalytics.disableAnalytics = function () { + if (!asteriobidAnalyticsEnabled) { + return + } + flush() + clearInterval(flushInterval) + asteriobidAnalytics.originDisableAnalytics() +} + +function collectUtmTagData() { + let newUtm = false + let pmUtmTags = {} + try { + utmTags.forEach(function (utmKey) { + let utmValue = getParameterByName(utmKey) + if (utmValue !== '') { + newUtm = true + } + pmUtmTags[utmKey] = utmValue + }) + if (newUtm === false) { + utmTags.forEach(function (utmKey) { + let itemValue = storage.getDataFromLocalStorage(`pm_${utmKey}`) + if (itemValue && itemValue.length !== 0) { + pmUtmTags[utmKey] = itemValue + } + }) + } else { + utmTags.forEach(function (utmKey) { + storage.setDataInLocalStorage(`pm_${utmKey}`, pmUtmTags[utmKey]) + }) + } + } catch (e) { + logError(`${analyticsName} Error`, e) + pmUtmTags['error_utm'] = 1 + } + return pmUtmTags +} + +function collectPageInfo() { + const pageInfo = { + domain: window.location.hostname, + } + if (document.referrer) { + pageInfo.referrerDomain = parseUrl(document.referrer).hostname + } + + const refererInfo = getRefererInfo() + pageInfo.page = refererInfo.page + pageInfo.ref = refererInfo.ref + + return pageInfo +} + +function flush() { + if (!asteriobidAnalyticsEnabled) { + return + } + + if (eventQueue.length > 0) { + const data = { + pageViewId: pageViewId, + ver: _VERSION, + bundleId: initOptions.bundleId, + events: eventQueue, + utmTags: collectUtmTagData(), + pageInfo: collectPageInfo(), + sampling: sampling + } + eventQueue = [] + + if ('version' in initOptions) { + data.version = initOptions.version + } + if ('tcf_compliant' in initOptions) { + data.tcf_compliant = initOptions.tcf_compliant + } + if ('adUnitDict' in initOptions) { + data.adUnitDict = initOptions.adUnitDict; + } + if ('customParam' in initOptions) { + data.customParam = initOptions.customParam; + } + + const url = initOptions.url ? initOptions.url : DEFAULT_EVENT_URL + ajax( + url, + () => logInfo(`${analyticsName} sent events batch`), + _VERSION + ':' + JSON.stringify(data), + { + contentType: 'text/plain', + method: 'POST', + withCredentials: true + } + ) + } +} + +function trimAdUnit(adUnit) { + if (!adUnit) return adUnit + const res = {} + res.code = adUnit.code + res.sizes = adUnit.sizes + return res +} + +function trimBid(bid) { + if (!bid) return bid + const res = {} + res.auctionId = bid.auctionId + res.bidder = bid.bidder + res.bidderRequestId = bid.bidderRequestId + res.bidId = bid.bidId + res.crumbs = bid.crumbs + res.cpm = bid.cpm + res.currency = bid.currency + res.mediaTypes = bid.mediaTypes + res.sizes = bid.sizes + res.transactionId = bid.transactionId + res.adUnitCode = bid.adUnitCode + res.bidRequestsCount = bid.bidRequestsCount + res.serverResponseTimeMs = bid.serverResponseTimeMs + return res +} + +function trimBidderRequest(bidderRequest) { + if (!bidderRequest) return bidderRequest + const res = {} + res.auctionId = bidderRequest.auctionId + res.auctionStart = bidderRequest.auctionStart + res.bidderRequestId = bidderRequest.bidderRequestId + res.bidderCode = bidderRequest.bidderCode + res.bids = bidderRequest.bids && bidderRequest.bids.map(trimBid) + return res +} + +function handleEvent(eventType, eventArgs) { + if (!asteriobidAnalyticsEnabled) { + return + } + + try { + eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {} + } catch (e) { + // keep eventArgs as is + } + + const pmEvent = {} + pmEvent.timestamp = eventArgs.timestamp || Date.now() + pmEvent.eventType = eventType + + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + pmEvent.auctionId = eventArgs.auctionId + pmEvent.timeout = eventArgs.timeout + pmEvent.adUnits = eventArgs.adUnits && eventArgs.adUnits.map(trimAdUnit) + pmEvent.bidderRequests = eventArgs.bidderRequests && eventArgs.bidderRequests.map(trimBidderRequest) + auctionStarts[pmEvent.auctionId] = pmEvent.timestamp + auctionTimeouts[pmEvent.auctionId] = pmEvent.timeout + break + } + case CONSTANTS.EVENTS.AUCTION_END: { + pmEvent.auctionId = eventArgs.auctionId + pmEvent.end = eventArgs.end + pmEvent.start = eventArgs.start + pmEvent.adUnitCodes = eventArgs.adUnitCodes + pmEvent.bidsReceived = eventArgs.bidsReceived && eventArgs.bidsReceived.map(trimBid) + pmEvent.start = auctionStarts[pmEvent.auctionId] + pmEvent.end = Date.now() + break + } + case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + break + } + case CONSTANTS.EVENTS.BID_TIMEOUT: { + pmEvent.bidders = eventArgs && eventArgs.map ? eventArgs.map(trimBid) : eventArgs + pmEvent.duration = auctionTimeouts[pmEvent.auctionId] + break + } + case CONSTANTS.EVENTS.BID_REQUESTED: { + pmEvent.auctionId = eventArgs.auctionId + pmEvent.bidderCode = eventArgs.bidderCode + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount + pmEvent.start = eventArgs.start + pmEvent.bidderRequestId = eventArgs.bidderRequestId + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid) + pmEvent.auctionStart = eventArgs.auctionStart + pmEvent.timeout = eventArgs.timeout + break + } + case CONSTANTS.EVENTS.BID_RESPONSE: { + pmEvent.bidderCode = eventArgs.bidderCode + pmEvent.width = eventArgs.width + pmEvent.height = eventArgs.height + pmEvent.adId = eventArgs.adId + pmEvent.mediaType = eventArgs.mediaType + pmEvent.cpm = eventArgs.cpm + pmEvent.currency = eventArgs.currency + pmEvent.requestId = eventArgs.requestId + pmEvent.adUnitCode = eventArgs.adUnitCode + pmEvent.auctionId = eventArgs.auctionId + pmEvent.timeToRespond = eventArgs.timeToRespond + pmEvent.requestTimestamp = eventArgs.requestTimestamp + pmEvent.responseTimestamp = eventArgs.responseTimestamp + pmEvent.netRevenue = eventArgs.netRevenue + pmEvent.size = eventArgs.size + pmEvent.adserverTargeting = eventArgs.adserverTargeting + break + } + case CONSTANTS.EVENTS.BID_WON: { + pmEvent.auctionId = eventArgs.auctionId + pmEvent.adId = eventArgs.adId + pmEvent.adserverTargeting = eventArgs.adserverTargeting + pmEvent.adUnitCode = eventArgs.adUnitCode + pmEvent.bidderCode = eventArgs.bidderCode + pmEvent.height = eventArgs.height + pmEvent.mediaType = eventArgs.mediaType + pmEvent.netRevenue = eventArgs.netRevenue + pmEvent.cpm = eventArgs.cpm + pmEvent.requestTimestamp = eventArgs.requestTimestamp + pmEvent.responseTimestamp = eventArgs.responseTimestamp + pmEvent.size = eventArgs.size + pmEvent.width = eventArgs.width + pmEvent.currency = eventArgs.currency + pmEvent.bidder = eventArgs.bidder + break + } + case CONSTANTS.EVENTS.BIDDER_DONE: { + pmEvent.auctionId = eventArgs.auctionId + pmEvent.auctionStart = eventArgs.auctionStart + pmEvent.bidderCode = eventArgs.bidderCode + pmEvent.bidderRequestId = eventArgs.bidderRequestId + pmEvent.bids = eventArgs.bids && eventArgs.bids.map(trimBid) + pmEvent.doneCbCallCount = eventArgs.doneCbCallCount + pmEvent.start = eventArgs.start + pmEvent.timeout = eventArgs.timeout + pmEvent.tid = eventArgs.tid + pmEvent.src = eventArgs.src + break + } + case CONSTANTS.EVENTS.SET_TARGETING: { + break + } + case CONSTANTS.EVENTS.REQUEST_BIDS: { + break + } + case CONSTANTS.EVENTS.ADD_AD_UNITS: { + break + } + case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + pmEvent.bid = eventArgs.bid + pmEvent.message = eventArgs.message + pmEvent.reason = eventArgs.reason + break + } + default: + return + } + + sendEvent(pmEvent) +} + +function sendEvent(event) { + eventQueue.push(event) + logInfo(`${analyticsName} Event ${event.eventType}:`, event) + + if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + flush() + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: asteriobidAnalytics, + code: 'asteriobid' +}) + +asteriobidAnalytics.getOptions = function () { + return initOptions +} + +asteriobidAnalytics.flush = flush + +export default asteriobidAnalytics diff --git a/modules/asteriobidAnalyticsAdapter.md b/modules/asteriobidAnalyticsAdapter.md new file mode 100644 index 00000000000..524cf6e2721 --- /dev/null +++ b/modules/asteriobidAnalyticsAdapter.md @@ -0,0 +1,41 @@ +# Overview + +Module Name: AsterioBid Analytics Adapter +Module Type: Analytics Adapter +Maintainer: admin@asteriobid.com + +# Description +Analytics adapter for AsterioBid. Contact admin@asteriobid.com for information. + +# Test Parameters + +``` +pbjs.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: '04bcf17b-9733-4675-9f67-d475f881ab78' + } +}); + +``` + +# Advanced Parameters + +``` +pbjs.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: '04bcf17b-9733-4675-9f67-d475f881ab78', + version: 'v1', // configuration version for the comparison + adUnitDict: { // provide names of the ad units for better reporting + adunitid1: 'Top Banner', + adunitid2: 'Bottom Banner' + }, + customParam: { // provide custom parameters values that you want to collect and report + param1: 'value1', + param2: 'value2' + } + } +}); + +``` diff --git a/test/spec/modules/asteriobidAnalyticsAdapter_spec.js b/test/spec/modules/asteriobidAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..9be6c1dedac --- /dev/null +++ b/test/spec/modules/asteriobidAnalyticsAdapter_spec.js @@ -0,0 +1,151 @@ +import asteriobidAnalytics, {storage} from 'modules/asteriobidAnalyticsAdapter.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import * as utils from 'src/utils.js'; +import {expectEvents} from '../../helpers/analytics.js'; + +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('AsterioBid Analytics Adapter', function () { + let bidWonEvent = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': '1ebb82ec35375e', + 'mediaType': 'banner', + 'cpm': 0.5, + 'requestId': '1582271863760569973', + 'creative_id': '96846035', + 'creativeId': '96846035', + 'ttl': 60, + 'currency': 'USD', + 'netRevenue': true, + 'auctionId': '9c7b70b9-b6ab-4439-9e71-b7b382797c18', + 'responseTimestamp': 1537521629657, + 'requestTimestamp': 1537521629331, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 326, + 'size': '300x250', + 'status': 'rendered', + 'eventType': 'bidWon', + 'ad': 'some ad', + 'adUrl': 'ad url' + }; + + describe('AsterioBid Analytic tests', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + asteriobidAnalytics.disableAnalytics(); + events.getEvents.restore(); + }); + + it('support custom endpoint', function () { + let custom_url = 'custom url'; + asteriobidAnalytics.enableAnalytics({ + provider: 'asteriobid', + options: { + url: custom_url, + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + expect(asteriobidAnalytics.getOptions().url).to.equal(custom_url); + }); + + it('bid won event', function() { + let bundleId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; + asteriobidAnalytics.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: bundleId + } + }); + + events.emit(constants.EVENTS.BID_WON, bidWonEvent); + asteriobidAnalytics.flush(); + + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://endpt.asteriobid.com/endpoint'); + expect(server.requests[0].requestBody.substring(0, 2)).to.equal('1:'); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + expect(pmEvents.pageViewId).to.exist; + expect(pmEvents.bundleId).to.equal(bundleId); + expect(pmEvents.ver).to.equal(1); + expect(pmEvents.events.length).to.equal(1); + expect(pmEvents.events[0].eventType).to.equal('bidWon'); + expect(pmEvents.events[0].ad).to.be.undefined; + expect(pmEvents.events[0].adUrl).to.be.undefined; + }); + + it('track event without errors', function () { + sinon.spy(asteriobidAnalytics, 'track'); + + asteriobidAnalytics.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + expectEvents().to.beTrackedBy(asteriobidAnalytics.track); + }); + }); + + describe('build utm tag data', function () { + let getDataFromLocalStorageStub; + this.timeout(4000) + beforeEach(function () { + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + getDataFromLocalStorageStub.withArgs('pm_utm_source').returns('utm_source'); + getDataFromLocalStorageStub.withArgs('pm_utm_medium').returns('utm_medium'); + getDataFromLocalStorageStub.withArgs('pm_utm_campaign').returns('utm_camp'); + getDataFromLocalStorageStub.withArgs('pm_utm_term').returns(''); + getDataFromLocalStorageStub.withArgs('pm_utm_content').returns(''); + }); + afterEach(function () { + getDataFromLocalStorageStub.restore(); + asteriobidAnalytics.disableAnalytics() + }); + it('should build utm data from local storage', function () { + asteriobidAnalytics.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + + expect(pmEvents.utmTags.utm_source).to.equal('utm_source'); + expect(pmEvents.utmTags.utm_medium).to.equal('utm_medium'); + expect(pmEvents.utmTags.utm_campaign).to.equal('utm_camp'); + expect(pmEvents.utmTags.utm_term).to.equal(''); + expect(pmEvents.utmTags.utm_content).to.equal(''); + }); + }); + + describe('build page info', function () { + afterEach(function () { + asteriobidAnalytics.disableAnalytics() + }); + it('should build page info', function () { + asteriobidAnalytics.enableAnalytics({ + provider: 'asteriobid', + options: { + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + } + }); + + const pmEvents = JSON.parse(server.requests[0].requestBody.substring(2)); + + expect(pmEvents.pageInfo.domain).to.equal(window.location.hostname); + expect(pmEvents.pageInfo.referrerDomain).to.equal(utils.parseUrl(document.referrer).hostname); + }); + }); +}); From cb9a616a50838c60d56b9186dab8dc0ff1ae5fd3 Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Fri, 17 Nov 2023 21:39:49 +0800 Subject: [PATCH 203/760] Discovery Bid Adapter : remove duplicate eids (#10743) * feat(getItems): add bid request params * feat(getItems): add bid request imp.ext params * feat(getItems): add plaintext * feat(spec):test buildRequests * feat(bid params): use utils.parseSizesInput to get screen size * feat(getScreenSize): add plaintext * feat(getItems): add bid request params) * feat(getItems): add bid request params) * feat(getItems): add bid request params) * feat(request): add bidId & keywords to imp[].ext * feat(getItems): add pbadslot to unit tests) * feat(request): Remove duplicate eids --------- Co-authored-by: yubei01 Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 3b9f858e15c..816ec3cbe1f 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -386,7 +386,6 @@ function getParam(validBidRequests, bidderRequest) { buyeruid: getUserID(), id: sharedid || pubcid, }, - eids, tmax: timeout, site: { name: domain, From b7744ecc5b537ba652be81904bf6273f8a7bb101 Mon Sep 17 00:00:00 2001 From: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Date: Fri, 17 Nov 2023 13:47:29 +0000 Subject: [PATCH 204/760] Adxcg Bid Adapter: use ortbConverter (#10525) * Update adxcgBidAdapter_spec.js * Update adxcgBidAdapter.js --- modules/adxcgBidAdapter.js | 413 ++----- test/spec/modules/adxcgBidAdapter_spec.js | 1372 ++++++++------------- 2 files changed, 642 insertions(+), 1143 deletions(-) diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 5930f3adb67..ab0ae1cbd48 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,307 +1,65 @@ // jshint esversion: 6, es3: false, node: true -'use strict'; - -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { - _map, - deepAccess, - deepSetValue, - getDNT, + convertTypes, isArray, - isPlainObject, - isStr, - mergeDeep, - parseSizesInput, replaceAuctionPrice, - triggerPixel + triggerPixel, + logMessage, + deepSetValue, + getBidIdParameter } from '../src/utils.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - -const { getConfig } = config; +import { config } from '../src/config.js'; const BIDDER_CODE = 'adxcg'; const SECURE_BID_URL = 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' - }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 - }, - body: { - id: 4, - name: 'data', - type: 2 - }, - cta: { - id: 1, - type: 12, - name: 'data' - } -}; +const DEFAULT_CURRENCY = 'EUR'; +const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'battr', 'deals']; +const DEFAULT_TMAX = 500; +/** + * Adxcg Bid Adapter. + * + */ export const spec = { + code: BIDDER_CODE, - supportedMediaTypes: [ NATIVE, BANNER, VIDEO ], + + aliases: ['mediaopti'], + + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + isBidRequestValid: (bid) => { + logMessage('adxcg - validating isBidRequestValid'); const params = bid.params || {}; const { adzoneid } = params; return !!(adzoneid); }, - buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let app, site; - - const commonFpd = bidderRequest.ortb2 || {}; - let { user } = commonFpd; - - if (typeof getConfig('app') === 'object') { - app = getConfig('app') || {}; - if (commonFpd.app) { - mergeDeep(app, commonFpd.app); - } - } else { - site = getConfig('site') || {}; - if (commonFpd.site) { - mergeDeep(site, commonFpd.site); - } - - if (!site.page) { - site.page = bidderRequest.refererInfo.page; - site.domain = bidderRequest.refererInfo.domain; - } - } - - const device = getConfig('device') || {}; - device.w = device.w || window.innerWidth; - device.h = device.h || window.innerHeight; - device.ua = device.ua || navigator.userAgent; - device.dnt = getDNT() ? 1 : 0; - device.language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - - const tid = bidderRequest.ortb2?.source?.tid; - const test = setOnAny(validBidRequests, 'params.test'); - const currency = getConfig('currency.adServerCurrency'); - const cur = currency && [ currency ]; - const eids = setOnAny(validBidRequests, 'userIdAsEids'); - const schain = setOnAny(validBidRequests, 'schain'); - - const imp = validBidRequests.map((bid, id) => { - const floorInfo = bid.getFloor ? bid.getFloor({ - currency: currency || 'USD' - }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; - const { adzoneid } = bid.params; - - const imp = { - id: id + 1, - tagid: adzoneid, - secure: 1, - bidfloor, - bidfloorcur, - ext: { - } - }; - - const assets = _map(bid.nativeParams, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin, w, h; - let aRatios = bidParams.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - w = sizes[0]; - h = sizes[1]; - } - - asset[props.name] = { - len: bidParams.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } - }).filter(Boolean); - - if (assets.length) { - imp.native = { - request: JSON.stringify({assets: assets}) - }; - } - - const bannerParams = deepAccess(bid, 'mediaTypes.banner'); - - if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - - imp.banner = { - format - }; - } - - const videoParams = deepAccess(bid, 'mediaTypes.video'); - if (videoParams) { - imp.video = videoParams; - } - - return imp; - }); - - const request = { - id: bidderRequest.auctionId, - site, - app, - user, - geo: { utcoffset: new Date().getTimezoneOffset() }, - device, - source: { tid, fd: 1 }, - ext: { - prebid: { - channel: { - name: 'pbjs', - version: '$prebid.version$' - } - } - }, - cur, - imp - }; - - if (test) { - request.is_debug = !!test; - request.test = 1; - } - if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); - } - - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (eids) { - deepSetValue(request, 'user.ext.eids', eids); - } - - if (schain) { - deepSetValue(request, 'source.ext.schain', schain); - } + buildRequests: (bidRequests, bidderRequest) => { + const data = converter.toORTB({ bidRequests, bidderRequest }); return { method: 'POST', url: SECURE_BID_URL, - data: JSON.stringify(request), + data, options: { contentType: 'application/json' }, - bids: validBidRequests + bidderRequest }; }, - interpretResponse: function(serverResponse, { bids }) { - if (!serverResponse.body) { - return; - } - const { seatbid, cur } = serverResponse.body; - - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; - return result; - }, []); - - return bids.map((bid, id) => { - const bidResponse = bidResponses[id]; - if (bidResponse) { - const mediaType = deepAccess(bidResponse, 'ext.crType'); - const result = { - requestId: bid.bidId, - cpm: bidResponse.price, - creativeId: bidResponse.crid, - ttl: bidResponse.ttl ? bidResponse.ttl : 300, - netRevenue: bid.netRevenue === 'net', - currency: cur, - burl: bid.burl || '', - mediaType: mediaType, - width: bidResponse.w, - height: bidResponse.h, - dealId: bidResponse.dealid, - }; - deepSetValue(result, 'meta.mediaType', mediaType); - if (isArray(bidResponse.adomain)) { - deepSetValue(result, 'meta.advertiserDomains', bidResponse.adomain); - } - - if (isPlainObject(bidResponse.ext)) { - if (isStr(bidResponse.ext.mediaType)) { - deepSetValue(result, 'meta.mediaType', mediaType); - } - if (isStr(bidResponse.ext.advertiser_id)) { - deepSetValue(result, 'meta.advertiserId', bidResponse.ext.advertiser_id); - } - if (isStr(bidResponse.ext.advertiser_name)) { - deepSetValue(result, 'meta.advertiserName', bidResponse.ext.advertiser_name); - } - if (isStr(bidResponse.ext.agency_name)) { - deepSetValue(result, 'meta.agencyName', bidResponse.ext.agency_name); - } - } - if (mediaType === BANNER) { - result.ad = bidResponse.adm; - } else if (mediaType === NATIVE) { - result.native = parseNative(bidResponse); - result.width = 0; - result.height = 0; - } else if (mediaType === VIDEO) { - result.vastUrl = bidResponse.nurl; - result.vastXml = bidResponse.adm; - } - - return result; - } - }).filter(Boolean); + interpretResponse: (response, request) => { + if (response.body) { + const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + return bids; + } + return []; }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { const syncs = []; let syncUrl = config.getConfig('adxcg.usersyncUrl'); @@ -323,44 +81,95 @@ export const spec = { } return syncs; }, + onBidWon: (bid) => { // for native requests we put the nurl as an imp tracker, otherwise if the auction takes place on prebid server // the server JS adapter puts the nurl in the adm as a tracking pixel and removes the attribute if (bid.nurl) { triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm)) } + }, + transformBidParams: function (params) { + return convertTypes({ + 'cf': 'string', + 'cp': 'number', + 'ct': 'number', + 'adzoneid': 'string' + }, params); } }; -registerBidder(spec); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: 'EUR' + }, -function parseNative(bid) { - const { assets, link, imptrackers, jstracker } = JSON.parse(bid.adm); - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + // tagid + imp.tagid = bidRequest.params.adzoneid.toString(); + // unknown params + const unknownParams = slotUnknownParams(bidRequest); + if (imp.ext || unknownParams) { + imp.ext = Object.assign({}, imp.ext, unknownParams); + } + // battr + if (bidRequest.params.battr) { + ['banner', 'video', 'audio', 'native'].forEach(k => { + if (imp[k]) { + imp[k].battr = bidRequest.params.battr; + } + }); + } + // deals + if (bidRequest.params.deals && isArray(bidRequest.params.deals)) { + imp.pmp = { + private_auction: 0, + deals: bidRequest.params.deals + }; } - }); - return result; -} -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; + imp.secure = Number(window.location.protocol === 'https:'); + + if (!imp.bidfloor && bidRequest.params.bidFloor) { + imp.bidfloor = bidRequest.params.bidFloor; + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' } - } -} + return imp; + }, + + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + request.tmax = request.tmax || DEFAULT_TMAX; + request.test = config.getConfig('debug') ? 1 : 0; + request.at = 1; + deepSetValue(request, 'ext.prebid.channel.name', 'pbjs'); + deepSetValue(request, 'ext.prebid.channel.version', '$prebid.version$'); + return request; + }, -function flatten(arr) { - return [].concat(...arr); + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.cur = bid.cur || DEFAULT_CURRENCY; + return bidResponse; + }, +}); + +/** + * Unknown params are captured and sent on ext + */ +function slotUnknownParams(slot) { + const ext = {}; + const knownParamsMap = {}; + KNOWN_PARAMS.forEach(value => knownParamsMap[value] = 1); + Object.keys(slot.params).forEach(key => { + if (!knownParamsMap[key]) { + ext[key] = slot.params[key]; + } + }); + return Object.keys(ext).length > 0 ? { prebid: ext } : null; } + +registerBidder(spec); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 65c7584b428..e07e3a6e5d4 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,835 +1,19 @@ // jshint esversion: 6, es3: false, node: true -import {assert} from 'chai'; -import {spec} from 'modules/adxcgBidAdapter.js'; -import {config} from 'src/config.js'; -import {createEidsArray} from 'modules/userId/eids.js'; +import { assert } from 'chai'; +import { spec } from 'modules/adxcgBidAdapter.js'; +import { config } from 'src/config.js'; +import { createEidsArray } from 'modules/userId/eids.js'; +/* eslint dot-notation:0, quote-props:0 */ +import { expect } from 'chai'; + +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import { deepClone } from '../../../src/utils'; + const utils = require('src/utils'); describe('Adxcg adapter', function () { let bids = []; - describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '19910113' - } - }; - - it('should return true when required params found', function () { - assert(spec.isBidRequestValid(bid)); - - bid.params = { - adzoneid: 4332, - }; - assert(spec.isBidRequestValid(bid)); - }); - - it('should return false when required params are missing', function () { - bid.params = {}; - assert.isFalse(spec.isBidRequestValid(bid)); - - bid.params = { - mname: 'some-placement' - }; - assert.isFalse(spec.isBidRequestValid(bid)); - - bid.params = { - inv: 1234 - }; - assert.isFalse(spec.isBidRequestValid(bid)); - }); - }); - - describe('buildRequests', function () { - beforeEach(function () { - config.resetConfig(); - }); - it('should send request with correct structure', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: { - adzoneid: '19910113' - } - }]; - let request = spec.buildRequests(validBidRequests, {refererInfo: {page: 'page', domain: 'localhost'}}); - - assert.equal(request.method, 'POST'); - assert.equal(request.url, 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); - assert.deepEqual(request.options, {contentType: 'application/json'}); - assert.ok(request.data); - }); - - describe('user privacy', function () { - it('should send GDPR Consent data to exchange if gdprApplies', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = { - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, bidderRequest.gdprConsent.consentString); - assert.equal(request.regs.ext.gdpr, bidderRequest.gdprConsent.gdprApplies); - assert.equal(typeof request.regs.ext.gdpr, 'number'); - }); - - it('should send gdpr as number', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = { - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(typeof request.regs.ext.gdpr, 'number'); - assert.equal(request.regs.ext.gdpr, 1); - }); - - it('should send CCPA Consent data to exchange', function () { - let validBidRequests = [{bidId: 'bidId', params: {test: 1}}]; - let bidderRequest = {uspConsent: '1YA-', refererInfo: {referer: 'page'}}; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.ext.us_privacy, '1YA-'); - - bidderRequest = { - uspConsent: '1YA-', - gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.regs.ext.us_privacy, '1YA-'); - assert.equal(request.user.ext.consent, 'consentDataString'); - assert.equal(request.regs.ext.gdpr, 1); - }); - - it('should not send GDPR Consent data to adxcg if gdprApplies is undefined', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let bidderRequest = { - gdprConsent: {gdprApplies: false, consentString: 'consentDataString'}, - refererInfo: {referer: 'page'} - }; - let request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user.ext.consent, 'consentDataString'); - assert.equal(request.regs.ext.gdpr, 0); - - bidderRequest = {gdprConsent: {consentString: 'consentDataString'}, refererInfo: {referer: 'page'}}; - request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); - it('should send default GDPR Consent data to exchange', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.user, undefined); - assert.equal(request.regs, undefined); - }); - }); - - it('should add test and is_debug to request, if test is set in parameters', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {test: 1} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.ok(request.is_debug); - assert.equal(request.test, 1); - }); - - it('should have default request structure', function () { - let keys = 'site,geo,device,source,ext,imp'.split(','); - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - let data = Object.keys(request); - - assert.deepEqual(keys, data); - }); - - it('should set request keys correct values', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'}, - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, { - refererInfo: {referer: 'page'}, - ortb2: {source: {tid: 'tid'}} - }).data); - - assert.equal(request.source.tid, 'tid'); - assert.equal(request.source.fd, 1); - }); - - it('should send info about device', function () { - config.setConfig({ - device: {w: 100, h: 100} - }); - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {page: 'page', domain: 'localhost'}}).data); - - assert.equal(request.device.ua, navigator.userAgent); - assert.equal(request.device.w, 100); - assert.equal(request.device.h, 100); - }); - - it('should send app info', function () { - config.setConfig({ - app: {id: 'appid'}, - }); - const ortb2 = {app: {name: 'appname'}} - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'}, - ortb2 - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}, ortb2}).data); - - assert.equal(request.app.id, 'appid'); - assert.equal(request.app.name, 'appname'); - assert.equal(request.site, undefined); - }); - - it('should send info about the site', function () { - config.setConfig({ - site: { - id: '123123', - publisher: { - domain: 'publisher.domain.com' - } - }, - }); - const ortb2 = { - site: { - publisher: { - id: 4441, - name: 'publisher\'s name' - } - } - }; - - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'}, - ortb2 - }]; - let refererInfo = {page: 'page', domain: 'localhost'}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo, ortb2}).data); - - assert.deepEqual(request.site, { - domain: 'localhost', - id: '123123', - page: refererInfo.page, - publisher: { - domain: 'publisher.domain.com', - id: 4441, - name: 'publisher\'s name' - } - }); - }); - - it('should pass extended ids', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) - }]; - - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - assert.deepEqual(request.user.ext.eids, [ - {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, - {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} - ]); - }); - - it('should send currency if defined', function () { - config.setConfig({currency: {adServerCurrency: 'EUR'}}); - let validBidRequests = [{params: {}}]; - let refererInfo = {referer: 'page'}; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo}).data); - - assert.deepEqual(request.cur, ['EUR']); - }); - - it('should pass supply chain object', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {}, - schain: { - validation: 'strict', - config: { - ver: '1.0' - } - } - }]; - - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - assert.deepEqual(request.source.ext.schain, { - validation: 'strict', - config: { - ver: '1.0' - } - }); - }); - - describe('bids', function () { - it('should add more than one bid to the request', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {siteId: 'siteId'} - }, { - bidId: 'bidId2', - params: {siteId: 'siteId'} - }]; - let request = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data); - - assert.equal(request.imp.length, 2); - }); - it('should add incrementing values of id', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }, { - bidId: 'bidId2', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }, { - bidId: 'bidId3', - params: {adzoneid: '1000'}, - mediaTypes: {video: {}} - }]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - - for (let i = 0; i < 3; i++) { - assert.equal(imps[i].id, i + 1); - } - }); - - it('should add adzoneid', function () { - let validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}, - {bidId: 'bidId2', params: {adzoneid: 1001}, mediaTypes: {video: {}}}, - {bidId: 'bidId3', params: {adzoneid: 1002}, mediaTypes: {video: {}}}]; - let imps = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - for (let i = 0; i < 3; i++) { - assert.equal(imps[i].tagid, validBidRequests[i].params.adzoneid); - } - }); - - describe('price floors', function () { - it('should not add if floors module not configured', function () { - const validBidRequests = [{bidId: 'bidId', params: {adzoneid: 1000}, mediaTypes: {video: {}}}]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, undefined); - }); - - it('should not add if floor price not defined', function () { - const validBidRequests = [getBidWithFloor()]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'USD'); - }); - - it('should request floor price in adserver currency', function () { - config.setConfig({currency: {adServerCurrency: 'DKK'}}); - const validBidRequests = [getBidWithFloor()]; - let imp = getRequestImps(validBidRequests)[0]; - - assert.equal(imp.bidfloor, undefined); - assert.equal(imp.bidfloorcur, 'DKK'); - }); - - it('should add correct floor values', function () { - const expectedFloors = [1, 1.3, 0.5]; - const validBidRequests = expectedFloors.map(getBidWithFloor); - let imps = getRequestImps(validBidRequests); - - expectedFloors.forEach((floor, index) => { - assert.equal(imps[index].bidfloor, floor); - assert.equal(imps[index].bidfloorcur, 'USD'); - }); - }); - - function getBidWithFloor(floor) { - return { - params: {adzoneid: 1}, - mediaTypes: {video: {}}, - getFloor: ({currency}) => { - return { - currency: currency, - floor - }; - } - }; - } - }); - - describe('multiple media types', function () { - it('should use all configured media types for bidding', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] - }, - video: {} - } - }, { - bidId: 'bidId1', - params: {adzoneid: 1000}, - mediaTypes: { - video: {}, - native: {} - } - }, { - bidId: 'bidId2', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140} - }, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] - }, - native: {}, - video: {} - } - }]; - let [first, second, third] = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - - assert.ok(first.banner); - assert.ok(first.video); - assert.equal(first.native, undefined); - - assert.ok(second.video); - assert.equal(second.banner, undefined); - assert.equal(second.native, undefined); - - assert.ok(third.native); - assert.ok(third.video); - assert.ok(third.banner); - }); - }); - - describe('banner', function () { - it('should convert sizes to openrtb format', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - banner: { - sizes: [[100, 100], [200, 300]] - } - } - }]; - let {banner} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; - assert.deepEqual(banner, { - format: [{w: 100, h: 100}, {w: 200, h: 300}] - }); - }); - }); - - describe('video', function () { - it('should pass video mediatype config', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'] - } - } - }]; - let {video} = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0]; - assert.deepEqual(video, { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'] - }); - }); - }); - - describe('native', function () { - describe('assets', function () { - it('should set correct asset id', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - }]; - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - - assert.equal(assets[0].id, 0); - assert.equal(assets[1].id, 3); - assert.equal(assets[2].id, 4); - }); - it('should add required key if it is necessary', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140}, - sponsoredBy: {required: true, len: 140} - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - - assert.equal(assets[0].required, 1); - assert.ok(!assets[1].required); - assert.ok(!assets[2].required); - assert.equal(assets[3].required, 1); - }); - - it('should map img and data assets', function () { - let validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: true, sizes: [150, 50]}, - icon: {required: false, sizes: [50, 50]}, - body: {required: false, len: 140}, - sponsoredBy: {required: true}, - cta: {required: false}, - clickUrl: {required: false} - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].title); - assert.equal(assets[0].title.len, 140); - assert.deepEqual(assets[1].img, {type: 3, w: 150, h: 50}); - assert.deepEqual(assets[2].img, {type: 1, w: 50, h: 50}); - assert.deepEqual(assets[3].data, {type: 2, len: 140}); - assert.deepEqual(assets[4].data, {type: 1}); - assert.deepEqual(assets[5].data, {type: 12}); - assert.ok(!assets[6]); - }); - - describe('icon/image sizing', function () { - it('should flatten sizes and utilise first pair', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - sizes: [[200, 300], [100, 200]] - }, - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.w, 200); - assert.equal(assets[0].img.h, 300); - }); - }); - - it('should utilise aspect_ratios', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [{ - min_width: 100, - ratio_height: 3, - ratio_width: 1 - }] - }, - icon: { - aspect_ratios: [{ - min_width: 10, - ratio_height: 5, - ratio_width: 2 - }] - } - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 100); - assert.equal(assets[0].img.hmin, 300); - - assert.ok(assets[1].img); - assert.equal(assets[1].img.wmin, 10); - assert.equal(assets[1].img.hmin, 25); - }); - - it('should not throw error if aspect_ratios config is not defined', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [] - }, - icon: { - aspect_ratios: [] - } - } - }]; - - assert.doesNotThrow(() => spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}})); - }); - }); - - it('should expect any dimensions if min_width not passed', function () { - const validBidRequests = [{ - bidId: 'bidId', - params: {adzoneid: 1000}, - nativeParams: { - image: { - aspect_ratios: [{ - ratio_height: 3, - ratio_width: 1 - }] - } - } - }]; - - let nativeRequest = JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp[0].native.request; - let assets = JSON.parse(nativeRequest).assets; - assert.ok(assets[0].img); - assert.equal(assets[0].img.wmin, 0); - assert.equal(assets[0].img.hmin, 0); - assert.ok(!assets[1]); - }); - }); - }); - - function getRequestImps(validBidRequests) { - return JSON.parse(spec.buildRequests(validBidRequests, {refererInfo: {referer: 'page'}}).data).imp; - } - }); - - describe('interpretResponse', function () { - it('should return if no body in response', function () { - let serverResponse = {}; - let bidRequest = {}; - - assert.ok(!spec.interpretResponse(serverResponse, bidRequest)); - }); - it('should return more than one bids', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{ - impid: '1', - native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, title: {text: 'Asset title text'}}]} - }] - }, { - bid: [{ - impid: '2', - native: {ver: '1.1', link: {url: 'link'}, assets: [{id: 1, data: {value: 'Asset title text'}}]} - }] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - }, - { - bidId: 'bidId2', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - } - ] - }; - - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(spec.interpretResponse(serverResponse, bidRequest).length, 2); - }); - - it('should set correct values to bid', function () { - let nativeExample1 = { - assets: [], - link: {url: 'link'}, - imptrackers: ['imptrackers url1', 'imptrackers url2'] - } - - let serverResponse = { - body: { - id: null, - bidid: null, - seatbid: [{ - bid: [ - { - impid: '1', - price: 93.1231, - crid: '12312312', - adm: JSON.stringify(nativeExample1), - dealid: 'deal-id', - adomain: ['demo.com'], - ext: { - crType: 'native', - advertiser_id: 'adv1', - advertiser_name: 'advname', - agency_name: 'agname', - mediaType: 'native' - } - } - ] - }], - cur: 'EUR' - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000}, - nativeParams: { - title: {required: true, len: 140}, - image: {required: false, wmin: 836, hmin: 627, w: 325, h: 300, mimes: ['image/jpg', 'image/gif']}, - body: {len: 140} - } - } - ] - }; - - const bids = spec.interpretResponse(serverResponse, bidRequest); - const bid = serverResponse.body.seatbid[0].bid[0]; - assert.deepEqual(bids[0].requestId, bidRequest.bids[0].bidId); - assert.deepEqual(bids[0].cpm, bid.price); - assert.deepEqual(bids[0].creativeId, bid.crid); - assert.deepEqual(bids[0].ttl, 300); - assert.deepEqual(bids[0].netRevenue, false); - assert.deepEqual(bids[0].currency, serverResponse.body.cur); - assert.deepEqual(bids[0].mediaType, 'native'); - assert.deepEqual(bids[0].meta.mediaType, 'native'); - assert.deepEqual(bids[0].meta.advertiserDomains, ['demo.com']); - - assert.deepEqual(bids[0].meta.advertiserName, 'advname'); - assert.deepEqual(bids[0].meta.agencyName, 'agname'); - - assert.deepEqual(bids[0].dealId, 'deal-id'); - }); - - it('should return empty when there is no bids in response', function () { - const serverResponse = { - body: { - id: null, - bidid: null, - seatbid: [{bid: []}], - cur: 'EUR' - } - }; - let bidRequest = { - data: {}, - bids: [{bidId: 'bidId1'}] - }; - const result = spec.interpretResponse(serverResponse, bidRequest)[0]; - assert.ok(!result); - }); - - describe('banner', function () { - it('should set ad content on response', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{impid: '1', adm: '', ext: {crType: 'banner'}}] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000} - } - ] - }; - - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(bids.length, 1); - assert.equal(bids[0].ad, ''); - assert.equal(bids[0].mediaType, 'banner'); - assert.equal(bids[0].meta.mediaType, 'banner'); - }); - }); - - describe('video', function () { - it('should set vastXml on response', function () { - let serverResponse = { - body: { - seatbid: [{ - bid: [{impid: '1', adm: '', ext: {crType: 'video'}}] - }] - } - }; - let bidRequest = { - data: {}, - bids: [ - { - bidId: 'bidId1', - params: {adzoneid: 1000} - } - ] - }; - - bids = spec.interpretResponse(serverResponse, bidRequest); - assert.equal(bids.length, 1); - assert.equal(bids[0].vastXml, ''); - assert.equal(bids[0].mediaType, 'video'); - assert.equal(bids[0].meta.mediaType, 'video'); - }); - }); - }); - describe('getUserSyncs', function () { const usersyncUrl = 'https://usersync-url.com'; beforeEach(() => { @@ -846,55 +30,55 @@ describe('Adxcg adapter', function () { }) it('should return user sync if pixel enabled with adxcg config', function () { - const ret = spec.getUserSyncs({pixelEnabled: true}) - expect(ret).to.deep.equal([{type: 'image', url: usersyncUrl}]) + const ret = spec.getUserSyncs({ pixelEnabled: true }) + expect(ret).to.deep.equal([{ type: 'image', url: usersyncUrl }]) }) it('should not return user sync if pixel disabled', function () { - const ret = spec.getUserSyncs({pixelEnabled: false}) + const ret = spec.getUserSyncs({ pixelEnabled: false }) expect(ret).to.be.an('array').that.is.empty }) it('should not return user sync if url is not set', function () { config.resetConfig() - const ret = spec.getUserSyncs({pixelEnabled: true}) + const ret = spec.getUserSyncs({ pixelEnabled: true }) expect(ret).to.be.an('array').that.is.empty }) - it('should pass GDPR consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + it('should pass GDPR consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: false, consentString: 'foo' }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=0&gdpr_consent=foo` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: undefined }, undefined)).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=` }]); }); - it('should pass US consent', function() { + it('should pass US consent', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=1NYN` }]); }); - it('should pass GDPR and US consent', function() { - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + it('should pass GDPR and US consent', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { gdprApplies: true, consentString: 'foo' }, '1NYN')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=foo&us_privacy=1NYN` }]); }); }); - describe('onBidWon', function() { - beforeEach(function() { + describe('onBidWon', function () { + beforeEach(function () { sinon.stub(utils, 'triggerPixel'); }); - afterEach(function() { + afterEach(function () { utils.triggerPixel.restore(); }); - it('Should trigger pixel if bid nurl', function() { + it('Should trigger pixel if bid nurl', function () { const bid = { nurl: 'http://example.com/win/${AUCTION_PRICE}', cpm: 2.1, @@ -904,4 +88,510 @@ describe('Adxcg adapter', function () { expect(utils.triggerPixel.callCount).to.equal(1) }) }) + + it('should return just to have at least 1 karma test ok', function () { + assert(true); + }); +}); + +describe('adxcg v8 oRtbConverter Adapter Tests', function () { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[728, 90], [160, 600]] + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '300x250', + adzoneid: '77' + } + }, { + placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'bid23456', + params: { + cp: 'p10000', + ct: 't20000', + cf: '728x90', + adzoneid: '77' + } + }]; + const nativeOrtbRequest = { + assets: [{ + id: 1, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + title: { + len: 80 + } + }, + { + id: 3, + required: 0, + data: { + type: 1 + } + }] + }; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + mediaTypes: { + native: { + sendTargetingKeys: false, + ortb: nativeOrtbRequest + } + }, + nativeOrtbRequest, + params: { + cp: 'p10000', + ct: 't10000', + adzoneid: '77' + } + }]; + const videoSlotConfig = [{ + placementCode: '/DfpAccount1/slotVideo', + bidId: 'bid12345', + mediaTypes: { + video: { + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + params: { + cp: 'p10000', + ct: 't10000', + adzoneid: '77' + } + }]; + const additionalParamsConfig = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + adzoneid: '77', + extra_key1: 'extra_val1', + extra_key2: 12345, + extra_key3: { + key1: 'val1', + key2: 23456, + }, + extra_key4: [1, 2, 3] + } + }]; + + const schainParamsSlotConfig = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + adzoneid: '77', + bcat: ['IAB-1', 'IAB-20'], + battr: [1, 2, 3], + bidfloor: 1.5, + badv: ['cocacola.com', 'lays.com'] + }, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + } + }; + + it('Verify build request', function () { + const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + // site object + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.publisher).to.not.equal(null); + // expect(ortbRequest.site.publisher.id).to.equal('p10000'); + expect(ortbRequest.site.page).to.equal('https://publisher.com/home'); + expect(ortbRequest.imp).to.have.lengthOf(2); + // device object + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + // slot 1 + // expect(ortbRequest.imp[0].tagid).to.equal('t10000'); + expect(ortbRequest.imp[0].banner).to.not.equal(null); + expect(ortbRequest.imp[0].banner.format).to.deep.eq([{ 'w': 728, 'h': 90 }, { 'w': 160, 'h': 600 }]); + // slot 2 + // expect(ortbRequest.imp[1].tagid).to.equal('t20000'); + expect(ortbRequest.imp[1].banner).to.not.equal(null); + expect(ortbRequest.imp[1].banner.format).to.deep.eq([{ 'w': 728, 'h': 90 }]); + }); + + it('Verify parse response', function () { + const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + crid: 'Creative#123', + mtype: 1, + w: 300, + h: 250, + exp: 20, + adomain: ['advertiser.com'] + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creative_id).to.equal('Creative#123'); + expect(bid.creativeId).to.equal('Creative#123'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('EUR'); + expect(bid.ttl).to.equal(20); + expect(bid.meta).to.not.be.null; + expect(bid.meta.advertiserDomains).to.eql(['advertiser.com']); + }); + + it('Verify full passback', function () { + const request = spec.buildRequests(slotConfigs, bidderRequest); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); + }); + + if (FEATURES.NATIVE) { + it('Verify Native request', function () { + const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + // native impression + expect(ortbRequest.imp[0].tagid).to.equal('77'); + expect(ortbRequest.imp[0].banner).to.be.undefined; + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(3); + // image asset + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.be.undefined; + expect(nativeRequest.assets[0].img).to.not.equal(null); + expect(nativeRequest.assets[0].img.w).to.equal(150); + expect(nativeRequest.assets[0].img.h).to.equal(50); + expect(nativeRequest.assets[0].img.type).to.equal(3); + // title asset + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[1].required).to.equal(1); + expect(nativeRequest.assets[1].title).to.not.equal(null); + expect(nativeRequest.assets[1].title.len).to.equal(80); + // data asset + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[2].title).to.be.undefined; + expect(nativeRequest.assets[2].data).to.not.equal(null); + expect(nativeRequest.assets[2].data.type).to.equal(1); + }); + + it('Verify Native response', function () { + const request = spec.buildRequests(nativeSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + const nativeResponse = { + assets: [ + { id: 1, img: { type: 3, url: 'https://images.cdn.brand.com/123' } }, + { id: 2, title: { text: 'Ad Title' } }, + { id: 3, data: { type: 1, value: 'Sponsored By: Brand' } } + ], + link: { url: 'https://brand.clickme.com/' }, + imptrackers: ['https://imp1.trackme.com/', 'https://imp1.contextweb.com/'] + + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: JSON.stringify(nativeResponse), + mtype: 4 + }] + }] + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.requestId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + expect(bid['native']).to.not.be.null; + expect(bid['native'].ortb).to.not.be.null; + const nativeBid = bid['native'].ortb; + expect(nativeBid.assets).to.have.lengthOf(3); + expect(nativeBid.assets[0].id).to.equal(1); + expect(nativeBid.assets[0].img).to.not.be.null; + expect(nativeBid.assets[0].img.type).to.equal(3); + expect(nativeBid.assets[0].img.url).to.equal('https://images.cdn.brand.com/123'); + expect(nativeBid.assets[1].id).to.equal(2); + expect(nativeBid.assets[1].title).to.not.be.null; + expect(nativeBid.assets[1].title.text).to.equal('Ad Title'); + expect(nativeBid.assets[2].id).to.equal(3); + expect(nativeBid.assets[2].data).to.not.be.null; + expect(nativeBid.assets[2].data.type).to.equal(1); + expect(nativeBid.assets[2].data.value).to.equal('Sponsored By: Brand'); + expect(nativeBid.link).to.not.be.null; + expect(nativeBid.link.url).to.equal('https://brand.clickme.com/'); + expect(nativeBid.imptrackers).to.have.lengthOf(2); + expect(nativeBid.imptrackers[0]).to.equal('https://imp1.trackme.com/'); + expect(nativeBid.imptrackers[1]).to.equal('https://imp1.contextweb.com/'); + }); + } + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('adxcg'); + }); + + it('Verifies bidder aliases', function () { + expect(spec.aliases).to.have.lengthOf(1); + expect(spec.aliases[0]).to.equal('mediaopti'); + }); + + it('Verifies supported media types', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(3); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('native'); + expect(spec.supportedMediaTypes[2]).to.equal('video'); + }); + + if (FEATURES.VIDEO) { + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.equal('https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video).to.not.be.null; + expect(ortbRequest.imp[0].native).to.be.undefined; + expect(ortbRequest.imp[0].banner).to.be.undefined; + expect(ortbRequest.imp[0].video.w).to.equal(400); + expect(ortbRequest.imp[0].video.h).to.equal(300); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.maxduration).to.equal(10); + expect(ortbRequest.imp[0].video.startdelay).to.equal(0); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(200); + expect(ortbRequest.imp[0].video.protocols).to.eql([1, 2, 4]); + }); + } + + it('Verify extra parameters', function () { + let request = spec.buildRequests(additionalParamsConfig, syncAddFPDToBidderRequest(bidderRequest)); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].ext).to.not.equal(null); + expect(ortbRequest.imp[0].ext.prebid).to.not.equal(null); + expect(ortbRequest.imp[0].ext.prebid).to.not.be.null; + expect(ortbRequest.imp[0].ext.prebid.extra_key1).to.equal('extra_val1'); + expect(ortbRequest.imp[0].ext.prebid.extra_key2).to.equal(12345); + expect(ortbRequest.imp[0].ext.prebid.extra_key3).to.not.be.null; + expect(ortbRequest.imp[0].ext.prebid.extra_key3.key1).to.equal('val1'); + expect(ortbRequest.imp[0].ext.prebid.extra_key3.key2).to.equal(23456); + expect(ortbRequest.imp[0].ext.prebid.extra_key4).to.eql([1, 2, 3]); + expect(Object.keys(ortbRequest.imp[0].ext.prebid)).to.eql(['adzoneid', 'extra_key1', 'extra_key2', 'extra_key3', 'extra_key4']); + // attempting with a configuration with no unknown params. + request = spec.buildRequests(videoSlotConfig, bidderRequest); + ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + // expect(ortbRequest.imp[0].ext).to.be.undefined; + }); + + it('Verify user level first party data', function () { + const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + }, + ortb2: { + user: { + yob: 1985, + gender: 'm', + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + }; + let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.user).to.not.equal(null); + }); + + it('Verify site level first party data', function () { + const bidderRequest = { + ortb2: { + site: { + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com', + publisher: { + domain: 'pub.com' + } + } + } + }; + let request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(bidderRequest)); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site).to.deep.equal({ + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com', + publisher: { + // id: 'p10000', + domain: 'pub.com' + } + }); + }); + + it('Verify impression/slot level first party data', function () { + const bidderRequests = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + adzoneid: '77', + extra_key1: 'extra_val1', + extra_key2: 12345 + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + } + } + }]; + let request = spec.buildRequests(bidderRequests, bidderRequest); + let ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.imp).to.not.equal(null); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].ext).to.not.equal(null); + expect(ortbRequest.imp[0].ext).to.deep.equal({ + prebid: { + adzoneid: '77', + extra_key1: 'extra_val1', + extra_key2: 12345 + }, + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123' + } + }); + }); + + it('Verify bid request timeouts', function () { + const mkRequest = (bidderRequest) => spec.buildRequests(slotConfigs, bidderRequest).data; + // assert default is used when no bidderRequest.timeout value is available + expect(mkRequest(bidderRequest).tmax).to.equal(500) + + // assert bidderRequest value is used when available + expect(mkRequest(Object.assign({}, { timeout: 6000 }, bidderRequest)).tmax).to.equal(6000) + }); }); From deff7e097b9db67c5505556e98abdd4ac6417ed5 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 20 Nov 2023 13:31:43 -0800 Subject: [PATCH 205/760] Core: unify rendering paths; codify (and test) cross-domain render example (#9647) * Refactor rendering to go through a single code path * Build creative together with js * Fix pubUrl / pubDomain * Update dev tasks for creative building * Cross-domain render * Clean up empty fn * Autogenerated cross-domain creative example * Update text * Refactor creative * fix lint * Add test case for custom renderer * Always resize renderAd iframe --- gulpfile.js | 27 ++- .../gpt/x-domain/creative.html | 108 +-------- libraries/creativeRender/constants.js | 10 + libraries/creativeRender/crossDomain.js | 57 +++++ libraries/creativeRender/direct.js | 62 +++++ libraries/creativeRender/writer.js | 34 +++ package-lock.json | 211 ++++++++++++++++++ package.json | 1 + src/adRendering.js | 70 +++++- src/auction.js | 2 + src/prebid.js | 130 +---------- src/secureCreatives.js | 94 ++------ src/utils.js | 10 +- .../spec/creative/crossDomainCreative_spec.js | 182 +++++++++++++++ test/spec/unit/pbjs_api_spec.js | 28 +-- test/spec/unit/secureCreatives_spec.js | 73 +++--- webpack.creative.js | 19 ++ 17 files changed, 759 insertions(+), 359 deletions(-) create mode 100644 libraries/creativeRender/constants.js create mode 100644 libraries/creativeRender/crossDomain.js create mode 100644 libraries/creativeRender/direct.js create mode 100644 libraries/creativeRender/writer.js create mode 100644 test/spec/creative/crossDomainCreative_spec.js create mode 100644 webpack.creative.js diff --git a/gulpfile.js b/gulpfile.js index 09de874e389..5e16af8b0c1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -158,6 +158,20 @@ function makeWebpackPkg(extraConfig = {}) { } } +function buildCreative() { + return gulp.src(['**/*']) + .pipe(webpackStream(require('./webpack.creative.js'))) + .pipe(gulp.dest('build/creative')) +} + +function updateCreativeExample(cb) { + const CREATIVE_EXAMPLE = 'integrationExamples/gpt/x-domain/creative.html'; + const root = require('node-html-parser').parse(fs.readFileSync(CREATIVE_EXAMPLE)); + root.querySelectorAll('script')[0].textContent = fs.readFileSync('build/creative/creative.js') + fs.writeFileSync(CREATIVE_EXAMPLE, root.toString()) + cb(); +} + function getModulesListToAddInBanner(modules) { if (!modules || modules.length === helpers.getModuleNames().length) { return 'All available modules for this version.' @@ -405,6 +419,7 @@ function watchTaskMaker(options = {}) { return function watch(done) { var mainWatcher = gulp.watch([ 'src/**/*.js', + 'libraries/**/*.js', 'modules/**/*.js', ].concat(options.alsoWatch)); @@ -415,8 +430,8 @@ function watchTaskMaker(options = {}) { } } -const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))}); -const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')}); +const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test, buildCreative))}); +const watchFast = watchTaskMaker({livereload: false, task: () => gulp.parallel('build-bundle-dev', buildCreative)}); // support tasks gulp.task(lint); @@ -447,21 +462,23 @@ gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ } }), gulpBundle.bind(null, false))); +gulp.task('build-creative', gulp.series(buildCreative, updateCreativeExample)); + // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); gulp.task('test-all-features-disabled', testTaskMaker({disableFeatures: require('./features.json'), oneBrowser: 'chrome', watch: false})); -gulp.task('test', gulp.series(clean, lint, gulp.series('test-all-features-disabled', 'test-only'))); +gulp.task('test', gulp.series(clean, lint, gulp.parallel('build-creative', gulp.series('test-all-features-disabled', 'test-only')))); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); gulp.task('coveralls', gulp.series('test-coverage', coveralls)); -gulp.task('build', gulp.series(clean, 'build-bundle-prod')); +gulp.task('build', gulp.series(clean, 'build-bundle-prod', 'build-creative')); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); -gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', buildCreative, watchFast))); gulp.task('serve-prod', gulp.series(clean, gulp.parallel('build-bundle-prod', startLocalServer))); gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer))); diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 2216d0ed6ae..f1c0c647e72 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -1,105 +1,13 @@ - -} - - -function requestAdFromPrebid() { - const message = JSON.stringify({ - message: 'Prebid Request', - adId - }); - const channel = new MessageChannel(); - channel.port1.onmessage = renderAd; - window.parent.postMessage(message, publisherDomain, [channel.port2]); -} - -function listenAdFromPrebid() { - window.addEventListener('message', receiveMessage, false); -} - -listenAdFromPrebid(); -requestAdFromPrebid(); + diff --git a/libraries/creativeRender/constants.js b/libraries/creativeRender/constants.js new file mode 100644 index 00000000000..7b67f8ed5cd --- /dev/null +++ b/libraries/creativeRender/constants.js @@ -0,0 +1,10 @@ +import events from '../../src/constants.json'; + +export const PREBID_NATIVE = 'Prebid Native'; +export const PREBID_REQUEST = 'Prebid Request'; +export const PREBID_RESPONSE = 'Prebid Response'; +export const PREBID_EVENT = 'Prebid Event'; +export const AD_RENDER_SUCCEEDED = events.EVENTS.AD_RENDER_SUCCEEDED; +export const AD_RENDER_FAILED = events.EVENTS.AD_RENDER_FAILED; +export const NO_AD = events.AD_RENDER_FAILED_REASON.NO_AD; +export const EXCEPTION = events.AD_RENDER_FAILED_REASON.EXCEPTION; diff --git a/libraries/creativeRender/crossDomain.js b/libraries/creativeRender/crossDomain.js new file mode 100644 index 00000000000..ffa8b468f12 --- /dev/null +++ b/libraries/creativeRender/crossDomain.js @@ -0,0 +1,57 @@ +import {mkFrame, writeAd} from './writer.js'; +import { + AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, + PREBID_EVENT, + PREBID_RESPONSE, + PREBID_REQUEST, + EXCEPTION +} from './constants.js'; + +export function renderer(win = window) { + return function ({adId, pubUrl, clickUrl}) { + const pubDomain = (function() { + const a = win.document.createElement('a'); + a.href = pubUrl; + return a.protocol + '//' + a.host; + })(); + function sendMessage(type, payload, transfer) { + win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, transfer); + } + function cb(err) { + sendMessage(PREBID_EVENT, { + event: err == null ? AD_RENDER_SUCCEEDED : AD_RENDER_FAILED, + info: err + }); + } + function onMessage(ev) { + let data = {}; + try { + data = JSON.parse(ev[ev.message ? 'message' : 'data']); + } catch (e) { + return; + } + if (data.message === PREBID_RESPONSE && data.adId === adId) { + try { + let doc = win.document + if (data.ad) { + doc = mkFrame(doc, {width: data.width, height: data.height}).contentDocument; + doc.open(); + } + writeAd(data, cb, doc); + } catch (e) { + // eslint-disable-next-line standard/no-callback-literal + cb({ reason: EXCEPTION, message: e.message }) + } + } + } + + const channel = new MessageChannel(); + channel.port1.onmessage = onMessage; + sendMessage(PREBID_REQUEST, { + options: {clickUrl} + }, [channel.port2]); + win.addEventListener('message', onMessage, false); + } +} +window.renderAd = renderer(); diff --git a/libraries/creativeRender/direct.js b/libraries/creativeRender/direct.js new file mode 100644 index 00000000000..0b4fb2f0a75 --- /dev/null +++ b/libraries/creativeRender/direct.js @@ -0,0 +1,62 @@ +import {emitAdRenderFail, emitAdRenderSucceeded, handleRender} from '../../src/adRendering.js'; +import {writeAd} from './writer.js'; +import {auctionManager} from '../../src/auctionManager.js'; +import CONSTANTS from '../../src/constants.json'; +import {inIframe, insertElement} from '../../src/utils.js'; +import {getGlobal} from '../../src/prebidGlobal.js'; +import {EXCEPTION} from './constants.js'; + +export function renderAdDirect(doc, adId, options) { + let bid; + function cb(err) { + if (err != null) { + emitAdRenderFail(Object.assign({id: adId, bid}, err)); + } else { + emitAdRenderSucceeded({doc, bid, adId}) + } + } + function renderFn(adData) { + writeAd(adData, cb, doc); + if (doc.defaultView && doc.defaultView.frameElement) { + doc.defaultView.frameElement.width = adData.width; + doc.defaultView.frameElement.height = adData.height; + } + // TODO: this is almost certainly the wrong way to do this + const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); + insertElement(creativeComment, doc, 'html'); + } + try { + if (!adId || !doc) { + // eslint-disable-next-line standard/no-callback-literal + cb({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, + message: `missing ${adId ? 'doc' : 'adId'}` + }); + } else { + bid = auctionManager.findBidByAdId(adId); + + if (FEATURES.VIDEO) { + // TODO: could the video module implement this as a custom renderer, rather than a special case in here? + const adUnit = bid && auctionManager.index.getAdUnit(bid); + const videoModule = getGlobal().videoModule; + if (adUnit?.video && videoModule) { + videoModule.renderBid(adUnit.video.divId, bid); + return; + } + } + + if ((doc === document && !inIframe())) { + // eslint-disable-next-line standard/no-callback-literal + cb({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, + message: `renderAd was prevented from writing to the main document.` + }) + } else { + handleRender(renderFn, {adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid}); + } + } + } catch (e) { + // eslint-disable-next-line standard/no-callback-literal + cb({reason: EXCEPTION, message: e.message}) + } +} diff --git a/libraries/creativeRender/writer.js b/libraries/creativeRender/writer.js new file mode 100644 index 00000000000..80bb0592a1f --- /dev/null +++ b/libraries/creativeRender/writer.js @@ -0,0 +1,34 @@ +import {NO_AD} from './constants.js'; + +const IFRAME_ATTRS = { + frameBorder: 0, + scrolling: 'no', + marginHeight: 0, + marginWidth: 0, + topMargin: 0, + leftMargin: 0, + allowTransparency: 'true', +}; + +export function mkFrame(doc, attrs) { + const frame = doc.createElement('iframe'); + attrs = Object.assign({}, attrs, IFRAME_ATTRS); + Object.entries(attrs).forEach(([k, v]) => frame.setAttribute(k, v)); + doc.body.appendChild(frame); + return frame; +} + +export function writeAd({ad, adUrl, width, height}, cb, doc = document) { + if (!ad && !adUrl) { + // eslint-disable-next-line standard/no-callback-literal + cb({reason: NO_AD, message: 'Missing ad markup or URL'}); + } else { + if (adUrl && !ad) { + mkFrame(doc, {width, height, src: adUrl}) + } else { + doc.write(ad); + doc.close(); + } + cb(); + } +} diff --git a/package-lock.json b/package-lock.json index caee07c67f6..4650db8f2ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -90,6 +90,7 @@ "lodash": "^4.17.21", "mocha": "^10.0.0", "morgan": "^1.10.0", + "node-html-parser": "^6.1.5", "opn": "^5.4.0", "resolve-from": "^5.0.0", "sinon": "^4.1.3", @@ -6491,6 +6492,12 @@ "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", "dev": true }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7855,6 +7862,22 @@ "source-map-resolve": "^0.6.0" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css-shorthand-properties": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", @@ -7867,6 +7890,18 @@ "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", "dev": true }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/css/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8693,12 +8728,67 @@ "void-elements": "^2.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", "dev": true }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -8959,6 +9049,18 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -18708,6 +18810,16 @@ } } }, + "node_modules/node-html-parser": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.6.tgz", + "integrity": "sha512-C/MGDQ2NjdjzUq41bW9kW00MPZecAe/oo89vZEFLDfWoQVDk/DdML1yuxVVKLDMFIFax2VTq6Vpfzyn7z5yYgQ==", + "dev": true, + "dependencies": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, "node_modules/node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -18809,6 +18921,18 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -30247,6 +30371,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -31305,6 +31435,19 @@ } } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, "css-shorthand-properties": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", @@ -31317,6 +31460,12 @@ "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", "dev": true }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -31928,12 +32077,49 @@ "void-elements": "^2.0.0" } }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, "dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", "dev": true }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, "dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -32164,6 +32350,12 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true + }, "errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -39738,6 +39930,16 @@ "whatwg-url": "^5.0.0" } }, + "node-html-parser": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.6.tgz", + "integrity": "sha512-C/MGDQ2NjdjzUq41bW9kW00MPZecAe/oo89vZEFLDfWoQVDk/DdML1yuxVVKLDMFIFax2VTq6Vpfzyn7z5yYgQ==", + "dev": true, + "requires": { + "css-select": "^5.1.0", + "he": "1.2.0" + } + }, "node-releases": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", @@ -39813,6 +40015,15 @@ } } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", diff --git a/package.json b/package.json index 7076d012285..4ea9131920d 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "lodash": "^4.17.21", "mocha": "^10.0.0", "morgan": "^1.10.0", + "node-html-parser": "^6.1.5", "opn": "^5.4.0", "resolve-from": "^5.0.0", "sinon": "^4.1.3", diff --git a/src/adRendering.js b/src/adRendering.js index 0a847d7cc25..c3e518eae4d 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -1,8 +1,12 @@ -import {logError} from './utils.js'; +import {deepAccess, logError, logWarn, replaceMacros} from './utils.js'; import * as events from './events.js'; -import CONSTANTS from './constants.json'; +import constants from './constants.json'; +import {config} from './config.js'; +import {executeRenderer, isRendererRequired} from './Renderer.js'; +import {VIDEO} from './mediaTypes.js'; +import {auctionManager} from './auctionManager.js'; -const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED} = CONSTANTS.EVENTS; +const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = constants.EVENTS; /** * Emit the AD_RENDER_FAILED event. @@ -17,7 +21,7 @@ export function emitAdRenderFail({ reason, message, bid, id }) { if (bid) data.bid = bid; if (id) data.adId = id; - logError(message); + logError(`Error rendering ad (id: ${id}): ${message}`); events.emit(AD_RENDER_FAILED, data); } @@ -36,3 +40,61 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { events.emit(AD_RENDER_SUCCEEDED, data); } + +export function handleRender(renderFn, {adId, options, bidResponse}) { + if (bidResponse == null) { + emitAdRenderFail({ + reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + message: `Cannot find ad '${adId}'`, + id: adId + }); + return; + } + if (bidResponse.status === constants.BID_STATUS.RENDERED) { + logWarn(`Ad id ${adId} has been rendered before`); + events.emit(STALE_RENDER, bidResponse); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + return; + } + } + try { + const {adId, ad, adUrl, width, height, renderer, cpm, originalCpm, mediaType} = bidResponse; + // rendering for outstream safeframe + if (isRendererRequired(renderer)) { + executeRenderer(renderer, bidResponse); + } else if (adId) { + if (mediaType === VIDEO) { + emitAdRenderFail({ + reason: constants.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, + message: 'Cannot render video ad', + bid: bidResponse, + id: adId + }); + return; + } + const repl = { + AUCTION_PRICE: originalCpm || cpm, + CLICKTHROUGH: options?.clickUrl || '' + }; + renderFn({ + ad: replaceMacros(ad, repl), + adUrl: replaceMacros(adUrl, repl), + adId, + width, + height + }); + } + } catch (e) { + emitAdRenderFail({ + reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION, + message: e.message, + id: adId, + bid: bidResponse + }); + return; + } + // save winning bids + auctionManager.addWinningBid(bidResponse); + + events.emit(BID_WON, bidResponse); +} diff --git a/src/auction.js b/src/auction.js index efce69ea9f8..0d256bc3cd7 100644 --- a/src/auction.js +++ b/src/auction.js @@ -58,6 +58,7 @@ */ import { + callBurl, deepAccess, generateUUID, getValue, @@ -362,6 +363,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a function addWinningBid(winningBid) { const winningAd = adUnits.find(adUnit => adUnit.transactionId === winningBid.transactionId); _winningBids = _winningBids.concat(winningBid); + callBurl(winningBid); adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits); if (winningAd && !winningAd.deferBilling) adapterManager.callBidBillableBidder(winningBid); } diff --git a/src/prebid.js b/src/prebid.js index 6ad5120ce82..df224ab83cf 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -2,15 +2,11 @@ import {getGlobal} from './prebidGlobal.js'; import { - callBurl, - createInvisibleIframe, deepAccess, deepClone, deepSetValue, flatten, generateUUID, - inIframe, - insertElement, isArray, isArrayOfNums, isEmpty, @@ -22,8 +18,6 @@ import { logMessage, logWarn, mergeDeep, - replaceAuctionPrice, - replaceClickThrough, transformAdServerTargetingObj, uniques, unsupportedBidderMessage @@ -37,10 +31,8 @@ import {hook, wrapHook} from './hook.js'; import {loadSession} from './debugging.js'; import {includes} from './polyfill.js'; import {adunitCounter} from './adUnits.js'; -import {executeRenderer, isRendererRequired} from './Renderer.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; -import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; import {default as adapterManager, getS2SBidderSet} from './adapterManager.js'; import CONSTANTS from './constants.json'; import * as events from './events.js'; @@ -48,6 +40,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; +import {renderAdDirect} from '../libraries/creativeRender/direct.js'; import {getHighestCpm} from './utils/reducers.js'; import {fillVideoDefaults} from './video.js'; @@ -55,8 +48,7 @@ const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; /* private variables */ -const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, STALE_RENDER } = CONSTANTS.EVENTS; -const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON; +const { ADD_AD_UNITS, REQUEST_BIDS, SET_TARGETING } = CONSTANTS.EVENTS; const eventValidators = { bidWon: checkDefinedPlacement @@ -96,13 +88,6 @@ function checkDefinedPlacement(id) { return true; } -function setRenderSize(doc, width, height) { - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } -} - function validateSizes(sizes, targLength) { let cleanSizes = []; if (isArray(sizes) && ((targLength) ? sizes.length === targLength : sizes.length > 0)) { @@ -458,19 +443,6 @@ pbjsInstance.setTargetingForAst = function (adUnitCodes) { events.emit(SET_TARGETING, targeting.getAllTargeting()); }; -/** - * This function will check for presence of given node in given parent. If not present - will inject it. - * @param {Node} node node, whose existance is in question - * @param {Document} doc document element do look in - * @param {string} tagName tag name to look in - */ -function reinjectNodeIfRemoved(node, doc, tagName) { - const injectionNode = doc.querySelector(tagName); - if (!node.parentNode || node.parentNode !== injectionNode) { - insertElement(node, doc, tagName); - } -} - /** * This function will render the ad (based on params) in the given iframe document passed through. * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously @@ -481,103 +453,7 @@ function reinjectNodeIfRemoved(node, doc, tagName) { pbjsInstance.renderAd = hook('async', function (doc, id, options) { logInfo('Invoking $$PREBID_GLOBAL$$.renderAd', arguments); logMessage('Calling renderAd with adId :' + id); - - if (!id) { - const message = `Error trying to write ad Id :${id} to the page. Missing adId`; - emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); - return; - } - - try { - // lookup ad by ad Id - const bid = auctionManager.findBidByAdId(id); - if (!bid) { - const message = `Error trying to write ad. Cannot find ad by given id : ${id}`; - emitAdRenderFail({ reason: CANNOT_FIND_AD, message, id }); - return; - } - - if (bid.status === CONSTANTS.BID_STATUS.RENDERED) { - logWarn(`Ad id ${bid.adId} has been rendered before`); - events.emit(STALE_RENDER, bid); - if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { - return; - } - } - - // replace macros according to openRTB with price paid = bid.cpm - bid.ad = replaceAuctionPrice(bid.ad, bid.originalCpm || bid.cpm); - bid.adUrl = replaceAuctionPrice(bid.adUrl, bid.originalCpm || bid.cpm); - // replacing clickthrough if submitted - if (options && options.clickThrough) { - const {clickThrough} = options; - bid.ad = replaceClickThrough(bid.ad, clickThrough); - bid.adUrl = replaceClickThrough(bid.adUrl, clickThrough); - } - - // save winning bids - auctionManager.addWinningBid(bid); - - // emit 'bid won' event here - events.emit(BID_WON, bid); - - const {height, width, ad, mediaType, adUrl, renderer} = bid; - - // video module - if (FEATURES.VIDEO) { - const adUnitCode = bid.adUnitCode; - const adUnit = pbjsInstance.adUnits.filter(adUnit => adUnit.code === adUnitCode); - const videoModule = pbjsInstance.videoModule; - if (adUnit.video && videoModule) { - videoModule.renderBid(adUnit.video.divId, bid); - return; - } - } - - if (!doc) { - const message = `Error trying to write ad Id :${id} to the page. Missing document`; - emitAdRenderFail({ reason: MISSING_DOC_OR_ADID, message, id }); - return; - } - - const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); - insertElement(creativeComment, doc, 'html'); - - if (isRendererRequired(renderer)) { - executeRenderer(renderer, bid, doc); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - emitAdRenderSucceeded({ doc, bid, id }); - } else if ((doc === document && !inIframe()) || mediaType === 'video') { - const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; - emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id}); - } else if (ad) { - doc.write(ad); - doc.close(); - setRenderSize(doc, width, height); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - callBurl(bid); - emitAdRenderSucceeded({ doc, bid, id }); - } else if (adUrl) { - const iframe = createInvisibleIframe(); - iframe.height = height; - iframe.width = width; - iframe.style.display = 'inline'; - iframe.style.overflow = 'hidden'; - iframe.src = adUrl; - - insertElement(iframe, doc, 'body'); - setRenderSize(doc, width, height); - reinjectNodeIfRemoved(creativeComment, doc, 'html'); - callBurl(bid); - emitAdRenderSucceeded({ doc, bid, id }); - } else { - const message = `Error trying to write ad. No ad for bid response id: ${id}`; - emitAdRenderFail({reason: NO_AD, message, bid, id}); - } - } catch (e) { - const message = `Error trying to write ad Id :${id} to the page:${e.message}`; - emitAdRenderFail({ reason: EXCEPTION, message, id }); - } + renderAdDirect(doc, id, options); }); /** diff --git a/src/secureCreatives.js b/src/secureCreatives.js index c719bc191f2..0ea93e7e4fb 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -6,26 +6,24 @@ import * as events from './events.js'; import {fireNativeTrackers, getAllAssetsMessage, getAssetMessage} from './native.js'; import constants from './constants.json'; -import {deepAccess, isApnGetTagDefined, isGptPubadsDefined, logError, logWarn, replaceAuctionPrice} from './utils.js'; +import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; -import {executeRenderer, isRendererRequired} from './Renderer.js'; -import {config} from './config.js'; -import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js'; +import {emitAdRenderFail, emitAdRenderSucceeded, handleRender} from './adRendering.js'; +import {PREBID_EVENT, PREBID_NATIVE, PREBID_REQUEST, PREBID_RESPONSE} from '../libraries/creativeRender/constants.js'; const BID_WON = constants.EVENTS.BID_WON; -const STALE_RENDER = constants.EVENTS.STALE_RENDER; const WON_AD_IDS = new WeakSet(); const HANDLER_MAP = { - 'Prebid Request': handleRenderRequest, - 'Prebid Event': handleEventRequest, -} + [PREBID_REQUEST]: handleRenderRequest, + [PREBID_EVENT]: handleEventRequest, +}; if (FEATURES.NATIVE) { Object.assign(HANDLER_MAP, { - 'Prebid Native': handleNativeRequest, - }) + [PREBID_NATIVE]: handleNativeRequest, + }); } export function listenMessagesFromCreative() { @@ -35,18 +33,18 @@ export function listenMessagesFromCreative() { export function getReplier(ev) { if (ev.origin == null && ev.ports.length === 0) { return function () { - const msg = 'Cannot post message to a frame with null origin. Please update creatives to use MessageChannel, see https://github.com/prebid/Prebid.js/issues/7870' - logError(msg) + const msg = 'Cannot post message to a frame with null origin. Please update creatives to use MessageChannel, see https://github.com/prebid/Prebid.js/issues/7870'; + logError(msg); throw new Error(msg); - } + }; } else if (ev.ports.length > 0) { return function (message) { ev.ports[0].postMessage(JSON.stringify(message)); - } + }; } else { return function (message) { ev.source.postMessage(JSON.stringify(message), ev.origin); - } + }; } } @@ -69,39 +67,13 @@ export function receiveMessage(ev) { } } -function handleRenderRequest(reply, data, adObject) { - if (adObject == null) { - emitAdRenderFail({ - reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - message: `Cannot find ad for cross-origin render request: '${data.adId}'`, - id: data.adId - }); - return; - } - if (adObject.status === constants.BID_STATUS.RENDERED) { - logWarn(`Ad id ${adObject.adId} has been rendered before`); - events.emit(STALE_RENDER, adObject); - if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { - return; - } - } - - try { - _sendAdToCreative(adObject, reply); - } catch (e) { - emitAdRenderFail({ - reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION, - message: e.message, - id: data.adId, - bid: adObject - }); - return; - } - - // save winning bids - auctionManager.addWinningBid(adObject); - - events.emit(BID_WON, adObject); +function handleRenderRequest(reply, message, bidResponse) { + handleRender(function (adData) { + resizeRemoteCreative(bidResponse); + reply(Object.assign({ + message: PREBID_RESPONSE, + }, adData)); + }, {options: message.options, adId: message.adId, bidResponse}); } function handleNativeRequest(reply, data, adObject) { @@ -164,29 +136,11 @@ function handleEventRequest(reply, data, adObject) { }); break; default: - logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`) - } -} - -export function _sendAdToCreative(adObject, reply) { - const { adId, ad, adUrl, width, height, renderer, cpm, originalCpm } = adObject; - // rendering for outstream safeframe - if (isRendererRequired(renderer)) { - executeRenderer(renderer, adObject); - } else if (adId) { - resizeRemoteCreative(adObject); - reply({ - message: 'Prebid Response', - ad: replaceAuctionPrice(ad, originalCpm || cpm), - adUrl: replaceAuctionPrice(adUrl, originalCpm || cpm), - adId, - width, - height - }); + logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`); } } -function resizeRemoteCreative({ adId, adUnitCode, width, height }) { +export function resizeRemoteCreative({adId, adUnitCode, width, height}) { // resize both container div + iframe ['div', 'iframe'].forEach(elmType => { // not select element that gets removed after dfp render @@ -208,9 +162,9 @@ function resizeRemoteCreative({ adId, adUnitCode, width, height }) { function getElementIdBasedOnAdServer(adId, adUnitCode) { if (isGptPubadsDefined()) { - return getDfpElementId(adId) + return getDfpElementId(adId); } else if (isApnGetTagDefined()) { - return getAstElementId(adUnitCode) + return getAstElementId(adUnitCode); } else { return adUnitCode; } diff --git a/src/utils.js b/src/utils.js index 256dfb15174..288f0d5da5d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -599,9 +599,15 @@ export function isSafariBrowser() { return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } -export function replaceAuctionPrice(str, cpm) { +export function replaceMacros(str, subs) { if (!str) return; - return str.replace(/\$\{AUCTION_PRICE\}/g, cpm); + return Object.entries(subs).reduce((str, [key, val]) => { + return str.replace(new RegExp('\\$\\{' + key + '\\}', 'g'), val || ''); + }, str); +} + +export function replaceAuctionPrice(str, cpm) { + return replaceMacros(str, {AUCTION_PRICE: cpm}) } export function replaceClickThrough(str, clicktag) { diff --git a/test/spec/creative/crossDomainCreative_spec.js b/test/spec/creative/crossDomainCreative_spec.js new file mode 100644 index 00000000000..765d5e5311a --- /dev/null +++ b/test/spec/creative/crossDomainCreative_spec.js @@ -0,0 +1,182 @@ +import {renderer} from '../../../libraries/creativeRender/crossDomain.js'; +import { + AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, EXCEPTION, NO_AD, + PREBID_EVENT, + PREBID_REQUEST, + PREBID_RESPONSE +} from '../../../libraries/creativeRender/constants.js'; + +describe('cross-domain creative', () => { + let win, renderAd, messages, mkIframe, listeners; + + beforeEach(() => { + messages = []; + listeners = []; + mkIframe = sinon.stub(); + win = { + document: { + body: { + appendChild: sinon.stub(), + }, + createElement: sinon.stub().callsFake(tagname => { + switch (tagname.toLowerCase()) { + case 'a': + return document.createElement('a') + case 'iframe': { + return mkIframe(); + } + } + }) + }, + addEventListener: sinon.stub().callsFake((_, listener) => listeners.push(listener)), + parent: { + postMessage: sinon.stub().callsFake((payload, targetOrigin, transfer) => { + messages.push({payload: JSON.parse(payload), targetOrigin, transfer}); + }) + } + }; + renderAd = renderer(win); + }) + + it('derives postMessage target origin from pubUrl ', () => { + renderAd({pubUrl: 'https://domain.com:123/path'}); + expect(messages[0].targetOrigin).to.eql('https://domain.com:123') + }); + + it('generates request message with adId and clickUrl', () => { + renderAd({adId: '123', clickUrl: 'https://click-url.com'}); + expect(messages[0].payload).to.eql({ + message: PREBID_REQUEST, + adId: '123', + options: { + clickUrl: 'https://click-url.com' + } + }) + }) + + Object.entries({ + 'MessageChannel': (msg) => messages[0].transfer[0].postMessage(msg), + 'message listener': (msg) => listeners.forEach((fn) => fn({data: msg})) + }).forEach(([t, transport]) => { + describe(`when using ${t}`, () => { + function reply(msg) { + transport(JSON.stringify(msg)) + }; + + it('ignores messages that are not a prebid response message', () => { + renderAd({adId: '123'}); + reply({adId: '123', ad: 'markup'}); + sinon.assert.notCalled(mkIframe); + }) + + describe('signals AD_RENDER_FAILED', () => { + it('on exception', (done) => { + mkIframe.callsFake(() => { throw new Error('error message') }); + renderAd({adId: '123'}); + reply({message: PREBID_RESPONSE, adId: '123', ad: 'markup'}); + setTimeout(() => { + expect(messages[1].payload).to.eql({ + message: PREBID_EVENT, + adId: '123', + event: AD_RENDER_FAILED, + info: { + reason: EXCEPTION, + message: 'error message' + } + }) + done(); + }, 100) + }); + + it('on missing ad', (done) => { + renderAd({adId: '123'}); + reply({message: PREBID_RESPONSE, adId: '123'}); + setTimeout(() => { + sinon.assert.match(messages[1].payload, { + message: PREBID_EVENT, + adId: '123', + event: AD_RENDER_FAILED, + info: { + reason: NO_AD, + } + }) + done(); + }, 100) + }) + }); + + describe('rendering', () => { + let iframe; + + beforeEach(() => { + iframe = { + attrs: {}, + setAttribute: sinon.stub().callsFake((k, v) => iframe.attrs[k.toLowerCase()] = v), + contentDocument: { + open: sinon.stub(), + write: sinon.stub(), + close: sinon.stub(), + } + } + mkIframe.callsFake(() => iframe); + }); + + it('renders adUrl as iframe src', (done) => { + renderAd({adId: '123'}); + reply({message: PREBID_RESPONSE, adId: '123', adUrl: 'some-url'}); + setTimeout(() => { + sinon.assert.calledWith(win.document.body.appendChild, iframe); + expect(iframe.attrs.src).to.eql('some-url'); + done(); + }, 100) + }); + + it('renders ad through document.write', (done) => { + renderAd({adId: '123'}); + reply({message: PREBID_RESPONSE, adId: '123', ad: 'some-markup'}); + setTimeout(() => { + sinon.assert.calledWith(win.document.body.appendChild, iframe); + sinon.assert.called(iframe.contentDocument.open); + sinon.assert.calledWith(iframe.contentDocument.write, 'some-markup'); + sinon.assert.called(iframe.contentDocument.close); + done(); + }, 100) + }); + + Object.entries({ + adUrl: 'mock-ad-url', + ad: 'mock-ad-markup' + }).forEach(([prop, propValue]) => { + describe(`when message has ${prop}`, () => { + beforeEach((done) => { + renderAd({adId: '123'}); + reply({ + message: PREBID_RESPONSE, + adId: '123', + [prop]: propValue, + width: 100, + height: 200 + }); + setTimeout(done, 100); + }); + + it('emits AD_RENDER_SUCCEEDED', () => { + expect(messages[1].payload).to.eql({ + message: PREBID_EVENT, + adId: '123', + event: AD_RENDER_SUCCEEDED + }) + }); + + it('sets iframe height / width to ad height / width', () => { + sinon.assert.match(iframe.attrs, { + width: 100, + height: 200 + }) + }) + }) + }) + }); + }); + }); +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 664f7ebb58f..00eedb9a2b5 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -14,7 +14,7 @@ import { config as configObj } from 'src/config.js'; import * as ajaxLib from 'src/ajax.js'; import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; -import { _sendAdToCreative } from 'src/secureCreatives.js'; +import {resizeRemoteCreative} from 'src/secureCreatives.js'; import {find} from 'src/polyfill.js'; import * as pbjsModule from 'src/prebid.js'; import {hook} from '../../../src/hook.js'; @@ -1104,7 +1104,7 @@ describe('Unit: Prebid Module', function () { adUnitCode: config.adUnitCodes[0], }; - _sendAdToCreative(mockAdObject, sinon.stub()); + resizeRemoteCreative(mockAdObject); expect(slots[0].spyGetSlotElementId.called).to.equal(false); expect(slots[1].spyGetSlotElementId.called).to.equal(true); @@ -1233,7 +1233,8 @@ describe('Unit: Prebid Module', function () { } }, getElementsByTagName: sinon.stub(), - querySelector: sinon.stub() + querySelector: sinon.stub(), + createElement: sinon.stub(), }; elStub = { @@ -1264,7 +1265,7 @@ describe('Unit: Prebid Module', function () { it('should require doc and id params', function () { $$PREBID_GLOBAL$$.renderAd(); - var error = 'Error trying to write ad Id :undefined to the page. Missing adId'; + var error = 'Error rendering ad (id: undefined): missing adId'; assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); }); @@ -1289,14 +1290,13 @@ describe('Unit: Prebid Module', function () { adUrl: 'http://server.example.com/ad/ad.js' }); $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.ok(elStub.insertBefore.called, 'url was written to iframe in doc'); + sinon.assert.calledWith(doc.createElement, 'iframe'); }); it('should log an error when no ad or url', function () { pushBidResponseToAuction({}); $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var error = 'Error trying to write ad. No ad for bid response id: ' + bidId; - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + sinon.assert.called(spyLogError); }); it('should log an error when not in an iFrame', function () { @@ -1305,7 +1305,7 @@ describe('Unit: Prebid Module', function () { }); inIframe = false; $$PREBID_GLOBAL$$.renderAd(document, bidId); - const error = 'Error trying to write ad. Ad render call ad id ' + bidId + ' was prevented from writing to the main document.'; + const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); @@ -1326,14 +1326,14 @@ describe('Unit: Prebid Module', function () { doc.write = sinon.stub().throws(error); $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var errorMessage = 'Error trying to write ad Id :' + bidId + ' to the page:' + error.message; + var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); }); it('should log an error when ad not found', function () { var fakeId = 99; $$PREBID_GLOBAL$$.renderAd(doc, fakeId); - var error = 'Error trying to write ad. Cannot find ad by given id : ' + fakeId; + var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` assert.ok(spyLogError.calledWith(error), 'expected error was logged'); }); @@ -1345,14 +1345,6 @@ describe('Unit: Prebid Module', function () { assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); }); - it('should replace ${CLICKTHROUGH} macro in winning bids response', function () { - pushBidResponseToAuction({ - ad: "" - }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId, {clickThrough: 'https://someadserverclickurl.com'}); - expect(adResponse).to.have.property('ad').and.to.match(/https:\/\/someadserverclickurl\.com/i); - }); - it('fires billing url if present on s2s bid', function () { const burl = 'http://www.example.com/burl'; pushBidResponseToAuction({ diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 7d5f9af35dd..895bf03165a 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,6 +1,4 @@ -import { - _sendAdToCreative, getReplier, receiveMessage -} from 'src/secureCreatives.js'; +import {getReplier, receiveMessage} from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; @@ -8,10 +6,11 @@ import * as auctionModule from 'src/auction.js'; import * as native from 'src/native.js'; import {fireNativeTrackers, getAllAssetsMessage} from 'src/native.js'; import * as events from 'src/events.js'; -import { config as configObj } from 'src/config.js'; +import {config as configObj} from 'src/config.js'; import 'src/prebid.js'; -import { expect } from 'chai'; +import {expect} from 'chai'; +import {handleRender} from '../../../src/adRendering.js'; var CONSTANTS = require('src/constants.json'); @@ -54,37 +53,45 @@ describe('secureCreatives', () => { }); }); - describe('_sendAdToCreative', () => { - beforeEach(function () { - sinon.stub(utils, 'logError'); - sinon.stub(utils, 'logWarn'); + describe('handleRender', () => { + let bidResponse, renderFn, result; + beforeEach(() => { + result = null; + renderFn = sinon.stub().callsFake((r) => { result = r; }); + bidResponse = { + adId: 123 + } }); - afterEach(function () { - utils.logError.restore(); - utils.logWarn.restore(); + it('does not invoke renderFn, but the renderer instead, if the ad has one', () => { + const renderer = { + url: 'some-custom-renderer', + render: sinon.spy() + } + handleRender(renderFn, {bidResponse: {renderer}}); + sinon.assert.notCalled(renderFn); + sinon.assert.called(renderer.render); }); - it('should macro replace ${AUCTION_PRICE} with the winning bid for ad and adUrl', () => { - const oldVal = window.googletag; - const oldapntag = window.apntag; - window.apntag = null - window.googletag = null; - const mockAdObject = { - adId: 'someAdId', - ad: '', - adUrl: 'http://creative.prebid.org/${AUCTION_PRICE}', - width: 300, - height: 250, - renderer: null, - cpm: '1.00', - adUnitCode: 'some_dom_id' - }; - const reply = sinon.spy(); - _sendAdToCreative(mockAdObject, reply); - expect(reply.args[0][0].ad).to.equal(''); - expect(reply.args[0][0].adUrl).to.equal('http://creative.prebid.org/1.00'); - window.googletag = oldVal; - window.apntag = oldapntag; + + ['ad', 'adUrl'].forEach((prop) => { + describe(`on ${prop}`, () => { + it('replaces AUCTION_PRICE macro', () => { + bidResponse[prop] = 'pre${AUCTION_PRICE}post'; + bidResponse.cpm = 123; + handleRender(renderFn, {adId: 123, bidResponse}); + expect(result[prop]).to.eql('pre123post'); + }); + it('replaces CLICKTHROUGH macro', () => { + bidResponse[prop] = 'pre${CLICKTHROUGH}post'; + handleRender(renderFn, {adId: 123, bidResponse, options: {clickUrl: 'clk'}}); + expect(result[prop]).to.eql('preclkpost'); + }); + it('defaults CLICKTHROUGH to empty string', () => { + bidResponse[prop] = 'pre${CLICKTHROUGH}post'; + handleRender(renderFn, {adId: 123, bidResponse}); + expect(result[prop]).to.eql('prepost'); + }); + }); }); }); diff --git a/webpack.creative.js b/webpack.creative.js new file mode 100644 index 00000000000..7279455e155 --- /dev/null +++ b/webpack.creative.js @@ -0,0 +1,19 @@ +const path = require('path'); + +module.exports = { + mode: 'production', + resolve: { + modules: [ + path.resolve('.'), + 'node_modules' + ], + }, + entry: { + 'creative': { + import: './libraries/creativeRender/crossDomain.js', + }, + }, + output: { + path: path.resolve('./build/creative'), + }, +} From c9d34ed3774eca35bee9fe79fa2e037bf68c95d1 Mon Sep 17 00:00:00 2001 From: iprom-adserver <79305981+iprom-adserver@users.noreply.github.com> Date: Tue, 21 Nov 2023 14:54:28 +0100 Subject: [PATCH 206/760] feat: Add gvlid to iprom bid adapter (#10750) Co-authored-by: Gasper --- modules/ipromBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ipromBidAdapter.js b/modules/ipromBidAdapter.js index eaf20ad3ad3..1188af471a7 100644 --- a/modules/ipromBidAdapter.js +++ b/modules/ipromBidAdapter.js @@ -3,13 +3,15 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'iprom'; const ENDPOINT_URL = 'https://core.iprom.net/programmatic'; -const VERSION = 'v1.0.2'; +const VERSION = 'v1.0.3'; const DEFAULT_CURRENCY = 'EUR'; const DEFAULT_NETREVENUE = true; const DEFAULT_TTL = 360; +const IAB_GVL_ID = 811; export const spec = { code: BIDDER_CODE, + gvlid: IAB_GVL_ID, isBidRequestValid: function ({ bidder, params = {} } = {}) { // id parameter checks if (!params.id) { From c8d83f06bef616fd8456b0402a7ff289c209382e Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 22 Nov 2023 10:02:26 +0200 Subject: [PATCH 207/760] ShinezRTB Bid Adapter : initial release (#10405) --- modules/shinezRtbBidAdapter.js | 336 +++++++++ modules/shinezRtbBidAdapter.md | 35 + test/spec/modules/shinezRtbBidAdapter_spec.js | 639 ++++++++++++++++++ 3 files changed, 1010 insertions(+) create mode 100644 modules/shinezRtbBidAdapter.js create mode 100644 modules/shinezRtbBidAdapter.md create mode 100644 test/spec/modules/shinezRtbBidAdapter_spec.js diff --git a/modules/shinezRtbBidAdapter.js b/modules/shinezRtbBidAdapter.js new file mode 100644 index 00000000000..d1d9f36a569 --- /dev/null +++ b/modules/shinezRtbBidAdapter.js @@ -0,0 +1,336 @@ +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; + +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'shinezRtb'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.sweetgum.io`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + gpid: gpid, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {bidId} = request.data; + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + metaData, + advertiserDomains, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.sweetgum.io/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.sweetgum.io/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/shinezRtbBidAdapter.md b/modules/shinezRtbBidAdapter.md new file mode 100644 index 00000000000..e9190c2a9c4 --- /dev/null +++ b/modules/shinezRtbBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Shinez RTB Bid Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** tech-team@shinez.io + +# Description + +Module that connects to Shinez RTB demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'shinezRtb', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js new file mode 100644 index 00000000000..3965cd69c5f --- /dev/null +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -0,0 +1,639 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/shinezRtbBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import {deepAccess} from 'src/utils.js'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId', 'digitrustid']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + }, + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppConsent': { + 'gppString': 'gpp_string', + 'applicableSections': [7] + }, + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['sweetgum.io'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('ShinezRtbBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + shinezRtb: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + prebidVersion: version, + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '0123456789' + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000, + enableTIDs: true + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.sweetgum.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.sweetgum.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['sweetgum.io'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['sweetgum.io'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['sweetgum.io'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'digitrustid': + return {data: {id}}; + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + shinezRtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + shinezRtb: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From 59532d6c2c684ee2ad89fca440d06455e560f24d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Sun, 26 Nov 2023 00:09:26 +0000 Subject: [PATCH 208/760] Prebid 8.25.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4650db8f2ab..981f34675f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.25.0-pre", + "version": "8.25.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 4ea9131920d..7236673d372 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.25.0-pre", + "version": "8.25.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From aa73d61dd9ce48d29a2e1607e2112d1887f6e870 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Sun, 26 Nov 2023 00:09:26 +0000 Subject: [PATCH 209/760] Increment version to 8.26.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 981f34675f8..2563c9435f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.25.0", + "version": "8.26.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7236673d372..8aef0e70e8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.25.0", + "version": "8.26.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1205d53ee866dc669666228923a2097fd08c8e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Sun, 26 Nov 2023 17:53:23 +0100 Subject: [PATCH 210/760] Criteo bid adapter: Bump fastbid version (#10696) --- modules/criteoBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 7c9411ccc2d..45769edaf61 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -31,7 +31,7 @@ const LOG_PREFIX = 'Criteo: '; Unminified source code can be found in the privately shared repo: https://github.com/Prebid-org/prebid-js-external-js-criteo/blob/master/dist/prod.js */ const FAST_BID_VERSION_PLACEHOLDER = '%FAST_BID_VERSION%'; -export const FAST_BID_VERSION_CURRENT = 139; +export const FAST_BID_VERSION_CURRENT = 144; const FAST_BID_VERSION_LATEST = 'latest'; const FAST_BID_VERSION_NONE = 'none'; const PUBLISHER_TAG_URL_TEMPLATE = 'https://static.criteo.net/js/ld/publishertag.prebid' + FAST_BID_VERSION_PLACEHOLDER + '.js'; From 656680b471685d0f777c726a0c52cc47075e794d Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:00:46 +0100 Subject: [PATCH 211/760] identityLinkIdSystem: include gpp in 3p envelope retrieval (#10745) * identityLinkIdSystem - liveramp - include gpp in 3p envelope retrieval * identityLinkIdSystem - liveramp - adjust test description --- modules/identityLinkIdSystem.js | 9 +++- .../spec/modules/identityLinkIdSystem_spec.js | 44 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index ba794df1a9c..a57f8d22753 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -10,6 +10,7 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { gppDataHandler } from '../src/adapterManager.js'; const MODULE_NAME = 'identityLink'; @@ -59,12 +60,16 @@ export const identityLinkSubmodule = { utils.logInfo('identityLink: Consent string is required to call envelope API.'); return; } - const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}`; + const gppData = gppDataHandler.getConsentData(); + const gppString = gppData && gppData.gppString ? gppData.gppString : false; + const gppSectionId = gppData && gppData.gppString && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== -1 ? gppData.applicableSections[0] : false; + const hasGpp = gppString && gppSectionId; + const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}${hasGpp ? '&gpp=' + gppString + '&gpp_sid=' + gppSectionId : ''}`; let resp; resp = function (callback) { // Check ats during callback so it has a chance to initialise. // If ats library is available, use it to retrieve envelope. If not use standard third party endpoint - if (window.ats) { + if (window.ats && window.ats.retrieveEnvelope) { utils.logInfo('identityLink: ATS exists!'); window.ats.retrieveEnvelope(function (envelope) { if (envelope) { diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index a273f26b28b..f1c2773de07 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -3,6 +3,7 @@ import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; import {getCoreStorageManager} from '../../../src/storageManager.js'; import {stub} from 'sinon'; +import { gppDataHandler } from '../../../src/adapterManager.js'; const storage = getCoreStorageManager(); @@ -20,6 +21,7 @@ function setTestEnvelopeCookie () { describe('IdentityLinkId tests', function () { let logErrorStub; + let gppConsentDataStub; beforeEach(function () { defaultConfigParams = { params: {pid: pid} }; @@ -112,6 +114,48 @@ describe('IdentityLinkId tests', function () { expect(callBackSpy.calledOnce).to.be.true; }); + it('should call the LiveRamp envelope endpoint with GPP consent string', function() { + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + gppConsentDataStub.returns({ + ready: true, + gppString: 'DBABLA~BVVqAAAACqA.QA', + applicableSections: [7] + }); + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&gpp=DBABLA~BVVqAAAACqA.QA&gpp_sid=7'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + gppConsentDataStub.restore(); + }); + + it('should call the LiveRamp envelope endpoint without GPP consent string if consent string is not provided', function () { + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + gppConsentDataStub.returns({ + ready: true, + gppString: '', + applicableSections: [7] + }); + let callBackSpy = sinon.spy(); + let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; + submoduleCallback(callBackSpy); + let request = server.requests[0]; + expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14'); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + expect(callBackSpy.calledOnce).to.be.true; + gppConsentDataStub.restore(); + }); + it('should not throw Uncaught TypeError when envelope endpoint returns empty response', function () { let callBackSpy = sinon.spy(); let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams).callback; From b1e601aa4e29553871b372d0aeee7086b613c008 Mon Sep 17 00:00:00 2001 From: barRubi <151519394+barRubi@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:13:49 +0200 Subject: [PATCH 212/760] Taboola Bid Adapter: cast tmax type (#10766) * tmax * tmax * tmax * tmax --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 86766200aaa..0d56d6daec7 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -125,7 +125,7 @@ export const spec = { site, device, source: {fd: 1}, - tmax: bidderRequest.timeout, + tmax: (bidderRequest.timeout == undefined) ? undefined : parseInt(bidderRequest.timeout), bcat: ortb2.bcat || bidRequest.params.bcat || [], badv: ortb2.badv || bidRequest.params.badv || [], wlang: ortb2.wlang || bidRequest.params.wlang || [], diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 16bbb525ee7..1b6104599d7 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -275,6 +275,26 @@ describe('Taboola Adapter', function () { expect(resData.tmax).to.equal(500); }); + it('should pass bidder tmax as int', function () { + const bidderRequest = { + ...commonBidderRequest, + timeout: '500' + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(500); + }); + + it('should pass bidder timeout as null', function () { + const bidderRequest = { + ...commonBidderRequest, + timeout: null + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + expect(resData.tmax).to.equal(undefined); + }); + describe('first party data', function () { it('should parse first party data', function () { const bidderRequest = { From f215abd72ad5820f16011631a0e103f05ac2c9d1 Mon Sep 17 00:00:00 2001 From: LyricWulf Date: Mon, 27 Nov 2023 07:59:42 -0800 Subject: [PATCH 213/760] adxcg Bid Adapter : fix convertTypes util import (#10763) * adxcgBidAdapter: fix convertTypes util import * adxcgBidAdapter: remove previous import --- modules/adxcgBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index ab0ae1cbd48..dda88575ff5 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,9 +1,9 @@ // jshint esversion: 6, es3: false, node: true import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { convertTypes } from '../libraries/transformParamsUtils/convertTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { - convertTypes, isArray, replaceAuctionPrice, triggerPixel, From 5945465a4218a5278bdc3e9186e7c274f55a00b6 Mon Sep 17 00:00:00 2001 From: Wls-demo <67785512+Wls-demo@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:25:16 +0200 Subject: [PATCH 214/760] Boldwin Bid Adapter : update sync url (#10757) * new boldwin bid adapter * fix * Restarting ci / circleci * changes * fixed conflicts * added endpointId param * kick off CircleCI tests * gpp * update sync url --------- Co-authored-by: Aiholkin Co-authored-by: Vladislav Isaiko Co-authored-by: Mykhailo Yaremchuk Co-authored-by: Chris Huie --- modules/boldwinBidAdapter.js | 2 +- test/spec/modules/boldwinBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index 4d97f830d33..c7def383b5e 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -5,7 +5,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'boldwin'; const AD_URL = 'https://ssp.videowalldirect.com/pbjs'; -const SYNC_URL = 'https://cs.videowalldirect.com' +const SYNC_URL = 'https://sync.videowalldirect.com'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 52a6ec03757..9a7b16c0914 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -314,7 +314,7 @@ describe('BoldwinBidAdapter', function () { expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://cs.videowalldirect.com'); + expect(userSync[0].url).to.be.equal('https://sync.videowalldirect.com'); }); }); }); From 498b6b50da4a8b02d753177f43afb7de9218c9d3 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Mon, 27 Nov 2023 18:40:06 +0100 Subject: [PATCH 215/760] Onetag Bid Adapter: FLEDGE/PAAPI support (#10755) Co-authored-by: federico --- modules/onetagBidAdapter.js | 16 ++++- test/spec/modules/onetagBidAdapter_spec.js | 69 +++++++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index eb381727a3b..ee6d2980385 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -87,6 +87,7 @@ function buildRequests(validBidRequests, bidderRequest) { const connection = navigator.connection || navigator.webkitConnection; payload.networkConnectionType = (connection && connection.type) ? connection.type : null; payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; + payload.fledgeEnabled = Boolean(bidderRequest && bidderRequest.fledgeEnabled) return { method: 'POST', url: ENDPOINT, @@ -101,10 +102,10 @@ function interpretResponse(serverResponse, bidderRequest) { if (!body || (body.nobid && body.nobid === true)) { return bids; } - if (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0) { + if (!body.fledgeAuctionConfigs && (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0)) { return bids; } - body.bids.forEach(bid => { + Array.isArray(body.bids) && body.bids.forEach(bid => { const responseBid = { requestId: bid.requestId, cpm: bid.cpm, @@ -141,7 +142,16 @@ function interpretResponse(serverResponse, bidderRequest) { } bids.push(responseBid); }); - return bids; + + if (body.fledgeAuctionConfigs && Array.isArray(body.fledgeAuctionConfigs)) { + const fledgeAuctionConfigs = body.fledgeAuctionConfigs + return { + bids, + fledgeAuctionConfigs, + } + } else { + return bids; + } } function createRenderer(bid, rendererOptions = {}) { diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 6d43dd2e40e..7960574c1a4 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -189,7 +189,7 @@ describe('onetag', function () { }); it('Should contain all keys', function () { expect(data).to.be.an('object'); - expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version'); + expect(data).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'bids', 'docHidden', 'xOffset', 'yOffset', 'networkConnectionType', 'networkEffectiveConnectionType', 'timing', 'version', 'fledgeEnabled'); expect(data.location).to.satisfy(function (value) { return value === null || typeof value === 'string'; }); @@ -213,6 +213,7 @@ describe('onetag', function () { expect(data.networkEffectiveConnectionType).to.satisfy(function (value) { return value === null || typeof value === 'string' }); + expect(data.fledgeEnabled).to.be.a('boolean'); expect(data.bids).to.be.an('array'); expect(data.version).to.have.all.keys('prebid', 'adapter'); const bids = data['bids']; @@ -396,13 +397,61 @@ describe('onetag', function () { expect(payload.ortb2).to.exist.and.to.deep.equal(firtPartyData); }); }); + it('Should send FLEDGE eligibility flag when FLEDGE is enabled', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'fledgeEnabled': true + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + }); + it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'fledgeEnabled': false + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + }); + it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(false); + }); describe('interpretResponse', function () { const request = getBannerVideoRequest(); const response = getBannerVideoResponse(); + const fledgeResponse = getFledgeBannerResponse(); const requestData = JSON.parse(request.data); it('Returns an array of valid server responses if response object is valid', function () { const interpretedResponse = spec.interpretResponse(response, request); + const fledgeInterpretedResponse = spec.interpretResponse(fledgeResponse, request); expect(interpretedResponse).to.be.an('array').that.is.not.empty; + expect(fledgeInterpretedResponse).to.be.an('object'); + expect(fledgeInterpretedResponse.bids).to.satisfy(function (value) { + return value === null || Array.isArray(value); + }); + expect(fledgeInterpretedResponse.fledgeAuctionConfigs).to.be.an('array').that.is.not.empty; for (let i = 0; i < interpretedResponse.length; i++) { let dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); @@ -600,6 +649,24 @@ function getBannerVideoResponse() { }; } +function getFledgeBannerResponse() { + const bannerVideoResponse = getBannerVideoResponse(); + bannerVideoResponse.body.fledgeAuctionConfigs = [ + { + bidId: 'fledge', + config: { + seller: 'https://onetag-sys.com', + decisionLogicUrl: + 'https://onetag-sys.com/paapi/decision_logic.js', + interestGroupBuyers: [ + 'https://onetag-sys.com' + ], + } + } + ] + return bannerVideoResponse; +} + function getBannerVideoRequest() { return { data: JSON.stringify({ From ecf4272bf033fec84e54b26c6a6bcfdc414d9c8f Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Mon, 27 Nov 2023 09:48:39 -0800 Subject: [PATCH 216/760] FPD Enrichment: support for Cookie Deprecation Label (#10683) * cookie deprecation label * reverted unneeded changes made during development * addressed feedback * removed unnecessary else statement * added comment for FIRST_PARTY_GVLID param * ensure promise resolves when label is falsey * addressed feedback * removed async/await from getCookieDeprecationLabel * updating tests * more changes * addressed feedback * Small improvements --------- Co-authored-by: Demetrio Girardi --- modules/gdprEnforcement.js | 15 ++++- src/activities/redactor.js | 2 +- src/consentHandler.js | 8 +++ src/fpd/enrichment.js | 25 +++++++- test/spec/fpd/enrichment_spec.js | 70 ++++++++++++++++++++++- test/spec/modules/gdprEnforcement_spec.js | 17 +++++- 6 files changed, 129 insertions(+), 8 deletions(-) diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 4e600f71b90..5b73ec19e08 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -7,7 +7,7 @@ import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../src/consentHandler.js'; import { MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, @@ -111,7 +111,7 @@ export function getGvlid(moduleType, moduleName, fallbackFn) { if (gvlMapping && gvlMapping[moduleName]) { return gvlMapping[moduleName]; } else if (moduleType === MODULE_TYPE_PREBID) { - return VENDORLESS_GVLID; + return moduleName === 'cdep' ? FIRST_PARTY_GVLID : VENDORLESS_GVLID; } else { let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); if (gvlid == null && Object.keys(modules).length > 0) { @@ -166,6 +166,7 @@ export function shouldEnforce(consentData, purpose, name) { function getConsent(consentData, type, id, gvlId) { let purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${id}`); let vendor = !!deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); + if (type === 'purpose' && LI_PURPOSES.includes(id)) { purpose ||= !!deepAccess(consentData, `vendorData.purpose.legitimateInterests.${id}`); vendor ||= !!deepAccess(consentData, `vendorData.vendor.legitimateInterests.${gvlId}`); @@ -191,13 +192,21 @@ export function validateRules(rule, consentData, currentModule, gvlId) { } const vendorConsentRequred = rule.enforceVendor && !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); const {purpose, vendor} = getConsent(consentData, ruleOptions.type, ruleOptions.id, gvlId); - return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); + + let validation = (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); + + if (gvlId === FIRST_PARTY_GVLID) { + validation = (!rule.enforcePurpose || !!deepAccess(consentData, `vendorData.publisher.consents.${ruleOptions.id}`)); + } + + return validation; } function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback = () => null) { return function (params) { const consentData = gdprDataHandler.getConsentData(); const modName = params[ACTIVITY_PARAM_COMPONENT_NAME]; + if (shouldEnforce(consentData, purposeNo, modName)) { const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); let allow = !!checkConsent(consentData, modName, gvlid); diff --git a/src/activities/redactor.js b/src/activities/redactor.js index d052c029c13..694a96b2b14 100644 --- a/src/activities/redactor.js +++ b/src/activities/redactor.js @@ -18,7 +18,7 @@ export const ORTB_UFPD_PATHS = [ 'id', 'buyeruid', 'customdata' -].map(f => `user.${f}`); +].map(f => `user.${f}`).concat('device.ext.cdep'); export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids']; export const ORTB_GEO_PATHS = ['user.geo.lat', 'user.geo.lon', 'device.geo.lat', 'device.geo.lon']; diff --git a/src/consentHandler.js b/src/consentHandler.js index 9e3ee5b4c40..5b5d8b805cd 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -10,6 +10,14 @@ import {config} from './config.js'; */ export const VENDORLESS_GVLID = Object.freeze({}); +/** + * Placeholder gvlid for when device.ext.cdep is present (Privacy Sandbox cookie deprecation label). When this value is used as gvlid, the gdpr + * enforcement module will look to see that publisher consent was given. + * + * see https://github.com/prebid/Prebid.js/issues/10516 + */ +export const FIRST_PARTY_GVLID = Object.freeze({}); + export class ConsentHandler { #enabled; #data; diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index f812d8435d9..911509455e0 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -6,6 +6,10 @@ import {config} from '../config.js'; import {getHighEntropySUA, getLowEntropySUA} from './sua.js'; import {GreedyPromise} from '../utils/promise.js'; import {CLIENT_SECTIONS, clientSectionChecker, hasSection} from './oneClient.js'; +import {isActivityAllowed} from '../activities/rules.js'; +import {activityParams} from '../activities/activityParams.js'; +import {ACTIVITY_ACCESS_DEVICE} from '../activities/activities.js'; +import {MODULE_TYPE_PREBID} from '../activities/modules.js'; export const dep = { getRefererInfo, @@ -24,8 +28,10 @@ const oneClient = clientSectionChecker('FPD') * @returns: {Promise[{}]}: a promise to an enriched ortb2 object. */ export const enrichFPD = hook('sync', (fpd) => { - return GreedyPromise.all([fpd, getSUA().catch(() => null)]) - .then(([ortb2, sua]) => { + const promArr = [fpd, getSUA().catch(() => null), tryToGetCdepLabel().catch(() => null)]; + + return GreedyPromise.all(promArr) + .then(([ortb2, sua, cdep]) => { const ri = dep.getRefererInfo(); mergeLegacySetConfigs(ortb2); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { @@ -34,9 +40,18 @@ export const enrichFPD = hook('sync', (fpd) => { ortb2[section] = mergeDeep({}, data, ortb2[section]); } }); + if (sua) { deepSetValue(ortb2, 'device.sua', Object.assign({}, sua, ortb2.device.sua)); } + + if (cdep) { + const ext = { + cdep + } + deepSetValue(ortb2, 'device.ext', Object.assign({}, ext, ortb2.device.ext)); + } + ortb2 = oneClient(ortb2); for (let section of CLIENT_SECTIONS) { if (hasSection(ortb2, section)) { @@ -44,6 +59,7 @@ export const enrichFPD = hook('sync', (fpd) => { break; } } + return ortb2; }); }); @@ -78,6 +94,10 @@ function removeUndef(obj) { return getDefinedParams(obj, Object.keys(obj)) } +function tryToGetCdepLabel() { + return GreedyPromise.resolve('cookieDeprecationLabel' in navigator && isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && navigator.cookieDeprecationLabel.getValue()); +} + const ENRICHMENTS = { site(ortb2, ri) { if (CLIENT_SECTIONS.filter(p => p !== 'site').some(hasSection.bind(null, ortb2))) { @@ -93,6 +113,7 @@ const ENRICHMENTS = { return winFallback((win) => { const w = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; const h = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + return { w, h, diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 3b3afb15f8c..40692360dca 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -3,7 +3,10 @@ import {hook} from '../../../src/hook.js'; import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; +import * as activities from 'src/activities/rules.js' import {CLIENT_SECTIONS} from '../../../src/fpd/oneClient.js'; +import {ACTIVITY_ACCESS_DEVICE} from '../../../src/activities/activities.js'; +import {ACTIVITY_PARAM_COMPONENT} from '../../../src/activities/params.js'; describe('FPD enrichment', () => { let sandbox; @@ -213,7 +216,7 @@ describe('FPD enrichment', () => { ua: 'ua' }) }) - }) + }); }); }); @@ -310,6 +313,71 @@ describe('FPD enrichment', () => { }); }); + describe('privacy sandbox cookieDeprecationLabel', () => { + let isAllowed, cdep, shouldCleanupNav = false; + + before(() => { + if (!navigator.cookieDeprecationLabel) { + navigator.cookieDeprecationLabel = {}; + shouldCleanupNav = true; + } + }); + + after(() => { + if (shouldCleanupNav) { + delete navigator.cookieDeprecationLabel; + } + }); + + beforeEach(() => { + isAllowed = true; + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { + if (activity === ACTIVITY_ACCESS_DEVICE && params[ACTIVITY_PARAM_COMPONENT] === 'prebid.cdep') { + return isAllowed; + } else { + throw new Error('Unexpected activity check'); + } + }); + sandbox.stub(window.navigator, 'cookieDeprecationLabel').value({ + getValue: sinon.stub().callsFake(() => cdep) + }) + }) + + it('enrichment sets device.ext.cdep when allowed and navigator.getCookieDeprecationLabel exists', () => { + cdep = Promise.resolve('example-test-label'); + return fpd().then(ortb2 => { + expect(ortb2.device.ext.cdep).to.eql('example-test-label'); + }) + }); + + Object.entries({ + 'not allowed'() { + isAllowed = false; + }, + 'not supported'() { + delete navigator.cookieDeprecationLabel + } + }).forEach(([t, setup]) => { + it(`if ${t}, the navigator API is not called and no enrichment happens`, () => { + setup(); + cdep = Promise.resolve('example-test-label'); + return fpd().then(ortb2 => { + expect(ortb2.device.ext).to.not.exist; + if (navigator.cookieDeprecationLabel) { + sinon.assert.notCalled(navigator.cookieDeprecationLabel.getValue); + } + }) + }); + }) + + it('if the navigator API returns a promise that rejects, the enrichment does not halt forever', () => { + cdep = Promise.reject(new Error('oops, something went wrong')); + return fpd().then(ortb2 => { + expect(ortb2.device.ext).to.not.exist; + }) + }); + }); + it('leaves only one of app, site, dooh', () => { return fpd({ app: {p: 'val'}, diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 2880b2fac5d..295b14dd796 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -26,7 +26,7 @@ import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../../../src/consentHandler.js'; import {validateStorageEnforcement} from '../../../src/storageManager.js'; import {activityParams} from '../../../src/activities/activityParams.js'; @@ -789,6 +789,21 @@ describe('gdpr enforcement', function () { }) }) + it('if validateRules is passed FIRST_PARTY_GVLID, it will use publisher.consents', () => { + const rule = createGdprRule(); + const consentData = { + 'vendorData': { + 'publisher': { + 'consents': { + '1': true + } + }, + }, + }; + const result = validateRules(rule, consentData, 'cdep', FIRST_PARTY_GVLID); + expect(result).to.equal(true); + }); + describe('validateRules', function () { Object.entries({ '1 (which does not consider LI)': [1, 'storage', false], From 8993f7462cdba5dabbdbc9b586e02abba1e0b9e6 Mon Sep 17 00:00:00 2001 From: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:48:14 +0100 Subject: [PATCH 217/760] Currency module: Don't block 'responsesReady' when rates are present and no conversion is done (#10736) * Currency module: Don't block 'responsesReady' when rates are present and no conversion is needed * Add test case for currency unnecessarily blocking auctions * Update currency_spec.js --------- Co-authored-by: Demetrio Girardi --- modules/currency.js | 2 ++ test/spec/modules/currency_spec.js | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/modules/currency.js b/modules/currency.js index e42799c0864..eaed4c50df2 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -148,6 +148,8 @@ function loadRates() { } } ); + } else { + processBidResponseQueue(); } } diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index 623194cbee9..fa44b7daa7a 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -259,6 +259,19 @@ describe('currency', function () { expect(innerBid.getCpmInNewCurrency('JPY')).to.equal('100.000'); }); + it('does not block auctions if rates do not need to be fetched', () => { + sandbox.stub(responseReady, 'resolve'); + setConfig({ + adServerCurrency: 'USD', + rates: { + USD: { + JPY: 100 + } + } + }); + sinon.assert.called(responseReady.resolve); + }) + it('uses rates specified in json when provided and consider boosted bid', function () { setConfig({ adServerCurrency: 'USD', From d21fb7d1e51fbdbfa44ec3a434dbb650228743ce Mon Sep 17 00:00:00 2001 From: cpcpn-emil <115714010+cpcpn-emil@users.noreply.github.com> Date: Tue, 28 Nov 2023 02:19:44 +0100 Subject: [PATCH 218/760] ConceptX Bid Adapter : bugfix to avoid appending to the request url on build (#10749) * New adapter: concepx * Syntax change * Revert syntax change * Defensive check for response from bidder server * Add better validation for the request * Merge branch 'master' of https://github.com/prebid/Prebid.js * Don't append url on every buildrequest --- modules/conceptxBidAdapter.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/conceptxBidAdapter.js b/modules/conceptxBidAdapter.js index 127b049bc99..87ac96f2131 100644 --- a/modules/conceptxBidAdapter.js +++ b/modules/conceptxBidAdapter.js @@ -3,23 +3,23 @@ import { BANNER } from '../src/mediaTypes.js'; // import { logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; const BIDDER_CODE = 'conceptx'; -let ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; +const ENDPOINT_URL = 'https://conceptx.cncpt-central.com/openrtb'; // const LOG_PREFIX = 'ConceptX: '; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], isBidRequestValid: function (bid) { - return !!(bid.bidId); + return !!(bid.bidId && bid.params.site && bid.params.adunit); }, buildRequests: function (validBidRequests, bidderRequest) { // logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); const requests = []; - + let requestUrl = `${ENDPOINT_URL}` if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - ENDPOINT_URL += '?gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; - ENDPOINT_URL += '&consentString=' + bidderRequest.gdprConsent.consentString; + requestUrl += '?gdpr_applies=' + bidderRequest.gdprConsent.gdprApplies; + requestUrl += '&consentString=' + bidderRequest.gdprConsent.consentString; } for (var i = 0; i < validBidRequests.length; i++) { const requestParent = { adUnits: [], meta: {} }; @@ -33,7 +33,7 @@ export const spec = { requestParent.adUnits.push(adUnit); requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: requestUrl, options: { withCredentials: false, }, @@ -51,6 +51,9 @@ export const spec = { return bidResponses } const firstBid = bidResponsesFromServer[0] + if (!firstBid) { + return bidResponses + } const firstSeat = firstBid.ads[0] const bidResponse = { requestId: firstSeat.requestId, From 6a0c7f0be8b35b3a0a2c5b85ac13e9b32db8f89e Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 28 Nov 2023 15:15:07 +0100 Subject: [PATCH 219/760] Grid bid adapter: call data deletion request c2s endpoint (#10732) Co-authored-by: v.raybaud --- modules/gridBidAdapter.js | 13 ++-------- test/spec/modules/gridBidAdapter_spec.js | 32 ++---------------------- 2 files changed, 4 insertions(+), 41 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index aa00a84273c..9673633a0fe 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -18,7 +18,7 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; -const USP_DELETE_DATA_HANDLER = 'https://media.grid.bidswitch.net/uspapi_delete' +const USP_DELETE_DATA_HANDLER = 'https://media.grid.bidswitch.net/uspapi_delete_c2s' const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid'; const TIME_TO_LIVE = 360; @@ -464,16 +464,7 @@ export const spec = { }, onDataDeletionRequest: function(data) { - const uids = []; - const aliases = [spec.code, ...spec.aliases.map((alias) => alias.code || alias)]; - data.forEach(({ bids }) => bids && bids.forEach(({ bidder, params }) => { - if (aliases.includes(bidder) && params && params.uid) { - uids.push(params.uid); - } - })); - if (uids.length) { - spec.ajaxCall(USP_DELETE_DATA_HANDLER, () => {}, JSON.stringify({ uids }), {contentType: 'application/json', method: 'POST'}); - } + spec.ajaxCall(USP_DELETE_DATA_HANDLER, null, null, {method: 'GET'}); } }; diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index b12083236a2..0611fa68bf8 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1559,37 +1559,9 @@ describe('TheMediaGrid Adapter', function () { }); it('should send right request on onDataDeletionRequest call', function() { - spec.onDataDeletionRequest([{ - bids: [ - { - bidder: 'grid', - params: { - uid: 1 - } - }, - { - bidder: 'grid', - params: { - uid: 2 - } - }, - { - bidder: 'another', - params: { - uid: 3 - } - }, - { - bidder: 'gridNM', - params: { - uid: 4 - } - } - ], - }]); + spec.onDataDeletionRequest([{}]); expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.equal('https://media.grid.bidswitch.net/uspapi_delete'); - expect(ajaxStub.firstCall.args[2]).to.equal('{"uids":[1,2,4]}'); + expect(ajaxStub.firstCall.args[0]).to.equal('https://media.grid.bidswitch.net/uspapi_delete_c2s'); }); }); From 4e323bf52cbf04d4b39796f9ebfd94ecfbb7c280 Mon Sep 17 00:00:00 2001 From: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:24:56 +0100 Subject: [PATCH 220/760] SparteoBidAdapter: Send bidder params in OpenRTB request (#10760) --- modules/sparteoBidAdapter.js | 7 +++++ test/spec/modules/sparteoBidAdapter_spec.js | 33 +++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index 7d4f87c24f3..ac48ec4089d 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -28,6 +28,13 @@ const converter = ortbConverter({ return request; }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + + deepSetValue(imp, 'ext.sparteo.params', bidRequest.params); + + return imp; + }, bidResponse(buildBidResponse, bid, context) { context.mediaType = deepAccess(bid, 'ext.prebid.type'); diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index e82f23a1d4e..d6b8b0cc2d8 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -12,7 +12,8 @@ const VALID_BID_BANNER = { bidId: '1a2b3c4d', adUnitCode: 'id-1234', params: { - networkId: '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + networkId: '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + formats: ['corner'] }, mediaTypes: { banner: { @@ -64,6 +65,14 @@ const VALID_REQUEST_BANNER = { 'w': 1 }], 'topframe': 0 + }, + 'ext': { + 'sparteo': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'formats': ['corner'] + } + } } }], 'site': { @@ -99,7 +108,12 @@ const VALID_REQUEST_VIDEO = { 'maxduration': 30, }, 'ext': { - 'pbadslot': 'video' + 'pbadslot': 'video', + 'sparteo': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } } }], 'site': { @@ -127,6 +141,14 @@ const VALID_REQUEST = { 'w': 1 }], 'topframe': 0 + }, + 'ext': { + 'sparteo': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'formats': ['corner'] + } + } } }, { 'id': '5e6f7g8h', @@ -144,7 +166,12 @@ const VALID_REQUEST = { 'maxduration': 30, }, 'ext': { - 'pbadslot': 'video' + 'pbadslot': 'video', + 'sparteo': { + 'params': { + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + } + } } }], 'site': { From 1e33758db4e1e77deb02bfee89664550be667be1 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 28 Nov 2023 18:24:05 +0100 Subject: [PATCH 221/760] Impactify Bid Adapter : Removing checks on bid request (#10769) * Remove use of local storage As requested, we remove the use of local storage. https://github.com/prebid/Prebid.js/issues/8689 * Update impactifyBidAdapter.js * Add differents mediatypes to Impactify bidder * Add differents mediatypes to Impactify bidder * Add differents mediatypes to Impactify bidder * Add format parameter for banner * add getFloor * add getFloor * add getFloor * add parsing of local storage * delete unused var * fix spacing with import * Add local storage key management * Adjustments * Fix eids object * Fix eids object * Fix eids object * Fix tests * Remove check on the request banner size parameter * Few adjustments --------- Co-authored-by: Thomas De Stefano Co-authored-by: Pang Ronnie --- modules/impactifyBidAdapter.js | 26 +++++++------------ test/spec/modules/impactifyBidAdapter_spec.js | 15 +++++------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index f446050265a..04a45e64b19 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -17,7 +17,7 @@ const AUCTION_URI = '/bidder'; const COOKIE_SYNC_URI = '/static/cookie_sync.html'; const GVL_ID = 606; const GET_CONFIG = config.getConfig; -export const STORAGE = getStorageManager({gvlid: GVL_ID, bidderCode: BIDDER_CODE}); +export const STORAGE = getStorageManager({ gvlid: GVL_ID, bidderCode: BIDDER_CODE }); export const STORAGE_KEY = '_im_str' /** @@ -114,7 +114,7 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { validBidRequests, cur: [DEFAULT_CURRENCY], imp: [], - source: {tid: bidderRequest.ortb2?.source?.tid} + source: { tid: bidderRequest.ortb2?.source?.tid } }; // Get the url parameters @@ -149,7 +149,7 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: ((navigator.language || navigator.userLanguage || '').split('-'))[0] || 'en', }; - request.site = {page: bidderRequest.refererInfo.page}; + request.site = { page: bidderRequest.refererInfo.page }; // Handle privacy settings for GDPR/CCPA/COPPA let gdprApplies = 0; @@ -176,7 +176,6 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { // Create imps with bids validBidRequests.forEach((bid) => { let bannerObj = deepAccess(bid.mediaTypes, `banner`); - let videoObj = deepAccess(bid.mediaTypes, `video`); let imp = { id: bid.bidId, @@ -184,16 +183,14 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { ext: helpers.getExtParamsFromBid(bid) }; - if (videoObj) { - imp.video = { - ...helpers.createOrtbImpVideoObj(bid) - } - } - if (bannerObj && typeof imp.ext.impactify.size == 'string') { imp.banner = { ...helpers.createOrtbImpBannerObj(bid, imp.ext.impactify.size) } + } else { + imp.video = { + ...helpers.createOrtbImpVideoObj(bid) + } } if (typeof bid.getFloor === 'function') { @@ -227,8 +224,6 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - let isBanner = deepAccess(bid.mediaTypes, `banner`); - if (typeof bid.params.appId != 'string' || !bid.params.appId) { return false; } @@ -241,9 +236,6 @@ export const spec = { if (bid.params.style !== 'inline' && bid.params.style !== 'impact' && bid.params.style !== 'static') { return false; } - if (isBanner && (typeof bid.params.size != 'string' || !bid.params.size.includes('x') || bid.params.size.split('x').length != 2)) { - return false; - } return true; }, @@ -369,7 +361,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 */ - onBidWon: function(bid) { + onBidWon: function (bid) { ajax(`${LOGGER_URI}/prebid/won`, null, JSON.stringify(bid), { method: 'POST', contentType: 'application/json' @@ -382,7 +374,7 @@ export const spec = { * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data */ - onTimeout: function(data) { + onTimeout: function (data) { ajax(`${LOGGER_URI}/prebid/timeout`, null, JSON.stringify(data[0]), { method: 'POST', contentType: 'application/json' diff --git a/test/spec/modules/impactifyBidAdapter_spec.js b/test/spec/modules/impactifyBidAdapter_spec.js index adf968d610d..d9bf4becb22 100644 --- a/test/spec/modules/impactifyBidAdapter_spec.js +++ b/test/spec/modules/impactifyBidAdapter_spec.js @@ -36,7 +36,7 @@ describe('ImpactifyAdapter', function () { localStorageIsEnabledStub = sandbox.stub(STORAGE, 'localStorageIsEnabled'); }); - afterEach(function() { + afterEach(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; document.body.appendChild.restore(); sandbox.restore(); @@ -57,7 +57,6 @@ describe('ImpactifyAdapter', function () { params: { appId: 'example.com', format: 'display', - size: '728x90', style: 'static' } } @@ -282,7 +281,7 @@ describe('ImpactifyAdapter', function () { }; it('should pass bidfloor', function () { - videoBidRequests[0].getFloor = function() { + videoBidRequests[0].getFloor = function () { return { currency: 'USD', floor: 1.23, @@ -342,7 +341,7 @@ describe('ImpactifyAdapter', function () { h: 1, hash: 'test', expiry: 166192938, - meta: {'advertiserDomains': ['testdomain.com']}, + meta: { 'advertiserDomains': ['testdomain.com'] }, ext: { prebid: { 'type': 'video' @@ -418,7 +417,7 @@ describe('ImpactifyAdapter', function () { height: 1, hash: 'test', expiry: 166192938, - meta: {'advertiserDomains': ['testdomain.com']}, + meta: { 'advertiserDomains': ['testdomain.com'] }, ttl: 300, creativeId: '97517771' } @@ -480,7 +479,7 @@ describe('ImpactifyAdapter', function () { h: 1, hash: 'test', expiry: 166192938, - meta: {'advertiserDomains': ['testdomain.com']}, + meta: { 'advertiserDomains': ['testdomain.com'] }, ext: { prebid: { 'type': 'video' @@ -536,8 +535,8 @@ describe('ImpactifyAdapter', function () { const result = spec.getUserSyncs('bad', [], gdprData); expect(result).to.be.empty; }); - it('should append the various values if they exist', function() { - const result = spec.getUserSyncs({iframeEnabled: true}, validResponse, gdprData); + it('should append the various values if they exist', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, validResponse, gdprData); expect(result[0].url).to.include('gdpr=1'); expect(result[0].url).to.include('gdpr_consent=BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA'); }); From 1488d8c10c7f56081a8cad3abc8475f7f7543739 Mon Sep 17 00:00:00 2001 From: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:39:53 -0800 Subject: [PATCH 222/760] Unruly: Protected Audience Support (#10709) * Unruly Protected Audience Support * Unruly Conditional Protected Audience Support * Unruly map bidIds to AuctionConfigs * Unruly: Update Protected Audience origin and descision logic URL. * Unruly: Fix lint error --------- Co-authored-by: Brian Haines --- modules/unrulyBidAdapter.js | 55 ++- test/spec/modules/unrulyBidAdapter_spec.js | 424 ++++++++++++++++++++- 2 files changed, 463 insertions(+), 16 deletions(-) diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index d893bfd3038..8fbbcf7d7c5 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -56,6 +56,12 @@ const RemoveDuplicateSizes = (validBid) => { } }; +const ConfigureProtectedAudience = (validBid, protectedAudienceEnabled) => { + if (!protectedAudienceEnabled && validBid.ortb2Imp && validBid.ortb2Imp.ext) { + delete validBid.ortb2Imp.ext.ae; + } +} + const getRequests = (conf, validBidRequests, bidderRequest) => { const {bids, bidderRequestId, bidderCode, ...bidderRequestData} = bidderRequest; const invalidBidsCount = bidderRequest.bids.length - validBidRequests.length; @@ -65,6 +71,7 @@ const getRequests = (conf, validBidRequests, bidderRequest) => { const currSiteId = validBid.params.siteId; addBidFloorInfo(validBid); RemoveDuplicateSizes(validBid); + ConfigureProtectedAudience(validBid, conf.protectedAudienceEnabled); requestBySiteId[currSiteId] = requestBySiteId[currSiteId] || []; requestBySiteId[currSiteId].push(validBid); }); @@ -206,21 +213,49 @@ export const adapter = { endPoint = deepAccess(validBidRequests[0], 'params.endpoint') || endPoint; } - const url = endPoint; - const method = 'POST'; - const options = {contentType: 'application/json'}; - return getRequests({url, method, options}, validBidRequests, bidderRequest); + return getRequests({ + 'url': endPoint, + 'method': 'POST', + 'options': { + 'contentType': 'application/json' + }, + 'protectedAudienceEnabled': bidderRequest.fledgeEnabled + }, validBidRequests, bidderRequest); }, - interpretResponse: function (serverResponse = {}) { + interpretResponse: function (serverResponse) { + if (!(serverResponse && serverResponse.body && (serverResponse.body.auctionConfigs || serverResponse.body.bids))) { + return []; + } + const serverResponseBody = serverResponse.body; + let bids = []; + let fledgeAuctionConfigs = null; + if (serverResponseBody.bids.length) { + bids = handleBidResponseByMediaType(serverResponseBody.bids); + } - const noBidsResponse = []; - const isInvalidResponse = !serverResponseBody || !serverResponseBody.bids; + if (serverResponseBody.auctionConfigs) { + let auctionConfigs = serverResponseBody.auctionConfigs; + let bidIdList = Object.keys(auctionConfigs); + if (bidIdList.length) { + bidIdList.forEach((bidId) => { + fledgeAuctionConfigs = [{ + 'bidId': bidId, + 'config': auctionConfigs[bidId] + }]; + }) + } + } - return isInvalidResponse - ? noBidsResponse - : handleBidResponseByMediaType(serverResponseBody.bids); + if (!fledgeAuctionConfigs) { + return bids; + } + + return { + bids, + fledgeAuctionConfigs + }; } }; diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index 6d1d8f9949f..f1f6517af67 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -42,9 +42,40 @@ describe('UnrulyAdapter', function () { } } - const createExchangeResponse = (...bids) => ({ - body: {bids} - }); + function createOutStreamExchangeAuctionConfig() { + return { + 'seller': 'https://nexxen.tech', + 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', + 'interestGroupBuyers': 'https://mydsp.com', + 'perBuyerSignals': { + 'https://mydsp.com': { + 'floor': 'bouttreefiddy' + } + } + } + }; + + function createExchangeResponse (bidList, auctionConfigs = null) { + let bids = []; + if (Array.isArray(bidList)) { + bids = bidList; + } else if (bidList) { + bids.push(bidList); + } + + if (!auctionConfigs) { + return { + 'body': {bids} + }; + } + + return { + 'body': { + bids, + auctionConfigs + } + } + }; const inStreamServerResponse = { 'requestId': '262594d5d1f8104', @@ -658,6 +689,227 @@ describe('UnrulyAdapter', function () { let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); expect(result[0].data).to.deep.equal(expectedResult); }); + describe('Protected Audience Support', function() { + it('should return an array with 2 items and enabled protected audience', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'fledgeEnabled': true, + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + 'ortb2Imp': { + 'ext': { + 'ae': 1 + } + } + }, + { + 'bidder': 'unruly', + 'params': { + 'siteId': 2234554, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + 'ortb2Imp': { + 'ext': { + 'ae': 1 + } + } + } + ] + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(typeof result).to.equal('object'); + expect(result.length).to.equal(2); + expect(result[0].data.bidderRequest.bids.length).to.equal(1); + expect(result[1].data.bidderRequest.bids.length).to.equal(1); + expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); + expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); + }); + it('should return an array with 2 items and enabled protected audience on only one unit', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'fledgeEnabled': true, + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + 'ortb2Imp': { + 'ext': { + 'ae': 1 + } + } + }, + { + 'bidder': 'unruly', + 'params': { + 'siteId': 2234554, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + 'ortb2Imp': { + 'ext': {} + } + } + ] + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(typeof result).to.equal('object'); + expect(result.length).to.equal(2); + expect(result[0].data.bidderRequest.bids.length).to.equal(1); + expect(result[1].data.bidderRequest.bids.length).to.equal(1); + expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.equal(1); + expect(result[1].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; + }); + it('disables configured protected audience when fledge is not availble', function () { + mockBidRequests = { + 'bidderCode': 'unruly', + 'fledgeEnabled': false, + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': [ + 'video/mp4' + ], + 'playerSize': [ + [ + 640, + 480 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'sizes': [ + [ + 640, + 480 + ] + ], + 'bidId': '27a3ee1626a5c7', + 'bidderRequestId': '12e00d17dff07b', + 'ortb2Imp': { + 'ext': { + 'ae': 1 + } + } + } + ] + }; + + let result = adapter.buildRequests(mockBidRequests.bids, mockBidRequests); + expect(typeof result).to.equal('object'); + expect(result.length).to.equal(1); + expect(result[0].data.bidderRequest.bids.length).to.equal(1); + expect(result[0].data.bidderRequest.bids[0].ortb2Imp.ext.ae).to.be.undefined; + }); + }); }); describe('interpretResponse', function () { @@ -705,7 +957,167 @@ describe('UnrulyAdapter', function () { renderer: fakeRenderer, mediaType: 'video' } - ]) + ]); + }); + + it('should return object with an array of bids and an array of auction configs when it receives a successful response from server', function () { + let bidId = '27a3ee1626a5c7' + const mockExchangeBid = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); + const mockExchangeAuctionConfig = {}; + mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); + const mockServerResponse = createExchangeResponse(mockExchangeBid, mockExchangeAuctionConfig); + const originalRequest = { + 'data': { + 'bidderRequest': { + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 480 + ], + [ + 300, + 250 + ], + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': bidId, + 'bidderRequestId': '12e00d17dff07b', + } + ] + } + } + }; + + expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ + 'bids': [ + { + 'ext': { + 'statusCode': 1, + 'renderer': { + 'id': 'unruly_inarticle', + 'config': { + 'siteId': 123456, + 'targetingUUID': 'xxx-yyy-zzz' + }, + 'url': 'https://video.unrulymedia.com/native/prebid-loader.js' + }, + 'adUnitCode': 'video1' + }, + requestId: 'mockBidId', + bidderCode: 'unruly', + cpm: 20, + width: 323, + height: 323, + vastUrl: 'https://targeting.unrulymedia.com/in_article?uuid=74544e00-d43b-4f3a-a799-69d22ce979ce&supported_mime_type=application/javascript&supported_mime_type=video/mp4&tj=%7B%22site%22%3A%7B%22lang%22%3A%22en-GB%22%2C%22ref%22%3A%22%22%2C%22page%22%3A%22https%3A%2F%2Fdemo.unrulymedia.com%2FinArticle%2Finarticle_nypost_upbeat%2Ftravel_magazines.html%22%2C%22domain%22%3A%22demo.unrulymedia.com%22%7D%2C%22user%22%3A%7B%22profile%22%3A%7B%22quantcast%22%3A%7B%22segments%22%3A%5B%7B%22id%22%3A%22D%22%7D%2C%7B%22id%22%3A%22T%22%7D%5D%7D%7D%7D%7D&video_width=618&video_height=347', + netRevenue: true, + creativeId: 'mockBidId', + ttl: 360, + 'meta': { + 'mediaType': 'video', + 'videoContext': 'outstream' + }, + currency: 'USD', + renderer: fakeRenderer, + mediaType: 'video' + } + ], + 'fledgeAuctionConfigs': [{ + 'bidId': bidId, + 'config': { + 'seller': 'https://nexxen.tech', + 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', + 'interestGroupBuyers': 'https://mydsp.com', + 'perBuyerSignals': { + 'https://mydsp.com': { + 'floor': 'bouttreefiddy' + } + } + } + }] + }); + }); + + it('should return object with an array of auction configs when it receives a successful response from server without bids', function () { + let bidId = '27a3ee1626a5c7'; + const mockExchangeAuctionConfig = {}; + mockExchangeAuctionConfig[bidId] = createOutStreamExchangeAuctionConfig(); + const mockServerResponse = createExchangeResponse(null, mockExchangeAuctionConfig); + const originalRequest = { + 'data': { + 'bidderRequest': { + 'bids': [ + { + 'bidder': 'unruly', + 'params': { + 'siteId': 233261, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 640, + 480 + ], + [ + 640, + 480 + ], + [ + 300, + 250 + ], + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'video2', + 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', + 'bidId': bidId, + 'bidderRequestId': '12e00d17dff07b', + } + ] + } + } + }; + + expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ + 'bids': [], + 'fledgeAuctionConfigs': [{ + 'bidId': bidId, + 'config': { + 'seller': 'https://nexxen.tech', + 'decisionLogicURL': 'https://nexxen.tech/padecisionlogic', + 'interestGroupBuyers': 'https://mydsp.com', + 'perBuyerSignals': { + 'https://mydsp.com': { + 'floor': 'bouttreefiddy' + } + } + } + }] + }); }); it('should initialize and set the renderer', function () { @@ -875,7 +1287,7 @@ describe('UnrulyAdapter', function () { it('should return correct response for multiple bids', function () { const outStreamServerResponse = createOutStreamExchangeBid({adUnitCode: 'video1', requestId: 'mockBidId'}); - const mockServerResponse = createExchangeResponse(outStreamServerResponse, inStreamServerResponse, bannerServerResponse); + const mockServerResponse = createExchangeResponse([outStreamServerResponse, inStreamServerResponse, bannerServerResponse]); const expectedOutStreamResponse = outStreamServerResponse; expectedOutStreamResponse.mediaType = 'video'; @@ -890,7 +1302,7 @@ describe('UnrulyAdapter', function () { it('should return only valid bids', function () { const {ad, ...bannerServerResponseNoAd} = bannerServerResponse; - const mockServerResponse = createExchangeResponse(bannerServerResponseNoAd, inStreamServerResponse); + const mockServerResponse = createExchangeResponse([bannerServerResponseNoAd, inStreamServerResponse]); const expectedInStreamResponse = inStreamServerResponse; expectedInStreamResponse.mediaType = 'video'; From a9b62f88fa3b4490429d5cefa492f86d29613652 Mon Sep 17 00:00:00 2001 From: relaido <63339139+relaido@users.noreply.github.com> Date: Wed, 29 Nov 2023 22:46:39 +0900 Subject: [PATCH 223/760] Relaido Bid Adapter : add userIdAsEids and canonical_url to request (#10762) * add relaido adapter * remove event listener * fixed UserSyncs and e.data * fix conflicts * add response placementID * add bidrequest some param --------- Co-authored-by: ishigami_shingo Co-authored-by: cmertv-sishigami Co-authored-by: t_bun Co-authored-by: n.maeura --- modules/relaidoBidAdapter.js | 5 ++++- test/spec/modules/relaidoBidAdapter_spec.js | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index 1e702d812f0..751e8fa442c 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -104,7 +104,8 @@ function buildRequests(validBidRequests, bidderRequest) { width: width, height: height, banner_sizes: getBannerSizes(bidRequest), - media_type: mediaType + media_type: mediaType, + userIdAsEids: bidRequest.userIdAsEids || {}, }); } @@ -117,6 +118,7 @@ function buildRequests(validBidRequests, bidderRequest) { uuid: getUuid(), pv: '$prebid.version$', imuid: imuid, + canonical_url: bidderRequest.refererInfo?.canonicalUrl || null, canonical_url_hash: getCanonicalUrlHash(bidderRequest.refererInfo), ref: bidderRequest.refererInfo.page }); @@ -142,6 +144,7 @@ function interpretResponse(serverResponse, bidRequest) { const playerUrl = res.playerUrl || bidRequest.player || body.playerUrl; let bidResponse = { requestId: res.bidId, + placementId: res.placementId, width: res.width, height: res.height, cpm: res.price, diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 7778e9cbf80..f0d019913e8 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -239,6 +239,7 @@ describe('RelaidoAdapter', function () { const request = data.bids[0]; expect(bidRequests.method).to.equal('POST'); expect(bidRequests.url).to.equal('https://api.relaido.jp/bid/v1/sprebid'); + expect(data.canonical_url).to.equal('https://publisher.com/home'); expect(data.canonical_url_hash).to.equal('e6092f44a0044903ae3764126eedd6187c1d9f04'); expect(data.ref).to.equal(bidderRequest.refererInfo.page); expect(data.timeout_ms).to.equal(bidderRequest.timeout); @@ -317,6 +318,23 @@ describe('RelaidoAdapter', function () { expect(data.bids).to.have.lengthOf(1); expect(data.imuid).to.equal('i.tjHcK_7fTcqnbrS_YA2vaw'); }); + + it('should get userIdAsEids', function () { + const userIdAsEids = [ + { + source: 'hogehoge.com', + uids: { + atype: 1, + id: 'hugahuga' + } + } + ] + bidRequest.userIdAsEids = userIdAsEids + const bidRequests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(bidRequests.data); + expect(data.bids[0].userIdAsEids).to.have.lengthOf(1); + expect(data.bids[0].userIdAsEids[0].source).to.equal('hogehoge.com'); + }); }); describe('spec.interpretResponse', function () { @@ -325,6 +343,7 @@ describe('RelaidoAdapter', function () { expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.placementId).to.equal(serverResponse.body.ads[0].placementId); expect(response.width).to.equal(serverRequest.data.bids[0].width); expect(response.height).to.equal(serverRequest.data.bids[0].height); expect(response.cpm).to.equal(serverResponse.body.ads[0].price); @@ -343,6 +362,7 @@ describe('RelaidoAdapter', function () { expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.placementId).to.equal(serverResponse.body.ads[0].placementId); expect(response.width).to.equal(serverRequest.data.bids[0].width); expect(response.height).to.equal(serverRequest.data.bids[0].height); expect(response.cpm).to.equal(serverResponse.body.ads[0].price); @@ -360,6 +380,7 @@ describe('RelaidoAdapter', function () { expect(bidResponses).to.have.lengthOf(1); const response = bidResponses[0]; expect(response.requestId).to.equal(serverRequest.data.bids[0].bidId); + expect(response.placementId).to.equal(serverResponseBanner.body.ads[0].placementId); expect(response.cpm).to.equal(serverResponseBanner.body.ads[0].price); expect(response.currency).to.equal(serverResponseBanner.body.ads[0].currency); expect(response.creativeId).to.equal(serverResponseBanner.body.ads[0].creativeId); From b154fc16286b336f3e55bb4b325ed4d6876b7cd2 Mon Sep 17 00:00:00 2001 From: Olivier Date: Wed, 29 Nov 2023 19:38:24 +0100 Subject: [PATCH 224/760] Adagio Bid Adapter: add GPID support (#10775) --- modules/adagioBidAdapter.js | 5 ++++ test/spec/modules/adagioBidAdapter_spec.js | 28 ++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 90f62ba4694..1f3d6040d34 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1115,6 +1115,11 @@ export const spec = { _buildVideoBidRequest(bidRequest); } + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + bidRequest.gpid = gpid; + } + storeRequestInAdagioNS(bidRequest); // Remove these fields at the very end, so we can still use them before. diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 1f734a6a7fc..7f0b31b9680 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -958,6 +958,34 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.usIfr).to.equal(false); }); }); + + describe('with GPID', function () { + const gpid = '/12345/my-gpt-tag-0'; + + it('should add preferred gpid to the request', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + bid01.ortb2Imp = { + ext: { + gpid: gpid + } + }; + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].gpid).to.exist.and.equal(gpid); + }); + + it('should add backup gpid to the request', function () { + const bid01 = new BidRequestBuilder().withParams().build(); + bid01.ortb2Imp = { + ext: { + data: { pbadslot: gpid } + } + }; + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].gpid).to.exist.and.equal(gpid); + }); + }); }); describe('interpretResponse()', function() { From 73fb8d3a160d79b2e24707d4ecbf047afb13a3ca Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 29 Nov 2023 15:00:40 -0800 Subject: [PATCH 225/760] Core: add option to turn off `Sec-Browsing-Topics` header (#10776) --- src/adapters/bidderFactory.js | 2 +- test/spec/unit/core/bidderFactory_spec.js | 112 ++++++++++++++-------- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 8628f2c60a0..44775b4e241 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -457,7 +457,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe return Object.assign(defaults, ro, { browsingTopics: ro?.hasOwnProperty('browsingTopics') && !ro.browsingTopics ? false - : isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, activityParams(MODULE_TYPE_BIDDER, spec.code)) + : (bidderSettings.get(spec.code, 'topicsHeader') ?? true) && isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, activityParams(MODULE_TYPE_BIDDER, spec.code)) }) } switch (request.method) { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index f089059b65a..9dcdb627698 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -422,7 +422,15 @@ describe('bidderFactory', () => { }); describe('browsingTopics ajax option', () => { - let transmitUfpdAllowed, bidder; + let transmitUfpdAllowed, bidder, origBS; + before(() => { + origBS = window.$$PREBID_GLOBAL$$.bidderSettings; + }) + + after(() => { + window.$$PREBID_GLOBAL$$.bidderSettings = origBS; + }); + beforeEach(() => { activityRules.isActivityAllowed.reset(); activityRules.isActivityAllowed.callsFake((activity) => activity === ACTIVITY_TRANSMIT_UFPD ? transmitUfpdAllowed : true); @@ -448,49 +456,71 @@ describe('bidderFactory', () => { }); Object.entries({ - 'allowed': true, - 'not allowed': false - }).forEach(([t, allow]) => { - it(`should be set to ${allow} when transmitUfpd is ${t}`, () => { - transmitUfpdAllowed = allow; - spec.buildRequests.returns([ - { - method: 'GET', - url: '1', - }, - { - method: 'POST', - url: '2', - data: {} - }, - { - method: 'GET', - url: '3', - options: { - browsingTopics: true - } - }, - { - method: 'POST', - url: '4', - data: {}, - options: { - browsingTopics: true + 'omitted': [undefined, true], + 'enabled': [true, true], + 'disabled': [false, false] + }).forEach(([t, [topicsHeader, enabled]]) => { + describe(`when bidderSettings.topicsHeader is ${t}`, () => { + beforeEach(() => { + window.$$PREBID_GLOBAL$$.bidderSettings = { + [CODE]: { + topicsHeader: topicsHeader } } - ]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - ['1', '2', '3', '4'].forEach(url => { - sinon.assert.calledWith( - ajaxStub, - url, - sinon.match.any, - sinon.match.any, - sinon.match({browsingTopics: allow}) - ); }); - }); - }); + + afterEach(() => { + delete window.$$PREBID_GLOBAL$$.bidderSettings[CODE]; + }); + + Object.entries({ + 'allowed': true, + 'not allowed': false + }).forEach(([t, allow]) => { + const shouldBeSet = allow && enabled; + + it(`should be set to ${shouldBeSet} when transmitUfpd is ${t}`, () => { + transmitUfpdAllowed = allow; + spec.buildRequests.returns([ + { + method: 'GET', + url: '1', + }, + { + method: 'POST', + url: '2', + data: {} + }, + { + method: 'GET', + url: '3', + options: { + browsingTopics: true + } + }, + { + method: 'POST', + url: '4', + data: {}, + options: { + browsingTopics: true + } + } + ]); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + ['1', '2', '3', '4'].forEach(url => { + sinon.assert.calledWith( + ajaxStub, + url, + sinon.match.any, + sinon.match.any, + sinon.match({browsingTopics: shouldBeSet}) + ); + }); + }); + }); + }) + }) }); it('should not add bids for each placement code if no requests are given', function () { From d70097e6671a1fa44449b22992bb8cf35d6252d8 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Thu, 30 Nov 2023 13:22:53 +0200 Subject: [PATCH 226/760] Missena Bid Adapter: add capping support (#10746) * Missena Bid Adapter: add capping support * Switch to safeJSONParse * Update tests to cover capping --- modules/missenaBidAdapter.js | 16 +++++- test/spec/modules/missenaBidAdapter_spec.js | 59 +++++++++++++++------ 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 1ab6d0c6945..a06ba1ce29c 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -3,17 +3,21 @@ import { formatQS, isFn, logInfo, + safeJSONParse, triggerPixel, } from '../src/utils.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'missena'; const ENDPOINT_URL = 'https://bid.missena.io/'; const EVENTS_DOMAIN = 'events.missena.io'; const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + /* Get Floor price information */ function getFloor(bidRequest) { if (!isFn(bidRequest.getFloor)) { @@ -22,7 +26,7 @@ function getFloor(bidRequest) { const bidFloors = bidRequest.getFloor({ currency: 'USD', - mediaType: BANNER + mediaType: BANNER, }); if (!isNaN(bidFloors.floor)) { @@ -53,6 +57,16 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + const capKey = `missena.missena.capper.remove-bubble.${validBidRequests[0]?.params.apiKey}`; + const capping = safeJSONParse(storage.getDataFromLocalStorage(capKey)); + if ( + typeof capping?.expiry === 'number' && + new Date().getTime() < capping?.expiry + ) { + logInfo('Missena - Capped'); + return []; + } + return validBidRequests.map((bidRequest) => { const payload = { adunit: bidRequest.adUnitCode, diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index e874aabbc70..15f9eff89e7 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -1,13 +1,15 @@ import { expect } from 'chai'; -import { spec, _getPlatform } from 'modules/missenaBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec, storage } from 'modules/missenaBidAdapter.js'; import { BANNER } from '../../../src/mediaTypes.js'; describe('Missena Adapter', function () { - const adapter = newBidder(spec); + $$PREBID_GLOBAL$$.bidderSettings = { + missena: { + storageAllowed: true, + }, + }; const bidId = 'abc'; - const bid = { bidder: 'missena', bidId: bidId, @@ -40,7 +42,20 @@ describe('Missena Adapter', function () { formats: ['sticky-banner'], }, }; + const consentString = 'AAAAAAAAA=='; + + const bidderRequest = { + gdprConsent: { + consentString: consentString, + gdprApplies: true, + }, + refererInfo: { + topmostLocation: 'https://referer', + canonicalUrl: 'https://canonical', + }, + }; + const bids = [bid, bidWithoutFloor]; describe('codes', function () { it('should return a bidder code of missena', function () { expect(spec.code).to.equal('missena'); @@ -66,20 +81,12 @@ describe('Missena Adapter', function () { }); describe('buildRequests', function () { - const consentString = 'AAAAAAAAA=='; - - const bidderRequest = { - gdprConsent: { - consentString: consentString, - gdprApplies: true, - }, - refererInfo: { - topmostLocation: 'https://referer', - canonicalUrl: 'https://canonical', - }, - }; + let getDataFromLocalStorageStub = sinon.stub( + storage, + 'getDataFromLocalStorage', + ); - const requests = spec.buildRequests([bid, bidWithoutFloor], bidderRequest); + const requests = spec.buildRequests(bids, bidderRequest); const request = requests[0]; const payload = JSON.parse(request.data); const payloadNoFloor = JSON.parse(requests[1].data); @@ -121,6 +128,24 @@ describe('Missena Adapter', function () { expect(payloadNoFloor.floor).to.equal(undefined); expect(payloadNoFloor.floor_currency).to.equal(undefined); }); + + getDataFromLocalStorageStub.restore(); + getDataFromLocalStorageStub = sinon.stub( + storage, + 'getDataFromLocalStorage', + ); + const localStorageData = { + [`missena.missena.capper.remove-bubble.${bid.params.apiKey}`]: + JSON.stringify({ + expiry: new Date().getTime() + 600_000, // 10 min into the future + }), + }; + getDataFromLocalStorageStub.callsFake((key) => localStorageData[key]); + const cappedRequests = spec.buildRequests(bids, bidderRequest); + + it('should not participate if capped', function () { + expect(cappedRequests.length).to.equal(0); + }); }); describe('interpretResponse', function () { From 7974ebc3ce6dd44bf64ae9fd6eaaab903b82f78c Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 30 Nov 2023 22:37:26 +1100 Subject: [PATCH 227/760] Adnuntius Bid Adaptor: Handle voidAuIds from server response (#10646) - invalid ad units are put into a voidAuIds array from the ad server. - this stores those ad units into local storage and prevents calling to those ad units. - also handles any values from metaData from the adserver response. --- modules/adnuntiusBidAdapter.js | 222 ++++++++++++++---- test/spec/modules/adnuntiusBidAdapter_spec.js | 119 +++++++++- 2 files changed, 282 insertions(+), 59 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index a2b695e55e0..b2653f16daf 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -15,39 +15,146 @@ const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; +const META_DATA_KEY = 'adn.metaData'; -const checkSegment = function (segment) { - if (isStr(segment)) return segment; - if (segment.id) return segment.id -} +export const misc = { + getUnixTimestamp: function(addDays, asMinutes) { + const multiplication = addDays / (asMinutes ? 1440 : 1); + return Date.now() + (addDays && addDays > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); + } +}; + +const storageTool = (function() { + const storage = getStorageManager({bidderCode: BIDDER_CODE}); + let metaInternal; + + const getMetaInternal = function() { + if (!storage.localStorageIsEnabled()) { + return {}; + } + + let parsedJson; + try { + parsedJson = JSON.parse(storage.getDataFromLocalStorage(META_DATA_KEY)); + } catch (e) { + return {}; + } + + let filteredEntries = parsedJson ? parsedJson.filter((datum) => { + if (datum.key === 'voidAuIds' && Array.isArray(datum.value)) { + return true; + } + return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp(); + }) : []; + const voidAuIdsEntry = filteredEntries.find(entry => entry.key === 'voidAuIds'); + if (voidAuIdsEntry) { + const now = misc.getUnixTimestamp(); + voidAuIdsEntry.value = voidAuIdsEntry.value.filter(voidAuId => voidAuId.auId && voidAuId.exp > now); + if (!voidAuIdsEntry.value.length) { + filteredEntries = filteredEntries.filter(entry => entry.key !== 'voidAuIds'); + } + } + return filteredEntries; + }; + + const setMetaInternal = function(apiResponse) { + if (!storage.localStorageIsEnabled()) { + return; + } + + const updateVoidAuIds = function(currentVoidAuIds, auIdsAsString) { + const newAuIds = auIdsAsString ? auIdsAsString.split(';') : []; + const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => { + return newAuIds.indexOf(auIdObj.value) < -1; + }) || []; + const oneDayFromNow = misc.getUnixTimestamp(1); + const apiIdsArray = newAuIds.map(auId => { + return {exp: oneDayFromNow, auId: auId}; + }) || []; + return notNewExistingAuIds.concat(apiIdsArray) || []; + } -const getSegmentsFromOrtb = function (ortb2) { - const userData = deepAccess(ortb2, 'user.data'); - let segments = []; - if (userData) { - userData.forEach(userdat => { - if (userdat.segment) { - segments.push(...userdat.segment.filter(checkSegment).map(checkSegment)); + const metaAsObj = getMetaInternal().reduce((a, entry) => ({...a, [entry.key]: {value: entry.value, exp: entry.exp}}), {}); + for (const key in apiResponse) { + if (key !== 'voidAuIds') { + metaAsObj[key] = { + value: apiResponse[key], + exp: misc.getUnixTimestamp(100) + } + } + } + const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiResponse.voidAuIds || []); + if (currentAuIds.length > 0) { + metaAsObj.voidAuIds = {value: currentAuIds}; + } + const metaDataForSaving = Object.entries(metaAsObj).map((entrySet) => { + if (entrySet[0] === 'voidAuIds') { + return { + key: entrySet[0], + value: entrySet[1].value + }; + } + return { + key: entrySet[0], + value: entrySet[1].value, + exp: entrySet[1].exp } }); + storage.setDataInLocalStorage(META_DATA_KEY, JSON.stringify(metaDataForSaving)); + }; + + const getUsi = function(meta, ortb2) { + let usi = (meta && meta.usi) ? meta.usi : false; + if (ortb2 && ortb2.user && ortb2.user.id) { + usi = ortb2.user.id + } + return usi; } - return segments -} -const handleMeta = function () { - const storage = getStorageManager({ bidderCode: BIDDER_CODE }) - let adnMeta = null - if (storage.localStorageIsEnabled()) { - adnMeta = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')) + const getSegmentsFromOrtb = function (ortb2) { + const userData = deepAccess(ortb2, 'user.data'); + let segments = []; + if (userData) { + userData.forEach(userdat => { + if (userdat.segment) { + segments.push(...userdat.segment.map((segment) => { + if (isStr(segment)) return segment; + if (isStr(segment.id)) return segment.id; + }).filter((seg) => !!seg)); + } + }); + } + return segments } - return (adnMeta !== null) ? adnMeta.reduce((acc, cur) => { return { ...acc, [cur.key]: cur.value } }, {}) : {} -} -const getUsi = function (meta, ortb2, bidderRequest) { - let usi = (meta !== null && meta.usi) ? meta.usi : false; - if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id } - return usi -} + return { + refreshStorage: function(bidderRequest) { + const ortb2 = bidderRequest.ortb2 || {}; + metaInternal = getMetaInternal().reduce((a, entry) => ({...a, [entry.key]: entry.value}), {}); + metaInternal.usi = getUsi(metaInternal, ortb2); + if (!metaInternal.usi) { + delete metaInternal.usi; + } + if (metaInternal.voidAuIds) { + metaInternal.voidAuIdsArray = metaInternal.voidAuIds.map((voidAuId) => { + return voidAuId.auId; + }); + } + metaInternal.segments = getSegmentsFromOrtb(ortb2); + }, + saveToStorage: function(serverData) { + setMetaInternal(serverData); + }, + getUrlRelatedData: function() { + const {segments, usi, voidAuIdsArray} = metaInternal; + return {segments, usi, voidAuIdsArray}; + }, + getPayloadRelatedData: function() { + const {segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData} = metaInternal; + return payloadRelatedData; + } + }; +})(); const validateBidType = function(bidTypeOption) { return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; @@ -67,34 +174,34 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - const networks = {}; - const bidRequests = {}; - const requests = []; - const request = []; - const ortb2 = bidderRequest.ortb2 || {}; - const bidderConfig = config.getConfig(); + const queryParamsAndValues = []; + queryParamsAndValues.push('tzo=' + new Date().getTimezoneOffset()) + queryParamsAndValues.push('format=json') - const adnMeta = handleMeta() - const usi = getUsi(adnMeta, ortb2, bidderRequest) - const segments = getSegmentsFromOrtb(ortb2); - const tzo = new Date().getTimezoneOffset(); const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); + if (gdprApplies !== undefined) queryParamsAndValues.push('consentString=' + consentString); - request.push('tzo=' + tzo) - request.push('format=json') + storageTool.refreshStorage(bidderRequest); - if (gdprApplies !== undefined) request.push('consentString=' + consentString); - if (segments.length > 0) request.push('segments=' + segments.join(',')); - if (usi) request.push('userId=' + usi); - if (bidderConfig.useCookie === false) request.push('noCookies=true'); - if (bidderConfig.maxDeals > 0) request.push('ds=' + Math.min(bidderConfig.maxDeals, MAXIMUM_DEALS_LIMIT)); + const urlRelatedMetaData = storageTool.getUrlRelatedData(); + if (urlRelatedMetaData.segments.length > 0) queryParamsAndValues.push('segments=' + urlRelatedMetaData.segments.join(',')); + if (urlRelatedMetaData.usi) queryParamsAndValues.push('userId=' + urlRelatedMetaData.usi); + + const bidderConfig = config.getConfig(); + if (bidderConfig.useCookie === false) queryParamsAndValues.push('noCookies=true'); + if (bidderConfig.maxDeals > 0) queryParamsAndValues.push('ds=' + Math.min(bidderConfig.maxDeals, MAXIMUM_DEALS_LIMIT)); + + const bidRequests = {}; + const networks = {}; for (let i = 0; i < validBidRequests.length; i++) { - const bid = validBidRequests[i] - let network = bid.params.network || 'network'; - const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); - const targeting = bid.params.targeting || {}; + const bid = validBidRequests[i]; + if ((urlRelatedMetaData.voidAuIdsArray && (urlRelatedMetaData.voidAuIdsArray.indexOf(bid.params.auId) > -1 || urlRelatedMetaData.voidAuIdsArray.indexOf(bid.params.auId.padStart(16, '0')) > -1))) { + // This auId is void. Do NOT waste time and energy sending a request to the server + continue; + } + let network = bid.params.network || 'network'; if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') { network += '_video' } @@ -105,21 +212,31 @@ export const spec = { networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; - if (adnMeta) networks[network].metaData = adnMeta; - const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.bidId, maxDeals: maxDeals } + + const payloadRelatedData = storageTool.getPayloadRelatedData(); + if (Object.keys(payloadRelatedData).length > 0) { + networks[network].metaData = payloadRelatedData; + } + + const targeting = bid.params.targeting || {}; + const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; + const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); + if (maxDeals > 0) { + adUnit.maxDeals = maxDeals; + } if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes networks[network].adUnits.push(adUnit); } + const requests = []; const networkKeys = Object.keys(networks) for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; - const networkRequest = [...request] - if (network.indexOf('_video') > -1) { networkRequest.push('tt=' + DEFAULT_VAST_VERSION) } + if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) } const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL requests.push({ method: 'POST', - url: requestURL + '?' + networkRequest.join('&'), + url: requestURL + '?' + queryParamsAndValues.join('&'), data: JSON.stringify(networks[network]), bid: bidRequests[network] }); @@ -129,6 +246,9 @@ export const spec = { }, interpretResponse: function (serverResponse, bidRequest) { + if (serverResponse.body.metaData) { + storageTool.saveToStorage(serverResponse.body.metaData); + } const adUnits = serverResponse.body.adUnits; let validatedBidType = validateBidType(config.getConfig().bidType); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 6a77c9205ca..8fa719f7af6 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -10,9 +10,10 @@ import {getGlobal} from '../../../src/prebidGlobal'; describe('adnuntiusBidAdapter', function() { const URL = 'https://ads.adnuntius.delivery/i?tzo='; const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; - const GVLID = 855; const usi = utils.generateUUID() - const meta = [{key: 'usi', value: usi}] + + const meta = [{key: 'valueless'}, {value: 'keyless'}, {key: 'voidAuIds'}, {key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp()}, {exp: misc.getUnixTimestamp(1)}]}, {key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1)}, {key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp()}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}, {key: 'usi', value: usi, exp: misc.getUnixTimestamp(100)}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}] + let storage; before(() => { getGlobal().bidderSettings = { @@ -20,8 +21,11 @@ describe('adnuntiusBidAdapter', function() { storageAllowed: true } }; - const storage = getStorageManager({bidderCode: 'adnuntius'}) - storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)) + storage = getStorageManager({bidderCode: 'adnuntius'}); + }); + + beforeEach(() => { + storage.setDataInLocalStorage('adn.metaData', JSON.stringify(meta)); }); after(() => { @@ -47,6 +51,7 @@ describe('adnuntiusBidAdapter', function() { bidder: 'adnuntius', params: { auId: '000000000008b6bc', + targetId: '123', network: 'adnuntius', maxDeals: 1 }, @@ -459,7 +464,78 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"adn-000000000008b6bc","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","maxDeals":0,"dimensions":[[1640,1480],[1600,1400]]}],"metaData":{"usi":"' + usi + '"}}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"metaData":{"valid":"also-valid"}}'); + }); + + it('Test requests with no local storage', function() { + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{}])); + const request = spec.buildRequests(bidderRequests, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL_BASE); + expect(request[0]).to.have.property('data'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); + + localStorage.removeItem('adn.metaData'); + const request2 = spec.buildRequests(bidderRequests, {}); + expect(request2.length).to.equal(1); + expect(request2[0]).to.have.property('url'); + expect(request2[0].url).to.equal(ENDPOINT_URL_BASE); + }); + + it('Test request changes for voided au ids', function() { + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp(1)}, {auId: '0000000000000023', exp: misc.getUnixTimestamp(1)}]}])); + const bRequests = bidderRequests.concat([{ + bidId: 'adn-11118b6bc', + bidder: 'adnuntius', + params: { + auId: '11118b6bc', + network: 'adnuntius', + }, + mediaTypes: { + banner: { + sizes: [[1640, 1480], [1600, 1400]], + } + }, + }]); + bRequests.push({ + bidId: 'adn-23', + bidder: 'adnuntius', + params: { + auId: '23', + network: 'adnuntius', + }, + mediaTypes: { + banner: { + sizes: [[1640, 1480], [1600, 1400]], + } + }, + }); + bRequests.push({ + bidId: 'adn-13', + bidder: 'adnuntius', + params: { + auId: '13', + network: 'adnuntius', + }, + mediaTypes: { + banner: { + sizes: [[164, 140], [10, 1400]], + } + }, + }); + const request = spec.buildRequests(bRequests, {}); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL_BASE); + expect(request[0]).to.have.property('data'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]},{"auId":"13","targetId":"adn-13","dimensions":[[164,140],[10,1400]]}]}'); }); it('Test Video requests', function() { @@ -477,7 +553,7 @@ describe('adnuntiusBidAdapter', function() { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}] + segment: [{id: 'segment1'}, {id: 'segment2'}, {invalidSegment: 'invalid'}, {id: 123}, {id: ['3332']}] }, { name: 'other', @@ -660,7 +736,7 @@ describe('adnuntiusBidAdapter', function() { expect(bidderRequests[0].params.maxDeals).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(bidderRequests[0].params.maxDeals); expect(bidderRequests[1].params).to.not.have.property('maxBids'); - expect(data.adUnits[1].maxDeals).to.equal(0); + expect(data.adUnits[1].maxDeals).to.equal(undefined); }); it('Should allow a maximum of 5 deals.', function() { config.setBidderConfig({ @@ -706,7 +782,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('data'); const data = JSON.parse(request[0].data); expect(data.adUnits.length).to.equal(1); - expect(data.adUnits[0].maxDeals).to.equal(0); + expect(data.adUnits[0].maxDeals).to.equal(undefined); }); it('Should set max deals using bidder config.', function() { config.setBidderConfig({ @@ -794,6 +870,33 @@ describe('adnuntiusBidAdapter', function() { expect(interpretedResponse[1].ttl).to.equal(360); expect(interpretedResponse[1].dealId).to.equal('not-in-deal-array-here'); expect(interpretedResponse[1].dealCount).to.equal(0); + + const results = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')); + const usiEntry = results.find(entry => entry.key === 'usi'); + expect(usiEntry.key).to.equal('usi'); + expect(usiEntry.value).to.equal('from-api-server dude'); + expect(usiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + + const voidAuIdsEntry = results.find(entry => entry.key === 'voidAuIds'); + expect(voidAuIdsEntry.key).to.equal('voidAuIds'); + expect(voidAuIdsEntry.exp).to.equal(undefined); + expect(voidAuIdsEntry.value[0].auId).to.equal('00000000000abcde'); + expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(misc.getUnixTimestamp()); + expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(voidAuIdsEntry.value[1].auId).to.equal('00000000000fffff'); + expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(misc.getUnixTimestamp()); + expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + + const validEntry = results.find(entry => entry.key === 'valid'); + expect(validEntry.key).to.equal('valid'); + expect(validEntry.value).to.equal('also-valid'); + expect(validEntry.exp).to.be.greaterThan(misc.getUnixTimestamp()); + expect(validEntry.exp).to.be.lessThan(misc.getUnixTimestamp(2)); + + const randomApiEntry = results.find(entry => entry.key === 'randomApiKey'); + expect(randomApiEntry.key).to.equal('randomApiKey'); + expect(randomApiEntry.value).to.equal('randomApiValue'); + expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); }); it('should not process valid response when passed alt bidder that is an adndeal', function() { From 32748b48a5943a4b607b3e18c66b7bf4964a4347 Mon Sep 17 00:00:00 2001 From: TPMN Admin Date: Thu, 30 Nov 2023 22:19:17 +0900 Subject: [PATCH 228/760] TPMN Bid Adapter: remove OptionsObject (#10777) * TPMN Support Video * add video param plcmt * update ortb bcat,badv,bapp * instream video is sound on default, outstream video sound off default * remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS * fixed incorrect placement default format & update example * update test case (Your tests failed on CircleCI) * Revert "remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS" This reverts commit 2699115a76ed891ec7f911ad924206b399790ad9. * Remove unnecessary code to meet guidelines. * Remove unnecessary code to meet guidelines. * fix Build fail * delete logging code * Remove OptionsObject(Credentials information needs to be sent.) --------- Co-authored-by: changjun Co-authored-by: yeongjaeju --- modules/tpmnBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 61f8a4c98af..89951a99f12 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -108,8 +108,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, - data: rtbData, - options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + data: rtbData } } From 7dd09d8bb53fb8a0c67af04f705168351838429c Mon Sep 17 00:00:00 2001 From: Chris Southern <79725079+southern-growthcode@users.noreply.github.com> Date: Thu, 30 Nov 2023 08:48:52 -0500 Subject: [PATCH 229/760] GC-157 Boune request when the PID is missing (#10774) --- modules/growthCodeRtdProvider.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/growthCodeRtdProvider.js b/modules/growthCodeRtdProvider.js index ef5c7906ad7..e7dce81f7d0 100644 --- a/modules/growthCodeRtdProvider.js +++ b/modules/growthCodeRtdProvider.js @@ -60,7 +60,11 @@ function init(config, userConsent) { items = tryParse(storage.getDataFromLocalStorage(RTD_CACHE_KEY, null)); - return callServer(configParams, items, expiresAt, userConsent); + if (configParams.pid === undefined) { + return true; // Die gracefully + } else { + return callServer(configParams, items, expiresAt, userConsent); + } } function callServer(configParams, items, expiresAt, userConsent) { // Expire Cache From 9c597a3a226b7ab258cf97b5e81f9079cf8ab1ce Mon Sep 17 00:00:00 2001 From: adxpremium <55161519+adxpremium@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:56:16 +0100 Subject: [PATCH 230/760] DynamicAdBoost Module : add to adloader list (#10752) * Add dynamicAdBoost Module * Add dynamicAdBoost spec file * dynamicAdBoost: fix ES Lint * dynamicAdBoost: ESLint fix * dynamicAdBoost: ESLint fix * dynamicAdBoost: fix ESLint * dynamicAdBoost: removed unused log warn * dynamicAdBoost: Fixed tests * dynamicAdBoost: export init() function * Delete dynamicAdBoost.md * Delete dynamicAdBoost.js * Delete dynamicAdBoost_spec.js * Rename dynamicAdBoost to dynamicAdBoostRtdProvider dynamicAdBoost is now submodule of the rtd * dynamicAdBoost tests * Added disconnect() and IO support check * dynamicAdBoostRtdProvider: markViewad added * dynamicAdBoostRtdProvider: markViewed added * dynamicAdBoostRtdProvider.js: observer fix * dynamicAdBoostRtdProvider.js: observer fix * dynamicAdBoostRtdProvider.js: ESLint fix * dynamicAdBoostRtdProvider.js: var to let * dynamicAdBoostRtdProvider.js: moved timers * dynamicAdBoostRtdProvider_spec.js: using clock * dynamicAdBoostRtdProvider_spec.js: fakeTimer * Update adloader.js Adding dynamicAdBoost module to approved list --- src/adloader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/adloader.js b/src/adloader.js index cd4ad771774..d1dca9627d8 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -28,7 +28,8 @@ const _approvedLoadExternalJSList = [ 'a1Media', 'geoedge', 'mediafilter', - 'qortex' + 'qortex', + 'dynamicAdBoost' ] /** From a8fedc6c6ed23974e6c9185856c5d9206941992f Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 30 Nov 2023 10:03:13 -0800 Subject: [PATCH 231/760] Generic analytics: fix spurious warning messages (#10739) --- modules/genericAnalyticsAdapter.js | 12 ++++++------ src/events.js | 4 +++- test/spec/modules/genericAnalyticsAdapter_spec.js | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js index b52cb7e5464..7f721863912 100644 --- a/modules/genericAnalyticsAdapter.js +++ b/modules/genericAnalyticsAdapter.js @@ -1,6 +1,6 @@ import AnalyticsAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {prefixLog, isPlainObject} from '../src/utils.js'; -import * as CONSTANTS from '../src/constants.json'; +import {has as hasEvent} from '../src/events.js'; import adapterManager from '../src/adapterManager.js'; import {ajaxBuilder} from '../src/ajax.js'; @@ -48,12 +48,12 @@ export function GenericAnalytics() { return false; } for (const [event, handler] of Object.entries(options.events)) { - if (!CONSTANTS.EVENTS.hasOwnProperty(event)) { + if (!hasEvent(event)) { logWarn(`options.events.${event} does not match any known Prebid event`); - if (typeof handler !== 'function') { - logError(`options.events.${event} must be a function`); - return false; - } + } + if (typeof handler !== 'function') { + logError(`options.events.${event} must be a function`); + return false; } } } diff --git a/src/events.js b/src/events.js index 7a1e25e0e49..6eb7ce49f11 100644 --- a/src/events.js +++ b/src/events.js @@ -89,6 +89,8 @@ const _public = (function () { return allEvents.includes(event) } + _public.has = _checkAvailableEvent; + _public.on = function (eventString, handler, id) { // check whether available event or not if (_checkAvailableEvent(eventString)) { @@ -163,7 +165,7 @@ const _public = (function () { utils._setEventEmitter(_public.emit.bind(_public)); -export const {on, off, get, getEvents, emit, addEvents} = _public; +export const {on, off, get, getEvents, emit, addEvents, has} = _public; export function clearEvents() { eventsFired.clear(); diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index a5a6074c425..79874f5d756 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -75,7 +75,7 @@ describe('Generic analytics', () => { options: { url: 'mock', events: { - bidResponse: null + mockEvent: null } } }); From 520278447a0f7dfe0ffc720ea289073313144024 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 Nov 2023 19:02:24 +0000 Subject: [PATCH 232/760] Prebid 8.26.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2563c9435f7..1fbf6ef9aa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.26.0-pre", + "version": "8.26.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 8aef0e70e8c..9470d5cf8c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.26.0-pre", + "version": "8.26.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ec42b130e89e25318583d23701d0a4d56f1c45bc Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 30 Nov 2023 19:02:24 +0000 Subject: [PATCH 233/760] Increment version to 8.27.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fbf6ef9aa2..e08d24d42d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.26.0", + "version": "8.27.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9470d5cf8c5..a7dbe2f6905 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.26.0", + "version": "8.27.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2de3f8782c930873c9709aa72bfec5892f8c51b4 Mon Sep 17 00:00:00 2001 From: Mike Lei Date: Fri, 1 Dec 2023 05:01:15 -0800 Subject: [PATCH 234/760] Flipp Bid Adapter: update constant NETWORK_ID (#10781) * Flipp Bid Adapter: initial release * Added flippBidAdapter * OFF-372 Support DTX/Hero in flippBidAdapter (#2) * support creativeType * OFF-422 flippBidAdapter handle AdTypes --------- Co-authored-by: Jairo Panduro * OFF-465 Add getUserKey logic to prebid.js adapter (#3) * Support cookie sync and uid * address pr feedback * remove redundant check * OFF-500 Support "startCompact" param for Prebid.JS #4 * set startCompact default value (#5) * fix docs * use client bidding endpoint * update unit testing endpoint * OFF-876 [Prebid Adapter] Check userKey for empty string (#6) * add more checks to userKey * update document * add uuid format statement * modify docs * fix network id --------- Co-authored-by: Jairo Panduro --- modules/flippBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index 480e414992d..ea5e71ad81d 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -3,7 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -const NETWORK_ID = 11090; +const NETWORK_ID = 10922; const AD_TYPES = [4309, 641]; const DTX_TYPES = [5061]; const TARGET_NAME = 'inline'; From 16133c5192c78e4e63975fcfc62a681282bacaf6 Mon Sep 17 00:00:00 2001 From: mamatic <52153441+mamatic@users.noreply.github.com> Date: Fri, 1 Dec 2023 16:11:53 +0100 Subject: [PATCH 235/760] identityLinkIdSystem - liveramp - bug fix setting tcf consent string in 3p call (#10778) --- modules/identityLinkIdSystem.js | 3 +-- test/spec/modules/identityLinkIdSystem_spec.js | 18 ------------------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index a57f8d22753..d70b5680b69 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -54,7 +54,6 @@ export const identityLinkSubmodule = { } const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? consentData.consentString : ''; - const tcfPolicyV2 = utils.deepAccess(consentData, 'vendorData.tcfPolicyVersion') === 2; // use protocol relative urls for http or https if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { utils.logInfo('identityLink: Consent string is required to call envelope API.'); @@ -64,7 +63,7 @@ export const identityLinkSubmodule = { const gppString = gppData && gppData.gppString ? gppData.gppString : false; const gppSectionId = gppData && gppData.gppString && gppData.applicableSections.length > 0 && gppData.applicableSections[0] !== -1 ? gppData.applicableSections[0] : false; const hasGpp = gppString && gppSectionId; - const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}${hasGpp ? '&gpp=' + gppString + '&gpp_sid=' + gppSectionId : ''}`; + const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? '&ct=4&cv=' + gdprConsentString : ''}${hasGpp ? '&gpp=' + gppString + '&gpp_sid=' + gppSectionId : ''}`; let resp; resp = function (callback) { // Check ats during callback so it has a chance to initialise. diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index f1c2773de07..66d5a3edd00 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -75,24 +75,6 @@ describe('IdentityLinkId tests', function () { expect(submoduleCallback).to.be.undefined; }); - it('should call the LiveRamp envelope endpoint with IAB consent string v1', function () { - let callBackSpy = sinon.spy(); - let consentData = { - gdprApplies: true, - consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' - }; - let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=1&cv=BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g'); - request.respond( - 200, - responseHeader, - JSON.stringify({}) - ); - expect(callBackSpy.calledOnce).to.be.true; - }); - it('should call the LiveRamp envelope endpoint with IAB consent string v2', function () { let callBackSpy = sinon.spy(); let consentData = { From faec1bbd6c22bf226515759555d7a9c1aa9f8f30 Mon Sep 17 00:00:00 2001 From: hybrid-ai <58724131+hybrid-ai@users.noreply.github.com> Date: Fri, 1 Dec 2023 20:45:16 +0300 Subject: [PATCH 236/760] Add GVLID to vox adapter (#10784) --- modules/voxBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 34bd46ccb98..431d0887334 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -12,6 +12,7 @@ const BIDDER_CODE = 'vox'; const SSP_ENDPOINT = 'https://ssp.hybrid.ai/auction/prebid'; const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const TTL = 60; +const GVLID = 206; function buildBidRequests(validBidRequests) { return _map(validBidRequests, function(bid) { @@ -183,6 +184,7 @@ function wrapBanner(bid, bidData) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], /** From fe2f35e00e131045bb130006d476d28f4ebd01e7 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Fri, 1 Dec 2023 18:59:16 +0100 Subject: [PATCH 237/760] LiveIntent UserId module: Add support for sovrn id (#10783) * Add EID translation for sovrn * Review comments: fix test --- modules/liveIntentIdSystem.js | 16 +++++++++ test/spec/modules/eids_spec.js | 33 +++++++++++++++++++ .../modules/liveIntentIdMinimalSystem_spec.js | 5 +++ test/spec/modules/liveIntentIdSystem_spec.js | 5 +++ 4 files changed, 59 insertions(+) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index cc43f6f6f84..bb56355de31 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -223,6 +223,10 @@ export const liveIntentIdSubmodule = { result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.sovrn) { + result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + } + return result } @@ -348,6 +352,18 @@ export const liveIntentIdSubmodule = { return data.ext; } } + }, + 'sovrn': { + source: 'liveintent.sovrn.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } } } }; diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index f2ccdcb3024..fa271b5114e 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -238,6 +238,39 @@ describe('eids array generation for known sub-modules', function() { }); }); + it('sovrn', function() { + const userId = { + sovrn: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('sovrn with ext', function() { + const userId = { + sovrn: {'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: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('magnite', function() { const userId = { magnite: {'id': 'sample_id'} diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index ed78d8f6e40..0ada7adc594 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -261,6 +261,11 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a sovrn id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a magnite id to a seperate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 9f57a5aa405..506c5b312cc 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -388,6 +388,11 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a sovrn id to a seperate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + it('should decode a magnite id to a seperate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); From 4f057b192fffdbfba369c84fe00048163ac66edc Mon Sep 17 00:00:00 2001 From: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:26:36 -0800 Subject: [PATCH 238/760] Unruly Bid Adapter : include Prebid.js version in request (#10773) * Unruly Protected Audience Support * Unruly Conditional Protected Audience Support * Unruly map bidIds to AuctionConfigs * Unruly: Update Protected Audience origin and descision logic URL. * Unruly: Fix lint error * Unruly: report Prebid.js version * Unruly: Fix tests * Unruly: Remove Extra Comma * Unruly: Switch to strings to see if tests pass on Prebid.js --------- Co-authored-by: Brian Haines --- modules/unrulyBidAdapter.js | 9 ++++++++- test/spec/modules/unrulyBidAdapter_spec.js | 11 +++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index 8fbbcf7d7c5..b825003f36f 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -80,7 +80,14 @@ const getRequests = (conf, validBidRequests, bidderRequest) => { Object.keys(requestBySiteId).forEach((key) => { let data = { - bidderRequest: Object.assign({}, {bids: requestBySiteId[key], invalidBidsCount, ...bidderRequestData}) + bidderRequest: Object.assign({}, + { + bids: requestBySiteId[key], + invalidBidsCount, + prebidVersion: '$prebid.version$', + ...bidderRequestData + } + ) }; request.push(Object.assign({}, {data, ...conf})); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index f1f6517af67..abf1a54787d 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -517,7 +517,8 @@ describe('UnrulyAdapter', function () { 'bidderRequestId': '12e00d17dff07b' } ], - 'invalidBidsCount': 0 + 'invalidBidsCount': 0, + 'prebidVersion': '$prebid.version$' } }; @@ -591,7 +592,8 @@ describe('UnrulyAdapter', function () { 'bidderRequestId': '12e00d17dff07b', } ], - 'invalidBidsCount': 0 + 'invalidBidsCount': 0, + 'prebidVersion': '$prebid.version$' } }; @@ -682,7 +684,8 @@ describe('UnrulyAdapter', function () { 'bidderRequestId': '12e00d17dff07b', } ], - 'invalidBidsCount': 0 + 'invalidBidsCount': 0, + 'prebidVersion': '$prebid.version$' } }; @@ -1095,7 +1098,7 @@ describe('UnrulyAdapter', function () { 'adUnitCode': 'video2', 'transactionId': 'a89619e3-137d-4cc5-9ed4-58a0b2a0bbc2', 'bidId': bidId, - 'bidderRequestId': '12e00d17dff07b', + 'bidderRequestId': '12e00d17dff07b' } ] } From cbf1e1580727a311dbd953566ed17cca7cf4f4e0 Mon Sep 17 00:00:00 2001 From: xmgiddev <133856186+xmgiddev@users.noreply.github.com> Date: Sat, 2 Dec 2023 14:49:55 +0200 Subject: [PATCH 239/760] MgidX Bid Adapter: refactor request.gdpr object generating (#10782) * new adapter - MgidX * add new required param host * Revert "add new required param host" This reverts commit 3bafe782a492d5d4b35476f8117260b08a237f2f. * refactoring generating request.gdpr object * fix test --------- Co-authored-by: Evgeny Nagorny Co-authored-by: xmgiddev <> --- modules/mgidXBidAdapter.js | 7 ++++++- test/spec/modules/mgidXBidAdapter_spec.js | 13 +++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 5789f0d8b95..7e084977cf1 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -166,10 +166,15 @@ export const spec = { placements, coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, tmax: config.getConfig('bidderTimeout') }; + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 14619e9c0e1..978ca3de036 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -76,7 +76,10 @@ describe('MGIDXBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { referer: 'https://test.com' } @@ -131,7 +134,7 @@ describe('MGIDXBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -172,8 +175,10 @@ describe('MGIDXBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); From 7be0a10bf24d5aba4821c9b52bf6300ab4f3dd70 Mon Sep 17 00:00:00 2001 From: Malkov Mikhail Date: Mon, 4 Dec 2023 11:24:11 +0300 Subject: [PATCH 240/760] NextMillennium Bid Adapter: support for GPP consent string, `site.pagecat`, `site.content.cat` and `site.content.language` (#10761) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code --- modules/nextMillenniumBidAdapter.js | 245 +++++---- .../modules/nextMillenniumBidAdapter_spec.js | 510 +++++++++++++++--- 2 files changed, 564 insertions(+), 191 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 0cbe954175c..26d417b9baf 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,7 +1,9 @@ import { _each, createTrackPixelHtml, - deepAccess, getBidIdParameter, + deepAccess, + deepSetValue, + getBidIdParameter, getDefinedParams, getWindowTop, isArray, @@ -20,17 +22,32 @@ import * as events from '../src/events.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; +const NM_VERSION = '3.0.0'; +const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; -const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?'; +const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&type={{.TYPE_PIXEL}}'; const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; + const VIDEO_PARAMS = [ - 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', - 'playbackmethod', 'protocols', 'startdelay' + 'api', + 'linearity', + 'maxduration', + 'mimes', + 'minduration', + 'placement', + 'playbackmethod', + 'protocols', + 'startdelay', +]; + +const ALLOWED_ORTB2_PARAMETERS = [ + 'site.pagecat', + 'site.content.cat', + 'site.content.language', ]; -const GVLID = 1060; const sendingDataStatistic = initSendingDataStatistic(); events.on(CONSTANTS.EVENTS.AUCTION_INIT, auctionInitHandler); @@ -61,85 +78,35 @@ export const spec = { const id = getPlacementId(bid); const auctionId = bid.auctionId; const bidId = bid.bidId; - let sizes = bid.sizes; - if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; const site = getSiteObj(); const device = getDeviceObj(); const postBody = { - 'id': bidderRequest?.bidderRequestId, - 'ext': { - 'prebid': { - 'storedrequest': { - 'id': id - } + id: bidderRequest?.bidderRequestId, + ext: { + prebid: { + storedrequest: { + id, + }, }, - 'nextMillennium': { - 'refresh_count': window.nmmRefreshCounts[bid.adUnitCode]++, - 'elOffsets': getBoundingClient(bid), - 'scrollTop': window.pageYOffset || document.documentElement.scrollTop - } + nextMillennium: { + nm_version: NM_VERSION, + refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++, + elOffsets: getBoundingClient(bid), + scrollTop: window.pageYOffset || document.documentElement.scrollTop, + }, }, 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]} }) - }; + imp: [], }; - const video = deepAccess(bid, 'mediaTypes.video'); - if (video) { - imp.video = getDefinedParams(video, VIDEO_PARAMS); - if (video.playerSize) { - imp.video = Object.assign( - imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} - ); - } else if (video.w && video.h) { - imp.video.w = video.w; - imp.video.h = video.h; - }; - }; - - postBody.imp.push(imp); - - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const uspConsent = bidderRequest && bidderRequest.uspConsent; - - if (gdprConsent || uspConsent) { - postBody.regs = { ext: {} }; - - if (uspConsent) { - postBody.regs.ext.us_privacy = uspConsent; - }; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies !== 'undefined') { - postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - }; - - if (typeof gdprConsent.consentString !== 'undefined') { - postBody.user = { - ext: { consent: gdprConsent.consentString } - }; - }; - }; - }; + postBody.imp.push(getImp(bid, id)); + setConsentStrings(postBody, bidderRequest); + setOrtb2Parameters(postBody, bidderRequest?.ortb2); const urlParameters = parseUrl(getWindowTop().location.href).search; const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; @@ -151,7 +118,7 @@ export const spec = { data: JSON.stringify(postBody), options: { contentType: 'text/plain', - withCredentials: true + withCredentials: true, }, bidId, @@ -173,6 +140,7 @@ export const spec = { 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}); @@ -189,8 +157,8 @@ export const spec = { netRevenue: true, ttl: TIME_TO_LIVE, meta: { - advertiserDomains: bid.adomain || [] - } + advertiserDomains: bid.adomain || [], + }, }; if (vastUrl || vastXml) { @@ -210,38 +178,29 @@ export const spec = { return bidResponses; }, - getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) return []; + const pixels = []; + const getSetPixelFunc = type => url => { pixels.push({type, url: replaceUsersyncMacros(url, gdprConsent, uspConsent, gppConsent, type)}) }; + const getSetPixelsFunc = type => response => { deepAccess(response, `body.ext.sync.${type}`, []).forEach(getSetPixelFunc(type)) }; + + const setPixel = (type, url) => { (getSetPixelFunc(type))(url) }; + const setPixelImages = getSetPixelsFunc('image'); + const setPixelIframes = getSetPixelsFunc('iframe'); if (isArray(responses)) { responses.forEach(response => { - if (syncOptions.pixelEnabled) { - deepAccess(response, 'body.ext.sync.image', []).forEach(imgUrl => { - pixels.push({ - type: 'image', - url: replaceUsersyncMacros(imgUrl, gdprConsent, uspConsent) - }); - }) - } - - if (syncOptions.iframeEnabled) { - deepAccess(response, 'body.ext.sync.iframe', []).forEach(iframeUrl => { - pixels.push({ - type: 'iframe', - url: replaceUsersyncMacros(iframeUrl, gdprConsent, uspConsent) - }); - }) - } + if (syncOptions.pixelEnabled) setPixelImages(response); + if (syncOptions.iframeEnabled) setPixelIframes(response); }) } if (!pixels.length) { - let syncUrl = SYNC_ENDPOINT; - if (gdprConsent && gdprConsent.gdprApplies) syncUrl += 'gdpr=1&gdpr_consent=' + gdprConsent.consentString + '&'; - if (uspConsent) syncUrl += 'us_privacy=' + uspConsent + '&'; - if (syncOptions.iframeEnabled) pixels.push({type: 'iframe', url: syncUrl + 'type=iframe'}); - if (syncOptions.pixelEnabled) pixels.push({type: 'image', url: syncUrl + 'type=image'}); + if (syncOptions.pixelEnabled) setPixel('image', SYNC_ENDPOINT); + if (syncOptions.iframeEnabled) setPixel('iframe', SYNC_ENDPOINT); } + return pixels; }, @@ -281,24 +240,88 @@ export const spec = { }, }; -function replaceUsersyncMacros(url, gdprConsent, uspConsent) { - const { consentString, gdprApplies } = gdprConsent || {}; +export function getImp(bid, id) { + const imp = { + id: bid.adUnitCode, + ext: { + prebid: { + storedrequest: { + id, + }, + }, + }, + }; + + const banner = deepAccess(bid, 'mediaTypes.banner'); + if (banner) { + imp.banner = { + format: (banner?.sizes || []).map(s => { return {w: s[0], h: s[1]} }), + }; + }; + + const video = deepAccess(bid, 'mediaTypes.video'); + if (video) { + imp.video = getDefinedParams(video, VIDEO_PARAMS); + if (video.playerSize) { + imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize) || {}); + } else if (video.w && video.h) { + imp.video.w = video.w; + imp.video.h = video.h; + }; + }; - if (gdprApplies) { - const gdpr = Number(gdprApplies); - url = url.replace('{{.GDPR}}', gdpr); + return imp; +}; - if (gdpr == 1 && consentString && consentString.length > 0) { - url = url.replace('{{.GDPRConsent}}', consentString); - } - } else { - url = url.replace('{{.GDPR}}', 0); - url = url.replace('{{.GDPRConsent}}', ''); - } +export function setConsentStrings(postBody = {}, bidderRequest) { + const gdprConsent = bidderRequest?.gdprConsent; + const uspConsent = bidderRequest?.uspConsent; + let gppConsent = bidderRequest?.gppConsent?.gppString && bidderRequest?.gppConsent; + if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) gppConsent = bidderRequest?.ortb2?.regs; - if (uspConsent) { - url = url.replace('{{.USPrivacy}}', uspConsent); + if (gdprConsent || uspConsent || gppConsent) { + postBody.regs = { ext: {} }; + + if (uspConsent) { + postBody.regs.ext.us_privacy = uspConsent; + }; + + if (gppConsent) { + postBody.regs.gpp = gppConsent?.gppString || gppConsent?.gpp; + postBody.regs.gpp_sid = bidderRequest.gppConsent?.applicableSections || gppConsent?.gpp_sid; + }; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + }; + + if (typeof gdprConsent.consentString !== 'undefined') { + postBody.user = { + ext: { consent: gdprConsent.consentString }, + }; + }; + }; + }; +}; + +export function setOrtb2Parameters(postBody, ortb2 = {}) { + for (let parameter of ALLOWED_ORTB2_PARAMETERS) { + const value = deepAccess(ortb2, parameter); + if (value) deepSetValue(postBody, parameter, value); } +} + +export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') { + const { consentString = '', gdprApplies = false } = gdprConsent; + const gdpr = Number(gdprApplies); + url = url + .replace('{{.GDPR}}', gdpr) + .replace('{{.GDPRConsent}}', consentString) + .replace('{{.USPrivacy}}', uspConsent) + .replace('{{.GPP}}', gppConsent.gppString || '') + .replace('{{.GPPSID}}', (gppConsent.applicableSections || []).join(',')) + .replace('{{.TYPE_PIXEL}}', type); return url; }; diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 564788c8b56..8b512218d56 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -1,32 +1,437 @@ import { expect } from 'chai'; -import { spec } from 'modules/nextMillenniumBidAdapter.js'; +import { + getImp, + replaceUsersyncMacros, + setConsentStrings, + setOrtb2Parameters, + spec, +} from 'modules/nextMillenniumBidAdapter.js'; + +describe('nextMillenniumBidAdapterTests', () => { + describe('function getImp', () => { + const dataTests = [ + { + title: 'imp - banner', + data: { + id: '123', + bid: { + mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, + adUnitCode: 'test-banner-1', + }, + }, -describe('nextMillenniumBidAdapterTests', function() { - const bidRequestData = [ - { - adUnitCode: 'test-div', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { placement_id: '-1' }, - sizes: [[300, 250]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true + expected: { + id: 'test-banner-1', + ext: {prebid: {storedrequest: {id: '123'}}}, + banner: {format: [{w: 300, h: 250}, {w: 320, h: 250}]}, + }, }, - ortb2: { - device: { - w: 1500, - h: 1000 + + { + title: 'imp - video', + data: { + id: '234', + bid: { + mediaTypes: {video: {playerSize: [400, 300]}}, + adUnitCode: 'test-video-1', + }, }, - site: { - domain: 'example.com', - page: 'http://example.com' - } + + expected: { + id: 'test-video-1', + ext: {prebid: {storedrequest: {id: '234'}}}, + video: {w: 400, h: 300}, + }, + }, + ]; + + for (let {title, data, expected} of dataTests) { + it(title, () => { + const {bid, id} = data; + const imp = getImp(bid, id); + expect(imp).to.deep.equal(expected); + }); + } + }); + + describe('function setConsentStrings', () => { + const dataTests = [ + { + title: 'full: uspConsent, gdprConsent and gppConsent', + data: { + postBody: {}, + bidderRequest: { + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}}, + }, + }, + + expected: { + user: {ext: {consent: 'kjfdniwjnifwenrif3'}}, + regs: { + gpp: 'DBACNYA~CPXxRfAPXxR', + gpp_sid: [7], + ext: {gdpr: 1, us_privacy: '1---'}, + }, + }, + }, + + { + title: 'gdprConsent(false) and ortb2(gpp)', + data: { + postBody: {}, + bidderRequest: { + gdprConsent: {consentString: 'ewtewbefbawyadexv', gdprApplies: false}, + ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}}, + }, + }, + + expected: { + user: {ext: {consent: 'ewtewbefbawyadexv'}}, + regs: { + gpp: 'DSFHFHWEUYVDC', + gpp_sid: [8, 9, 10], + ext: {gdpr: 0}, + }, + }, + }, + + { + title: 'gdprConsent(false)', + data: { + postBody: {}, + bidderRequest: {gdprConsent: {gdprApplies: false}}, + }, + + expected: { + regs: {ext: {gdpr: 0}}, + }, + }, + + { + title: 'empty', + data: { + postBody: {}, + bidderRequest: {}, + }, + + expected: {}, + }, + ]; + + for (let {title, data, expected} of dataTests) { + it(title, () => { + const {postBody, bidderRequest} = data; + setConsentStrings(postBody, bidderRequest); + expect(postBody).to.deep.equal(expected); + }); + } + }); + + describe('function replaceUsersyncMacros', () => { + const dataTests = [ + { + title: 'url with all macroses - consents full: uspConsent, gdprConsent and gppConsent', + data: { + url: 'https://some.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&type={{.TYPE_PIXEL}}', + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + type: 'image', + }, + + expected: 'https://some.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8&type=image', + }, + + { + title: 'url with some macroses - consents full: uspConsent, gdprConsent and gppConsent', + data: { + url: 'https://some.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&type={{.TYPE_PIXEL}}', + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: false}, + type: 'iframe', + }, + + expected: 'https://some.url?gdpr=0&gdpr_consent=kjfdniwjnifwenrif3&type=iframe', + }, + + { + title: 'url without macroses - consents full: uspConsent, gdprConsent and gppConsent', + data: { + url: 'https://some.url?param1=value1¶m2=value2', + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: false}, + type: 'iframe', + }, + + expected: 'https://some.url?param1=value1¶m2=value2', + }, + + { + title: 'url with all macroses - consents are empty', + data: { + url: 'https://some.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&type={{.TYPE_PIXEL}}', + }, + + expected: 'https://some.url?gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=&type=', + }, + ]; + + for (let {title, data, expected} of dataTests) { + it(title, () => { + const {url, gdprConsent, uspConsent, gppConsent, type} = data; + const newUrl = replaceUsersyncMacros(url, gdprConsent, uspConsent, gppConsent, type); + expect(newUrl).to.equal(expected); + }); + } + }); + + describe('function spec.getUserSyncs', () => { + const dataTests = [ + { + title: 'pixels from responses ({iframeEnabled: true, pixelEnabled: true})', + data: { + syncOptions: {iframeEnabled: true, pixelEnabled: true}, + responses: [ + {body: {ext: {sync: { + image: [ + 'https://some.1.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.2.url?us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.3.url?param=1234', + ], + + iframe: [ + 'https://some.4.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.5.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}', + ], + }}}}, + + {body: {ext: {sync: { + iframe: [ + 'https://some.6.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.7.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}', + ], + }}}}, + + {body: {ext: {sync: { + image: [ + 'https://some.8.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + ], + }}}}, + ], + + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [ + {type: 'image', url: 'https://some.1.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'image', url: 'https://some.2.url?us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'image', url: 'https://some.3.url?param=1234'}, + {type: 'iframe', url: 'https://some.4.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'iframe', url: 'https://some.5.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---'}, + {type: 'iframe', url: 'https://some.6.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'iframe', url: 'https://some.7.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---'}, + {type: 'image', url: 'https://some.8.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + ], + }, + + { + title: 'pixels from responses ({iframeEnabled: true, pixelEnabled: false})', + data: { + syncOptions: {iframeEnabled: true, pixelEnabled: false}, + responses: [ + {body: {ext: {sync: { + image: [ + 'https://some.1.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.2.url?us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.3.url?param=1234', + ], + + iframe: [ + 'https://some.4.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.5.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}', + ], + }}}}, + ], + + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [ + {type: 'iframe', url: 'https://some.4.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'iframe', url: 'https://some.5.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---'}, + ], + }, + + { + title: 'pixels from responses ({iframeEnabled: false, pixelEnabled: true})', + data: { + syncOptions: {iframeEnabled: false, pixelEnabled: true}, + responses: [ + {body: {ext: {sync: { + image: [ + 'https://some.1.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.2.url?us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.3.url?param=1234', + ], + + iframe: [ + 'https://some.4.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}', + 'https://some.5.url?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}', + ], + }}}}, + ], + + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [ + {type: 'image', url: 'https://some.1.url?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'image', url: 'https://some.2.url?us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8'}, + {type: 'image', url: 'https://some.3.url?param=1234'}, + ], + }, + + { + title: 'pixels - responses is empty ({iframeEnabled: true, pixelEnabled: true})', + data: { + syncOptions: {iframeEnabled: true, pixelEnabled: true}, + responses: [], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [ + {type: 'image', url: 'https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8&type=image'}, + {type: 'iframe', url: 'https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8&type=iframe'}, + ], + }, + + { + title: 'pixels - responses is empty ({iframeEnabled: true, pixelEnabled: false})', + data: { + syncOptions: {iframeEnabled: true, pixelEnabled: false}, + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [ + {type: 'iframe', url: 'https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&gpp=DBACNYA~CPXxRfAPXxR&gpp_sid=7,8&type=iframe'}, + ], + }, + + { + title: 'pixels - responses is empty ({iframeEnabled: false, pixelEnabled: false})', + data: { + syncOptions: {iframeEnabled: false, pixelEnabled: false}, + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7, 8]}, + gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, + }, + + expected: [], + }, + ]; + + for (let {title, data, expected} of dataTests) { + it(title, () => { + const {syncOptions, responses, gdprConsent, uspConsent, gppConsent} = data; + const pixels = spec.getUserSyncs(syncOptions, responses, gdprConsent, uspConsent, gppConsent); + expect(pixels).to.deep.equal(expected); + }); + } + }); + + describe('function setOrtb2Parameters', () => { + const dataTests = [ + { + title: 'site.pagecat, site.content.cat and site.content.language', + data: { + postBody: {}, + ortb2: {site: { + pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], + content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'}, + }}, + }, + + expected: {site: { + pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], + content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'}, + }}, + }, + + { + title: 'only site.content.language', + data: { + postBody: {site: {domain: 'some.domain'}}, + ortb2: {site: { + content: {language: 'EN'}, + }}, + }, + + expected: {site: { + domain: 'some.domain', + content: {language: 'EN'}, + }}, + }, + + { + title: 'object ortb2 is empty', + data: { + postBody: {imp: []}, + }, + + expected: {imp: []}, + }, + ]; + + for (let {title, data, expected} of dataTests) { + it(title, () => { + const {postBody, ortb2} = data; + setOrtb2Parameters(postBody, ortb2); + expect(postBody).to.deep.equal(expected); + }); + } + }); + + const bidRequestData = [{ + adUnitCode: 'test-div', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '-1' }, + sizes: [[300, 250]], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + }, + + ortb2: { + device: { + w: 1500, + h: 1000 + }, + + site: { + domain: 'example.com', + page: 'http://example.com' } } - ]; + }]; const serverResponse = { body: { @@ -49,7 +454,7 @@ describe('nextMillenniumBidAdapterTests', function() { cur: 'USD', ext: { sync: { - image: ['urlA?gdpr={{.GDPR}}'], + image: ['urlA?gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}'], iframe: ['urlB'], } } @@ -117,61 +522,6 @@ describe('nextMillenniumBidAdapterTests', function() { }, ]; - it('Request params check with GDPR and USP Consent', function () { - const request = spec.buildRequests(bidRequestData, bidRequestData[0]); - expect(JSON.parse(request[0].data).user.ext.consent).to.equal('kjfdniwjnifwenrif3'); - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); - }); - - it('Test getUserSyncs function', function () { - const syncOptions = { - 'iframeEnabled': false, - 'pixelEnabled': true - } - let userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA?gdpr=1'); - - syncOptions.iframeEnabled = true; - syncOptions.pixelEnabled = false; - userSync = spec.getUserSyncs(syncOptions, [serverResponse], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.equal('iframe'); - expect(userSync[0].url).to.equal('urlB'); - }); - - it('Test getUserSyncs with no response', function () { - const syncOptions = { - 'iframeEnabled': true, - 'pixelEnabled': false - } - let userSync = spec.getUserSyncs(syncOptions, [], bidRequestData[0].gdprConsent, bidRequestData[0].uspConsent); - expect(userSync).to.be.an('array') - expect(userSync[0].type).to.equal('iframe') - expect(userSync[0].url).to.equal('https://cookies.nextmillmedia.com/sync?gdpr=1&gdpr_consent=kjfdniwjnifwenrif3&us_privacy=1---&type=iframe') - }) - - it('Test getUserSyncs function if GDPR is undefined', function () { - const syncOptions = { - 'iframeEnabled': false, - 'pixelEnabled': true - } - - let userSync = spec.getUserSyncs(syncOptions, [serverResponse], undefined, bidRequestData[0].uspConsent); - expect(userSync).to.be.an('array').with.lengthOf(1); - expect(userSync[0].type).to.equal('image'); - expect(userSync[0].url).to.equal('urlA?gdpr=0'); - }); - - it('Request params check without GDPR Consent', function () { - delete bidRequestData[0].gdprConsent - const request = spec.buildRequests(bidRequestData, bidRequestData[0]); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('1---'); - }); - it('validate_generated_params', function() { const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); expect(request[0].bidId).to.equal('bid1234'); @@ -190,7 +540,7 @@ describe('nextMillenniumBidAdapterTests', function() { it('Check if refresh_count param is incremented', function() { const request = spec.buildRequests(bidRequestData); - expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(3); + expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(1); }); it('Check if domain was added', function() { From 4f3826a1a89837ff26e5acde1116880763334a5d Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:44:37 +0100 Subject: [PATCH 241/760] Replace openx.com with openx.net (#10786) --- modules/liveIntentIdSystem.js | 2 +- test/spec/modules/eids_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index bb56355de31..a1f156ca23d 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -330,7 +330,7 @@ export const liveIntentIdSubmodule = { } }, 'openx': { - source: 'openx.com', + source: 'openx.net', atype: 3, getValue: function(data) { return data.id; diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index fa271b5114e..25e70f12ced 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -344,7 +344,7 @@ describe('eids array generation for known sub-modules', function() { const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'openx.com', + source: 'openx.net', uids: [{ id: 'sample_id', atype: 3 @@ -359,7 +359,7 @@ describe('eids array generation for known sub-modules', function() { const newEids = createEidsArray(userId); expect(newEids.length).to.equal(1); expect(newEids[0]).to.deep.equal({ - source: 'openx.com', + source: 'openx.net', uids: [{ id: 'sample_id', atype: 3, From 27b57ab24488b3787587fce9217db2dc7e84f404 Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Tue, 5 Dec 2023 06:32:06 +0200 Subject: [PATCH 242/760] Rise Bid Adapter : support currency (#10790) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper * Rise Bid Adapter: support Coppa param (#24) * MinuteMedia Bid Adapter: support Coppa param (#25) * Revert "MinuteMedia Bid Adapter: support Coppa param (#25)" (#26) This reverts commit 66c4e7b46121afc5331c8bca6e2fc972fc55f090. * bump * update coppa fetch * setting coppa param update * update Coppa tests * update test naming * Rise Bid Adapter: support plcmt and sua (#27) * update minuteMediaBidAdapter - support missing params (#29) * RIseBidAdapter: support currency (#35) --------- Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> Co-authored-by: inna Co-authored-by: YakirLavi --- modules/riseBidAdapter.js | 19 +++++++++++-------- modules/riseBidAdapter.md | 1 + test/spec/modules/riseBidAdapter_spec.js | 12 +++++++++++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 78740f7f87d..44cc91aa87b 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -19,7 +19,7 @@ const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'rise'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; -const CURRENCY = 'USD'; +const DEFAULT_CURRENCY = 'USD'; const DEFAULT_SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb-multi', @@ -73,7 +73,7 @@ export const spec = { const bidResponse = { requestId: adUnit.requestId, cpm: adUnit.cpm, - currency: adUnit.currency || CURRENCY, + currency: adUnit.currency || DEFAULT_CURRENCY, width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, @@ -140,18 +140,20 @@ registerBidder(spec); /** * Get floor price * @param bid {bid} + * @param mediaType {string} + * @param currency {string} * @returns {Number} */ -function getFloor(bid, mediaType) { +function getFloor(bid, mediaType, currency) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: CURRENCY, + currency: currency, mediaType: mediaType, size: '*' }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; } /** @@ -289,7 +291,7 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); - + const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { params.floorPrice = 0; @@ -299,12 +301,13 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + currency: currency, + floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), bidId: getBidIdParameter('bidId', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), transactionId: bid.ortb2Imp?.ext?.tid, - coppa: 0 + coppa: 0, }; const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index f0837cb5508..ac0ea559c88 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -26,6 +26,7 @@ The adapter supports Video(instream). | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" | `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false +| `currency` | optional | String | 3 letters currency | "EUR" # Test Parameters diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index eed8d74f271..4be56c79913 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -53,7 +53,7 @@ describe('riseAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[640, 480]], 'params': { - 'org': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001', }, 'bidId': '299ffc8cca0b87', 'loop': 1, @@ -195,6 +195,16 @@ describe('riseAdapter', function () { 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: { From 645b562078ac32ebcda54738b2e9e2fb68c5b878 Mon Sep 17 00:00:00 2001 From: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Date: Tue, 5 Dec 2023 06:43:03 +0200 Subject: [PATCH 243/760] MinuteMedia Bid Adapter : support currency (#10791) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper * Rise Bid Adapter: support Coppa param (#24) * MinuteMedia Bid Adapter: support Coppa param (#25) * Revert "MinuteMedia Bid Adapter: support Coppa param (#25)" (#26) This reverts commit 66c4e7b46121afc5331c8bca6e2fc972fc55f090. * bump * update coppa fetch * setting coppa param update * update Coppa tests * update test naming * Rise Bid Adapter: support plcmt and sua (#27) * update minuteMediaBidAdapter - support missing params (#29) * add currency param to bid object and tests * update getFloor function and tests * adding test to currency param * adding doc & currency bidfloor support & update tests * update currency test * remove default test --------- Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> Co-authored-by: inna Co-authored-by: YakirLavi --- modules/minutemediaBidAdapter.js | 16 +++++++++------- modules/minutemediaBidAdapter.md | 1 + test/spec/modules/minutemediaBidAdapter_spec.js | 10 ++++++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index e67534d74fe..a4158e2abfa 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -19,7 +19,7 @@ const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; -const CURRENCY = 'USD'; +const DEFAULT_CURRENCY = 'USD'; const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; const MODES = { PRODUCTION: 'hb-mm-multi', @@ -72,7 +72,7 @@ export const spec = { const bidResponse = { requestId: adUnit.requestId, cpm: adUnit.cpm, - currency: adUnit.currency || CURRENCY, + currency: adUnit.currency || DEFAULT_CURRENCY, width: adUnit.width, height: adUnit.height, ttl: adUnit.ttl || TTL, @@ -141,16 +141,16 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid, mediaType) { +function getFloor(bid, mediaType, currency) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: CURRENCY, + currency: currency, mediaType: mediaType, size: '*' }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; } /** @@ -286,6 +286,7 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); + const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { @@ -296,12 +297,13 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + currency: currency, + floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0 + coppa: 0, }; const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index 66b54adaf0e..fdfdf1b32bf 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -24,6 +24,7 @@ The adapter supports Video(instream) & Banner. | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false +| `currency` | optional | String | 3 letters currency | "EUR" # Test Parameters ```javascript diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index 48f694bc79d..7e22114aeed 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -178,6 +178,16 @@ describe('minutemediaAdapter', function () { 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: { From 55ba11177e9e59d7a6fcb8a9c1c9d896447117f0 Mon Sep 17 00:00:00 2001 From: Patrick Clinger Date: Tue, 5 Dec 2023 12:02:23 -0800 Subject: [PATCH 244/760] Core: Add eventString to error executing handler message (#10797) * Add detail to error executing handler * Add test case for including eventString --- src/events.js | 2 +- test/spec/unit/core/events_spec.js | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/events.js b/src/events.js index 6eb7ce49f11..750ecdd8eee 100644 --- a/src/events.js +++ b/src/events.js @@ -80,7 +80,7 @@ const _public = (function () { try { fn.apply(null, args); } catch (e) { - utils.logError('Error executing handler:', 'events.js', e); + utils.logError('Error executing handler:', 'events.js', e, eventString); } }); } diff --git a/test/spec/unit/core/events_spec.js b/test/spec/unit/core/events_spec.js index 6551c9f2456..e1451f657b5 100644 --- a/test/spec/unit/core/events_spec.js +++ b/test/spec/unit/core/events_spec.js @@ -1,5 +1,6 @@ import {config} from 'src/config.js'; -import {emit, clearEvents, getEvents} from '../../../../src/events.js'; +import {emit, clearEvents, getEvents, on, off} from '../../../../src/events.js'; +import * as utils from '../../../../src/utils.js' describe('events', () => { let clock; @@ -26,5 +27,19 @@ describe('events', () => { config.setConfig({eventHistoryTTL: 1000}); clock.tick(10000); expect(getEvents().length).to.eql(1); - }) + }); + + it('should include the eventString if a callback fails', () => { + const logErrorStub = sinon.stub(utils, 'logError'); + const eventString = 'bidWon'; + let fn = function() { throw new Error('Test error'); }; + on(eventString, fn); + + emit(eventString, {}); + + sinon.assert.calledWith(logErrorStub, 'Error executing handler:', 'events.js', sinon.match.instanceOf(Error), eventString); + + off(eventString, fn); + logErrorStub.restore(); + }); }) From f818b8f8356d7f5b27e02d92624fe52ad7bfb11d Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 5 Dec 2023 21:19:30 +0100 Subject: [PATCH 245/760] Adagio Bid Adapter: remove useless adrequest fields (#10794) --- modules/adagioBidAdapter.js | 41 +++++++++++++--------- test/spec/modules/adagioBidAdapter_spec.js | 11 ------ 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 1f3d6040d34..59d1fa72cbb 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1020,6 +1020,9 @@ export const spec = { } } + // Enforce the organizationId param to be a string + bidRequest.params.organizationId = bidRequest.params.organizationId.toString(); + // Force the Data Layer key and value to be a String if (bidRequest.params.dataLayer) { if (isStr(bidRequest.params.dataLayer) || isNumber(bidRequest.params.dataLayer) || isArray(bidRequest.params.dataLayer) || isFn(bidRequest.params.dataLayer)) { @@ -1120,30 +1123,36 @@ export const spec = { bidRequest.gpid = gpid; } + // store the whole bidRequest (adUnit) object in the ADAGIO namespace. storeRequestInAdagioNS(bidRequest); - // Remove these fields at the very end, so we can still use them before. - delete bidRequest.transactionId; - delete bidRequest.ortb2Imp; - delete bidRequest.ortb2; - delete bidRequest.sizes; + // Remove some params that are not needed on the server side. + delete bidRequest.params.siteId; + + // whitelist the fields that are allowed to be sent to the server. + const adUnit = { + adUnitCode: bidRequest.adUnitCode, + auctionId: bidRequest.auctionId, + bidder: bidRequest.bidder, + bidId: bidRequest.bidId, + params: bidRequest.params, + features: bidRequest.features, + gpid: bidRequest.gpid, + mediaTypes: bidRequest.mediaTypes, + nativeParams: bidRequest.nativeParams, + score: bidRequest.score, + transactionId: bidRequest.transactionId, + } - return bidRequest; + return adUnit; }); // Group ad units by organizationId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - const adUnitCopy = deepClone(adUnit); - adUnitCopy.params.organizationId = adUnitCopy.params.organizationId.toString(); - - // remove useless props - delete adUnitCopy.floorData; - delete adUnitCopy.params.siteId; - delete adUnitCopy.userId; - delete adUnitCopy.userIdAsEids; + const organizationId = adUnit.params.organizationId - groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || []; - groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy); + groupedAdUnits[organizationId] = groupedAdUnits[organizationId] || []; + groupedAdUnits[organizationId].push(adUnit); return groupedAdUnits; }, {}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 7f0b31b9680..744f3c69e83 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -861,11 +861,6 @@ describe('Adagio bid adapter', () => { } const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.adUnits[0].floors.length).to.equal(3); - expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'banner', s: '300x250'}); - expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'banner', s: '300x600'}); - expect(requests[0].data.adUnits[0].floors[2]).to.deep.equal({f: 1, mt: 'video', s: '600x480'}); - expect(requests[0].data.adUnits[0].mediaTypes.banner.sizes.length).to.equal(2); expect(requests[0].data.adUnits[0].mediaTypes.banner.bannerSizes[0]).to.deep.equal({size: [300, 250], floor: 1}); expect(requests[0].data.adUnits[0].mediaTypes.banner.bannerSizes[1]).to.deep.equal({size: [300, 600], floor: 1}); @@ -890,10 +885,6 @@ describe('Adagio bid adapter', () => { } const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.adUnits[0].floors.length).to.equal(2); - expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({f: 1, mt: 'video'}); - expect(requests[0].data.adUnits[0].floors[1]).to.deep.equal({f: 1, mt: 'native'}); - expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.equal(1); expect(requests[0].data.adUnits[0].mediaTypes.native.floor).to.equal(1); }); @@ -913,8 +904,6 @@ describe('Adagio bid adapter', () => { } const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.adUnits[0].floors.length).to.equal(1); - expect(requests[0].data.adUnits[0].floors[0]).to.deep.equal({mt: 'video'}); expect(requests[0].data.adUnits[0].mediaTypes.video.floor).to.be.undefined; }); }); From 9a65d49ca70fd0145dcc1c67739c183d541337a3 Mon Sep 17 00:00:00 2001 From: Vladimir Fedoseev Date: Tue, 5 Dec 2023 22:59:35 +0100 Subject: [PATCH 246/760] VIS.X: add response time tracking on bidWon event (#10772) --- modules/visxBidAdapter.js | 23 ++++++++++++++ test/spec/modules/visxBidAdapter_spec.js | 40 +++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index a45a1db9ece..a86c958392e 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -15,6 +15,7 @@ const TIME_TO_LIVE = 360; const DEFAULT_CUR = 'EUR'; const ADAPTER_SYNC_PATH = '/push_sync'; const TRACK_TIMEOUT_PATH = '/track/bid_timeout'; +const RUNTIME_STATUS_RESPONSE_TIME = 999000; const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -33,6 +34,7 @@ const LOG_ERROR_MESS = { }; const currencyWhiteList = ['EUR', 'USD', 'GBP', 'PLN']; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const _bidResponseTimeLogged = []; export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -208,6 +210,12 @@ export const spec = { if (bid.ext && bid.ext.events && bid.ext.events.win) { triggerPixel(bid.ext.events.win); } + // Call 'track/runtime' with the corresponding bid.requestId - only once per auction + if (bid.ext && bid.ext.events && bid.ext.events.runtime && !_bidResponseTimeLogged.includes(bid.auctionId)) { + _bidResponseTimeLogged.push(bid.auctionId); + const _roundedTime = _roundResponseTime(bid.timeToRespond, 50); + triggerPixel(bid.ext.events.runtime.replace('{STATUS_CODE}', RUNTIME_STATUS_RESPONSE_TIME + _roundedTime)); + } }, onTimeout: function(timeoutData) { // Call '/track/bid_timeout' with timeout data @@ -321,6 +329,10 @@ function _addBidResponse(serverBid, bidsMap, currency, bidResponses) { if (serverBid.ext && serverBid.ext.prebid) { bidResponse.ext = serverBid.ext.prebid; + if (serverBid.ext.visx && serverBid.ext.visx.events) { + const prebidExtEvents = bidResponse.ext.events || {}; + bidResponse.ext.events = Object.assign(prebidExtEvents, serverBid.ext.visx.events); + } } const visxTargeting = deepAccess(serverBid, 'ext.prebid.targeting'); @@ -434,4 +446,15 @@ function _getUserId() { return null; } +function _roundResponseTime(time, timeRange) { + if (time <= 0) { + return 0; // Special code for scriptLoadTime of 0 ms or less + } else if (time > 5000) { + return 100; // Constant code for scriptLoadTime greater than 5000 ms + } else { + const roundedValue = Math.floor((time - 1) / timeRange) + 1; + return roundedValue; + } +} + registerBidder(spec); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 139349ceead..5528705efd7 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -1257,6 +1257,7 @@ describe('VisxAdapter', function () { const request = spec.buildRequests(bidRequests); const pendingUrl = 'https://t.visx.net/track/pending/123123123'; const winUrl = 'https://t.visx.net/track/win/53245341'; + const runtimeUrl = 'https://t.visx.net/track/status/12345678'; const expectedResponse = [ { 'requestId': '300bfeb0d71a5b', @@ -1281,7 +1282,8 @@ describe('VisxAdapter', function () { 'ext': { 'events': { 'pending': pendingUrl, - 'win': winUrl + 'win': winUrl, + 'runtime': runtimeUrl }, 'targeting': { 'hb_visx_product': 'understitial', @@ -1298,6 +1300,9 @@ describe('VisxAdapter', function () { pending: pendingUrl, win: winUrl, }); + utils.deepSetValue(serverResponse.bid[0], 'ext.visx.events', { + runtime: runtimeUrl + }); const result = spec.interpretResponse({'body': {'seatbid': [serverResponse]}}, request); expect(result).to.deep.equal(expectedResponse); }); @@ -1325,6 +1330,39 @@ describe('VisxAdapter', function () { expect(utils.triggerPixel.calledOnceWith(trackUrl)).to.equal(true); }); + it('onBidWon with runtime tracker (0 < timeToRespond <= 5000 )', function () { + const trackUrl = 'https://t.visx.net/track/win/123123123'; + const runtimeUrl = 'https://t.visx.net/track/status/12345678/{STATUS_CODE}'; + const bid = { auctionId: '1', ext: { events: { win: trackUrl, runtime: runtimeUrl } }, timeToRespond: 100 }; + spec.onBidWon(bid); + expect(utils.triggerPixel.calledTwice).to.equal(true); + expect(utils.triggerPixel.calledWith(trackUrl)).to.equal(true); + expect(utils.triggerPixel.calledWith(runtimeUrl.replace('{STATUS_CODE}', 999002))).to.equal(true); + }); + + it('onBidWon with runtime tracker (timeToRespond <= 0 )', function () { + const runtimeUrl = 'https://t.visx.net/track/status/12345678/{STATUS_CODE}'; + const bid = { auctionId: '2', ext: { events: { runtime: runtimeUrl } }, timeToRespond: 0 }; + spec.onBidWon(bid); + expect(utils.triggerPixel.calledOnceWith(runtimeUrl.replace('{STATUS_CODE}', 999000))).to.equal(true); + }); + + it('onBidWon with runtime tracker (timeToRespond > 5000 )', function () { + const runtimeUrl = 'https://t.visx.net/track/status/12345678/{STATUS_CODE}'; + const bid = { auctionId: '3', ext: { events: { runtime: runtimeUrl } }, timeToRespond: 5001 }; + spec.onBidWon(bid); + expect(utils.triggerPixel.calledOnceWith(runtimeUrl.replace('{STATUS_CODE}', 999100))).to.equal(true); + }); + + it('onBidWon runtime tracker should be called once per auction', function () { + const runtimeUrl = 'https://t.visx.net/track/status/12345678/{STATUS_CODE}'; + const bid1 = { auctionId: '4', ext: { events: { runtime: runtimeUrl } }, timeToRespond: 100 }; + spec.onBidWon(bid1); + const bid2 = { auctionId: '4', ext: { events: { runtime: runtimeUrl } }, timeToRespond: 200 }; + spec.onBidWon(bid2); + expect(utils.triggerPixel.calledOnceWith(runtimeUrl.replace('{STATUS_CODE}', 999002))).to.equal(true); + }); + it('onTimeout', function () { const data = [{ timeout: 3000, adUnitCode: 'adunit-code-1', auctionId: '1cbd2feafe5e8b', bidder: 'visx', bidId: '23423', params: [{ uid: '1' }] }]; const expectedData = [{ timeout: 3000, params: [{ uid: 1 }] }]; From 264efa7f1cbe4bd7e81452a6b70892b4717805af Mon Sep 17 00:00:00 2001 From: tkrishnaviant Date: Wed, 6 Dec 2023 04:07:53 -0800 Subject: [PATCH 247/760] ViantOrtb Bid Adapter : update endpoint url (#10801) * Edited viantOrtbBidAdapter bidder endpoint url. * Update viantOrtbBidAdapter.js * Update viantOrtbBidAdapter.js --- modules/viantOrtbBidAdapter.js | 2 +- test/spec/modules/viantOrtbBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index e7bf9129a62..0f7953a192a 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -5,7 +5,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js' import {deepAccess, getBidIdParameter, logError} from '../src/utils.js'; const BIDDER_CODE = 'viant'; -const ENDPOINT = 'https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder_test' +const ENDPOINT = 'https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder' const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index ef537d50986..6d5014bd639 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -172,7 +172,7 @@ describe('viantOrtbBidAdapter', function () { }); it('sends bid requests to the correct endpoint', function () { const url = testBuildRequests(baseBannerBidRequests, baseBidderRequest)[0].url; - expect(url).to.equal('https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder_test'); + expect(url).to.equal('https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder'); }); it('sends site', function () { From bf909eaa066c997ee169640008ee5933c1965874 Mon Sep 17 00:00:00 2001 From: github-saad-elmahfoudi <87966599+github-saad-elmahfoudi@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:19:18 +0100 Subject: [PATCH 248/760] Teads Bid Adapter: add new window features to request (#10796) * Add new window features to HB request * fixup! Add new window features to HB request --- modules/teadsBidAdapter.js | 7 +++ test/spec/modules/teadsBidAdapter_spec.js | 58 +++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 5e92af640b8..ba16c6ddf82 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -45,6 +45,7 @@ export const spec = { */ buildRequests: function(validBidRequests, bidderRequest) { const bids = validBidRequests.map(buildRequestObject); + const topWindow = window.top; const payload = { referrer: getReferrerInfo(bidderRequest), @@ -55,6 +56,12 @@ export const spec = { timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, + screenOrientation: screen.orientation?.type, + historyLength: topWindow.history?.length, + viewportHeight: topWindow.visualViewport?.height, + viewportWidth: topWindow.visualViewport?.width, + hardwareConcurrency: topWindow.navigator?.hardwareConcurrency, + deviceMemory: topWindow.navigator?.deviceMemory, hb_version: '$prebid.version$', ...getSharedViewerIdParameters(validBidRequests), ...getFirstPartyTeadsIdParameter(validBidRequests) diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index b0d5f436e0b..98f706f5193 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1,6 +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'; const ENDPOINT = 'https://a.teads.tv/hb/bid-request'; const AD_SCRIPT = '"'; @@ -254,6 +255,63 @@ describe('teadsBidAdapter', () => { expect(payload.pageReferrer).to.deep.equal(document.referrer); }); + it('should add screenOrientation info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const screenOrientation = window.top.screen.orientation?.type + + if (screenOrientation) { + expect(payload.screenOrientation).to.exist; + expect(payload.screenOrientation).to.deep.equal(screenOrientation); + } else expect(payload.screenOrientation).to.not.exist; + }); + + it('should add historyLength info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.historyLength).to.exist; + expect(payload.historyLength).to.deep.equal(window.top.history.length); + }); + + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + + it('should add viewportWidth info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportWidth).to.exist; + expect(payload.viewportWidth).to.deep.equal(window.top.visualViewport.width); + }); + + it('should add hardwareConcurrency info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const hardwareConcurrency = window.top.navigator?.hardwareConcurrency + + if (hardwareConcurrency) { + expect(payload.hardwareConcurrency).to.exist; + expect(payload.hardwareConcurrency).to.deep.equal(hardwareConcurrency); + } else expect(payload.hardwareConcurrency).to.not.exist + }); + + it('should add deviceMemory info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceMemory = window.top.navigator.deviceMemory + + if (deviceMemory) { + expect(payload.deviceMemory).to.exist; + expect(payload.deviceMemory).to.deep.equal(deviceMemory); + } else expect(payload.deviceMemory).to.not.exist; + }); + describe('pageTitle', function () { it('should add pageTitle info to payload based on document title', function () { const testText = 'This is a title'; From 0de4a099d2748b2aef3fd111fad9295947d055c2 Mon Sep 17 00:00:00 2001 From: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:26:20 +0100 Subject: [PATCH 249/760] Sparteo Bid Adapter : handle vastUrl for video context (#10805) * SparteoBidAdapter: handle vastUrl for video context * Add: video tests --- modules/sparteoBidAdapter.js | 15 ++++++++++----- test/spec/modules/sparteoBidAdapter_spec.js | 10 ++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index ac48ec4089d..93353c5dda7 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -38,7 +38,14 @@ const converter = ortbConverter({ bidResponse(buildBidResponse, bid, context) { context.mediaType = deepAccess(bid, 'ext.prebid.type'); - return buildBidResponse(bid, context) + const response = buildBidResponse(bid, context); + + if (context.mediaType == 'video') { + response.nurl = bid.nurl; + response.vastUrl = deepAccess(bid, 'ext.prebid.cache.vastXml.url') ?? null; + } + + return response; } }); @@ -120,10 +127,8 @@ export const spec = { onTimeout: function(timeoutData) {}, onBidWon: function(bid) { - if (bid && bid.nurl && bid.nurl.length > 0) { - bid.nurl.forEach(function(winUrl) { - triggerPixel(winUrl, null); - }); + if (bid && bid.nurl) { + triggerPixel(bid.nurl, null); } }, diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index d6b8b0cc2d8..71191b44a29 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -323,7 +323,12 @@ describe('SparteoAdapter', function () { 'price': 5, 'ext': { 'prebid': { - 'type': 'video' + 'type': 'video', + 'cache': { + 'vastXml': { + 'url': 'https://pbs.tet.com/cache?uuid=1234' + } + } } }, 'adm': 'tag', @@ -368,7 +373,8 @@ describe('SparteoAdapter', function () { ttl: TTL, mediaType: 'video', meta: {}, - vastUrl: 'https://t.bidder.sparteo.com/img', + nurl: 'https://t.bidder.sparteo.com/img', + vastUrl: 'https://pbs.tet.com/cache?uuid=1234', vastXml: 'tag' }); } From 864dc02001bbd6b3df67371adccf4e6f5e87ade2 Mon Sep 17 00:00:00 2001 From: mustafa kemal Date: Thu, 7 Dec 2023 17:42:52 +0300 Subject: [PATCH 250/760] deal support added (#10802) Co-authored-by: mustafakemal --- modules/theAdxBidAdapter.js | 12 +++- modules/theAdxBidAdapter.md | 19 +++--- test/spec/modules/theAdxBidAdapter_spec.js | 72 ++++++++++++++++++++++ 3 files changed, 92 insertions(+), 11 deletions(-) diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index 03f303498a1..def2446635b 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -159,7 +159,6 @@ export const spec = { withCredentials: true, }, bidder: 'theadx', - // TODO: is 'page' the right value here? referrer: encodeURIComponent(bidderRequest.refererInfo.page || ''), data: generatePayload(bidRequest, bidderRequest), mediaTypes: bidRequest['mediaTypes'], @@ -261,6 +260,7 @@ export const spec = { ad: creative, ttl: ttl || 3000, creativeId: bid.crid, + dealId: bid.dealid || null, netRevenue: true, currency: responseBody.cur, mediaType: mediaType, @@ -466,11 +466,19 @@ let generateImpBody = (bidRequest, bidderRequest) => { } else if (mediaTypes && mediaTypes.native) { native = generateNativeComponent(bidRequest, bidderRequest); } - const result = { id: bidRequest.index, tagid: bidRequest.params.tagId + '', }; + + // deals support + if (bidRequest.params.deals && Array.isArray(bidRequest.params.deals) && bidRequest.params.deals.length > 0) { + result.pmp = { + deals: bidRequest.params.deals, + private_auction: 0, + }; + } + if (banner) { result['banner'] = banner; } diff --git a/modules/theAdxBidAdapter.md b/modules/theAdxBidAdapter.md index 2392bfaa819..cdc7b8410a8 100644 --- a/modules/theAdxBidAdapter.md +++ b/modules/theAdxBidAdapter.md @@ -26,9 +26,9 @@ Module that connects to TheAdx demand sources { bidder: "theadx", params: { - pid: 1000, // publisher id - wid: 2000, //website id - tagId: 5000, //zone id + pid: 1, // publisher id + wid: 7, //website id + tagId: 19, //zone id } } ] @@ -43,9 +43,10 @@ Module that connects to TheAdx demand sources { bidder: "theadx", params: { - pid: 1000, // publisher id - wid: 2000, //website id - tagId: 5000, //zone id + pid: 1, // publisher id + wid: 7, //website id + tagId: 18, //zone id + deals:[{"id":"theadx:137"}] //optional } } ] @@ -80,9 +81,9 @@ Module that connects to TheAdx demand sources { bidder: "theadx", params: { - pid: 1000, // publisher id - wid: 2000, //website id - tagId: 5000, //zone id + pid: 1, // publisher id + wid: 7, //website id + tagId: 20, //zone id } } ] diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js index 99e5156190c..eb00834421a 100644 --- a/test/spec/modules/theAdxBidAdapter_spec.js +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -446,6 +446,78 @@ describe('TheAdxAdapter', function () { expect(processedBid.currency).to.equal(responseCurrency); }); + it('returns a valid deal bid response on sucessful banner request with deal', function () { + let incomingRequestId = 'XXtestingXX'; + let responsePrice = 3.14 + + let responseCreative = 'sample_creative&{FOR_COVARAGE}'; + + let responseCreativeId = '274'; + let responseCurrency = 'TRY'; + + let responseWidth = 300; + let responseHeight = 250; + let responseTtl = 213; + let dealId = 'theadx_deal_id'; + + let sampleResponse = { + id: '66043f5ca44ecd8f8769093b1615b2d9', + seatbid: [{ + bid: [{ + id: 'c21bab0e-7668-4d8f-908a-63e094c09197', + dealid: 'theadx_deal_id', + impid: '1', + price: responsePrice, + adid: responseCreativeId, + crid: responseCreativeId, + adm: responseCreative, + adomain: [ + 'www.domain.com' + ], + cid: '274', + attr: [], + w: responseWidth, + h: responseHeight, + ext: { + ttl: responseTtl + } + }], + seat: '201', + group: 0 + }], + bidid: 'c21bab0e-7668-4d8f-908a-63e094c09197', + cur: responseCurrency + }; + + let sampleRequest = { + bidId: incomingRequestId, + mediaTypes: { + banner: {} + }, + requestId: incomingRequestId, + deals: [{id: dealId}] + }; + let serverResponse = { + body: sampleResponse + } + let result = spec.interpretResponse(serverResponse, sampleRequest); + + expect(result.length).to.equal(1); + + let processedBid = result[0]; + + // expect(processedBid.requestId).to.equal(incomingRequestId); + expect(processedBid.cpm).to.equal(responsePrice); + expect(processedBid.width).to.equal(responseWidth); + expect(processedBid.height).to.equal(responseHeight); + expect(processedBid.ad).to.equal(responseCreative); + expect(processedBid.ttl).to.equal(responseTtl); + expect(processedBid.creativeId).to.equal(responseCreativeId); + expect(processedBid.netRevenue).to.equal(true); + expect(processedBid.currency).to.equal(responseCurrency); + expect(processedBid.dealId).to.equal(dealId); + }); + it('returns an valid bid response on sucessful video request', function () { let incomingRequestId = 'XXtesting-275XX'; let responsePrice = 6 From d0747e4618874b3c71c8ef494e0c273a496c361b Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Thu, 7 Dec 2023 23:16:21 +0800 Subject: [PATCH 251/760] Mediago Bid Adapter : add cookieSync and gdpr (#10724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * update Mediago & Discovery BidAdapter:remove size filter * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:add param in banner format * update mediago & discovery:first party data * update mediago & discovery:first party data * update mediago & discovery:first party data * fix(mediago & discovery): update param tagid * fix(mediago & discovery): update param tagid * feat:add imp params for analysis * fix:add imp params for analysis * code format * test:add test data & test * format code * rerun circleci * feat: 1. add cookieSync 2. add gdpr * code format * rerun circleci * add gvlid * feat:add param 1.ppuid 2.content 3.cat 4.reqtime * fix: getUserSyncs param fixed * fix: getUserSyncs param fixed * feat: add param 1. adUnitCode 2. referrer --------- Co-authored-by: BaronYu --- modules/mediagoBidAdapter.js | 136 +++++++++++++++----- test/spec/modules/mediagoBidAdapter_spec.js | 130 +++++++++++++++---- 2 files changed, 212 insertions(+), 54 deletions(-) diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index efd730acace..72b06dfe7ed 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -13,14 +13,20 @@ const BIDDER_CODE = 'mediago'; const ENDPOINT_URL = // ((PROTOCOL === 'https:') ? 'https' : 'http') + 'https://rtb-us.mediago.io/api/bid?tn='; +const COOKY_SYNC_URL = 'https://trace.mediago.io/ju/cs/eplist'; +const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; + const TIME_TO_LIVE = 500; +const GVLID = 1020; // const ENDPOINT_URL = '/api/bid?tn='; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const storage = getStorageManager({ bidderCode: BIDDER_CODE }); let globals = {}; let itemMaps = {}; /* ----- mguid:start ------ */ const COOKIE_KEY_MGUID = '__mguid_'; +const STORE_MAX_AGE = 1000 * 60 * 60 * 24 * 365; +let reqTimes = 0; /** * 获取用户id @@ -31,7 +37,7 @@ const getUserID = () => { if (i === null) { const uuid = utils.generateUUID(); - storage.setCookie(COOKIE_KEY_MGUID, uuid); + storage.setCookie(COOKIE_KEY_MGUID, uuid, STORE_MAX_AGE); return uuid; } return i; @@ -72,7 +78,7 @@ function isMobileAndTablet() { '.+mobile|avantgo|bada/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)', '|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone', '|p(ixi|re)/|plucker|pocket|psp|series(4|6)0|symbian|treo|up.(browser|link)|vodafone|wap', - '|windows ce|xda|xiino|android|ipad|playbook|silk', + '|windows ce|xda|xiino|android|ipad|playbook|silk' ].join(''), 'i' ); @@ -96,7 +102,7 @@ function isMobileAndTablet() { '|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(.b|g1|si)|utst|', 'v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)', '|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-', - '|your|zeto|zte-', + '|your|zeto|zte-' ].join(''), 'i' ); @@ -138,7 +144,7 @@ function getBidFloor(bid) { const bidFloor = bid.getFloor({ currency: 'USD', mediaType: '*', - size: '*', + size: '*' }); return bidFloor.floor; } catch (_) { @@ -156,11 +162,7 @@ function transformSizes(requestSizes) { let sizes = []; let sizeObj = {}; - if ( - utils.isArray(requestSizes) && - requestSizes.length === 2 && - !utils.isArray(requestSizes[0]) - ) { + if (utils.isArray(requestSizes) && requestSizes.length === 2 && !utils.isArray(requestSizes[0])) { sizeObj.width = parseInt(requestSizes[0], 10); sizeObj.height = parseInt(requestSizes[1], 10); sizes.push(sizeObj); @@ -187,7 +189,7 @@ const mediagoAdSize = [ { w: 160, h: 600 }, { w: 320, h: 180 }, { w: 320, h: 100 }, - { w: 336, h: 280 }, + { w: 336, h: 280 } ]; /** @@ -207,17 +209,13 @@ function getItems(validBidRequests, bidderRequest) { // 确认尺寸是否符合我们要求 for (let size of sizes) { - matchSize = mediagoAdSize.find( - (item) => size.width === item.w && size.height === item.h - ); + matchSize = mediagoAdSize.find(item => size.width === item.w && size.height === item.h); if (matchSize) { break; } } if (!matchSize) { - matchSize = sizes[0] - ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } - : { h: 0, w: 0 }; + matchSize = sizes[0] ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } : { h: 0, w: 0 }; } const bidFloor = getBidFloor(req); @@ -226,6 +224,18 @@ function getItems(validBidRequests, bidderRequest) { utils.deepAccess(req, 'ortb2Imp.ext.data.pbadslot') || utils.deepAccess(req, 'params.placementId', 0); + const gdprConsent = {}; + if (bidderRequest && bidderRequest.gdprConsent) { + gdprConsent.consent = bidderRequest.gdprConsent.consentString; + gdprConsent.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + // if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + // let ac = bidderRequest.gdprConsent.addtlConsent; + // // pull only the ids from the string (after the ~) and convert them to an array of ints + // let acStr = ac.substring(ac.indexOf('~') + 1); + // gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + // } + } + // if (mediaTypes.native) {} // banner广告类型 if (mediaTypes.banner) { @@ -237,17 +247,21 @@ function getItems(validBidRequests, bidderRequest) { h: matchSize.h, w: matchSize.w, pos: 1, - format: sizes, + format: sizes }, ext: { + adUnitCode: req.adUnitCode, + referrer: getReferrer(req, bidderRequest), ortb2Imp: utils.deepAccess(req, 'ortb2Imp'), // 传入完整对象,分析日志数据 gpid: gpid, // 加入后无法返回广告 + adslot: utils.deepAccess(req, 'ortb2Imp.ext.data.adserver.adslot', '', ''), + ...gdprConsent // gdpr }, - tagid: req.params && req.params.tagid, + tagid: req.params && req.params.tagid }; itemMaps[id] = { req, - ret, + ret }; } @@ -256,6 +270,21 @@ function getItems(validBidRequests, bidderRequest) { return items; } +/** + * @param {BidRequest} bidRequest + * @param bidderRequest + * @returns {string} + */ +function getReferrer(bidRequest = {}, bidderRequest = {}) { + let pageUrl; + if (bidRequest.params && bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else { + pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page'); + } + return pageUrl; +} + /** * 获取rtb请求参数 * @@ -268,7 +297,14 @@ function getParam(validBidRequests, bidderRequest) { const sharedid = utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || utils.deepAccess(validBidRequests[0], 'userId.pubcid'); - const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; + + const bidsUserIdAsEids = validBidRequests[0].userIdAsEids; + const bidsUserid = validBidRequests[0].userId; + const eids = bidsUserIdAsEids || bidsUserid; + const ppuid = bidsUserid && bidsUserid.pubProvidedId; + const content = utils.deepAccess(bidderRequest, 'ortb2.site.content'); + const cat = utils.deepAccess(bidderRequest, 'ortb2.site.cat'); + reqTimes += 1; let isMobile = isMobileAndTablet() ? 1 : 0; // input test status by Publisher. more frequently for test true req @@ -276,8 +312,7 @@ function getParam(validBidRequests, bidderRequest) { let auctionId = getProperty(bidderRequest, 'auctionId'); let items = getItems(validBidRequests, bidderRequest); - const domain = - utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; + const domain = utils.deepAccess(bidderRequest, 'refererInfo.domain') || document.domain; const location = utils.deepAccess(bidderRequest, 'refererInfo.location'); const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); @@ -301,15 +336,21 @@ function getParam(validBidRequests, bidderRequest) { // ua: 'Mozilla/5.0 (Linux; Android 12; SM-G970U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Mobile Safari/537.36', os: navigator.platform || '', ua: navigator.userAgent, - language: /en/.test(navigator.language) ? 'en' : navigator.language, + language: /en/.test(navigator.language) ? 'en' : navigator.language }, ext: { eids, + bidsUserIdAsEids, + bidsUserid, + ppuid, firstPartyData, + content, + cat, + reqTimes }, user: { buyeruid: getUserID(), - id: sharedid || pubcid, + id: sharedid || pubcid }, eids, site: { @@ -322,11 +363,11 @@ function getParam(validBidRequests, bidderRequest) { publisher: { // todo id: domain, - name: domain, - }, + name: domain + } }, imp: items, - tmax: timeout, + tmax: timeout }; return c; } else { @@ -336,6 +377,7 @@ function getParam(validBidRequests, bidderRequest) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, // aliases: ['ex'], // short code /** * Determines whether or not the given bid request is valid. @@ -367,7 +409,7 @@ export const spec = { return { method: 'POST', url: ENDPOINT_URL + globals['token'], - data: payloadString, + data: payloadString }; }, @@ -398,7 +440,7 @@ export const spec = { ttl: TIME_TO_LIVE, // referrer: REFERER, ad: getProperty(bid, 'adm'), - nurl: getProperty(bid, 'nurl'), + nurl: getProperty(bid, 'nurl') // adserverTargeting: { // granularityMultiplier: 0.1, // priceGranularity: 'pbHg', @@ -415,6 +457,38 @@ export const spec = { return bidResponses; }, + getUserSyncs: function (syncOptions, serverResponse, gdprConsent, uspConsent, gppConsent) { + const origin = encodeURIComponent(location.origin || `https://${location.host}`); + let syncParamUrl = `dm=${origin}`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncParamUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncParamUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncParamUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + return [ + { + type: 'iframe', + url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` + } + ]; + } else { + return [ + { + type: 'image', + url: `${COOKY_SYNC_URL}?${syncParamUrl}` + } + ]; + } + }, + /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data @@ -434,7 +508,7 @@ export const spec = { if (bid['nurl']) { utils.triggerPixel(bid['nurl']); } - }, + } /** * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index 5eb362893e3..601bcff527a 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -23,29 +23,37 @@ describe('mediago:BidAdapterTests', function () { tid: 'tid_01', data: { browsi: { - browsiViewability: 'NA', + browsiViewability: 'NA' }, adserver: { name: 'adserver_name', - adslot: 'adslot_name', + adslot: 'adslot_name' }, - }, - }, - }, + pbadslot: '/12345/my-gpt-tag-0' + } + } + } }, mediaTypes: { banner: { sizes: [[300, 250]], - pos: 'left', - }, + pos: 'left' + } }, ortb2: { - user: { - ext: { - data: { - }, + site: { + cat: ['IAB2'], + keywords: 'power tools, drills, tools=industrial', + content: { + keywords: 'video, source=streaming' }, + }, + user: { + ext: { + data: {} + } + } }, adUnitCode: 'regular_iframe', transactionId: '7b26fdae-96e6-4c35-a18b-218dda11397d', @@ -56,9 +64,83 @@ describe('mediago:BidAdapterTests', function () { src: 'client', bidRequestsCount: 1, bidderRequestsCount: 1, - bidderWinsCount: 0, - }, + bidderWinsCount: 0 + } ], + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true, + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + }, + userId: { + tdid: 'sample-userid', + uid2: { id: 'sample-uid2-value' }, + criteoId: 'sample-criteo-userid', + netId: 'sample-netId-userid', + idl_env: 'sample-idl-userid', + pubProvidedId: [ + { + source: 'puburl.com', + uids: [ + { + id: 'pubid2', + atype: 1, + ext: { + stype: 'ppuid' + } + } + ] + }, + { + source: 'puburl2.com', + uids: [ + { + id: 'pubid2' + }, + { + id: 'pubid2-123' + } + ] + } + ] + }, + userIdAsEids: [ + { + source: 'adserver.org', + uids: [{ id: 'sample-userid' }] + }, + { + source: 'criteo.com', + uids: [{ id: 'sample-criteo-userid' }] + }, + { + source: 'netid.de', + uids: [{ id: 'sample-netId-userid' }] + }, + { + source: 'liveramp.com', + uids: [{ id: 'sample-idl-userid' }] + }, + { + source: 'uidapi.com', + uids: [{ id: 'sample-uid2-value' }] + }, + { + source: 'puburl.com', + uids: [{ id: 'pubid1' }] + }, + { + source: 'puburl2.com', + uids: [{ id: 'pubid2' }, { id: 'pubid2-123' }] + } + ] }; let request = []; @@ -67,8 +149,8 @@ describe('mediago:BidAdapterTests', function () { spec.isBidRequestValid({ bidder: 'mediago', params: { - token: ['85a6b01e41ac36d49744fad726e3655d'], - }, + token: ['85a6b01e41ac36d49744fad726e3655d'] + } }) ).to.equal(true); }); @@ -80,10 +162,12 @@ describe('mediago:BidAdapterTests', function () { }); it('mediago:validate_response_params', function () { - let adm = ""; + let adm = + ''; let temp = '%3Cscr'; temp += 'ipt%3E'; - temp += '!function()%7B%22use%20strict%22%3Bfunction%20f(t)%7Breturn(f%3D%22function%22%3D%3Dtypeof%20Symbol%26%26%22symbol%22%3D%3Dtypeof%20Symbol.iterator%3Ffunction(t)%7Breturn%20typeof%20t%7D%3Afunction(t)%7Breturn%20t%26%26%22function%22%3D%3Dtypeof%20Symbol%26%26t.constructor%3D%3D%3DSymbol%26%26t!%3D%3DSymbol.prototype%3F%22symbol%22%3Atypeof%20t%7D)(t)%7Dfunction%20l(t)%7Bvar%20e%3D0%3Carguments.length%26%26void%200!%3D%3Dt%3Ft%3A%7B%7D%3Btry%7Be.random_t%3D(new%20Date).getTime()%2Cg(function(t)%7Bvar%20e%3D1%3Carguments.length%26%26void%200!%3D%3Darguments%5B1%5D%3Farguments%5B1%5D%3A%22%22%3Bif(%22object%22!%3D%3Df(t))return%20e%3Bvar%20n%3Dfunction(t)%7Bfor(var%20e%2Cn%3D%5B%5D%2Co%3D0%2Ci%3DObject.keys(t)%3Bo%3Ci.length%3Bo%2B%2B)e%3Di%5Bo%5D%2Cn.push(%22%22.concat(e%2C%22%3D%22).concat(t%5Be%5D))%3Breturn%20n%7D(t).join(%22%26%22)%2Co%3De.indexOf(%22%23%22)%2Ci%3De%2Ct%3D%22%22%3Breturn-1!%3D%3Do%26%26(i%3De.slice(0%2Co)%2Ct%3De.slice(o))%2Cn%26%26(i%26%26-1!%3D%3Di.indexOf(%22%3F%22)%3Fi%2B%3D%22%26%22%2Bn%3Ai%2B%3D%22%3F%22%2Bn)%2Ci%2Bt%7D(e%2C%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Flog%2Ftrack%22))%7Dcatch(t)%7B%7D%7Dfunction%20g(t%2Ce%2Cn)%7B(t%3Dt%3Ft.split(%22%3B%3B%3B%22)%3A%5B%5D).map(function(t)%7Btry%7B0%3C%3Dt.indexOf(%22%2Fapi%2Fbidder%2Ftrack%22)%26%26n%26%26(t%2B%3D%22%26inIframe%3D%22.concat(!(!self.frameElement%7C%7C%22IFRAME%22!%3Dself.frameElement.tagName)%7C%7Cwindow.frames.length!%3Dparent.frames.length%7C%7Cself!%3Dtop)%2Ct%2B%3D%22%26pos_x%3D%22.concat(n.left%2C%22%26pos_y%3D%22).concat(n.top%2C%22%26page_w%3D%22).concat(n.page_width%2C%22%26page_h%3D%22).concat(n.page_height))%7Dcatch(t)%7Bl(%7Btn%3As%2Cwinloss%3A1%2Cfe%3A2%2Cpos_err_c%3A1002%2Cpos_err_m%3At.toString()%7D)%7Dvar%20e%3Dnew%20Image%3Be.src%3Dt%2Ce.style.display%3D%22none%22%2Ce.style.visibility%3D%22hidden%22%2Ce.width%3D0%2Ce.height%3D0%2Cdocument.body.appendChild(e)%7D)%7Dvar%20d%3D%5B%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Fbidder%2Ftrack%3Ftn%3D39934c2bda4debbe4c680be1dd02f5d3%26price%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26evt%3D101%26rid%3D6e28cfaf115a354ea1ad8e1304d6d7b8%26campaignid%3D1339145%26impid%3D44-300x250-1%26offerid%3D24054386%26test%3D0%26time%3D1660789795%26cp%3DjZDh1xu6_QqJLlKVtCkiHIP_TER6gL9jeTrlHCBoxOM%26acid%3D599%26trackingid%3D99afea272c2b0e8626489674ddb7a0bb%26uid%3Da865b9ae-fa9e-4c09-8204-2db99ac7c8f7%26bm%3D2%26la%3Den%26cn%3Dus%26cid%3D3998296%26info%3DSi3oM-qfCbw2iZRYs01BkUWyH6c5CQWHrA8CQLE0VHcXAcf4ljY9dyLzQ4vAlTWd6-j_ou4ySor3e70Ll7wlKiiauQKaUkZqNoTizHm73C4FK8DYJSTP3VkhJV8RzrYk%26sid%3D128__110__1__12__28__38__163__96__58__24__47__99%26sp%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26scp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26acu%3DUSD%26scu%3DUSD%26sgcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26gprice%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26gcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26ah%3D%26de%3Dwjh.popin.cc%26iv%3D0%22%2C%22%24%7BITRACKER2%7D%22%2C%22%24%7BITRACKER3%7D%22%2C%22%24%7BITRACKER4%7D%22%2C%22%24%7BITRACKER5%7D%22%2C%22%24%7BITRACKER6%7D%22%5D%2Cp%3D%5B%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Fbidder%2Ftrack%3Ftn%3D39934c2bda4debbe4c680be1dd02f5d3%26price%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26evt%3D104%26rid%3D6e28cfaf115a354ea1ad8e1304d6d7b8%26campaignid%3D1339145%26impid%3D44-300x250-1%26offerid%3D24054386%26test%3D0%26time%3D1660789795%26cp%3DjZDh1xu6_QqJLlKVtCkiHIP_TER6gL9jeTrlHCBoxOM%26acid%3D599%26trackingid%3D99afea272c2b0e8626489674ddb7a0bb%26uid%3Da865b9ae-fa9e-4c09-8204-2db99ac7c8f7%26sid%3D128__110__1__12__28__38__163__96__58__24__47__99%26format%3D%26crid%3Dff32b6f9b3bbc45c00b78b6674a2952e%26bm%3D2%26la%3Den%26cn%3Dus%26cid%3D3998296%26info%3DSi3oM-qfCbw2iZRYs01BkUWyH6c5CQWHrA8CQLE0VHcXAcf4ljY9dyLzQ4vAlTWd6-j_ou4ySor3e70Ll7wlKiiauQKaUkZqNoTizHm73C4FK8DYJSTP3VkhJV8RzrYk%26sp%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26scp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26acu%3DUSD%26scu%3DUSD%26sgcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26gprice%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26gcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26ah%3D%26de%3Dwjh.popin.cc%26iv%3D0%22%2C%22%24%7BVTRACKER2%7D%22%2C%22%24%7BVTRACKER3%7D%22%2C%22%24%7BVTRACKER4%7D%22%2C%22%24%7BVTRACKER5%7D%22%2C%22%24%7BVTRACKER6%7D%22%5D%2Cs%3D%22f9f2b1ef23fe2759c2cad0953029a94b%22%2Cn%3Ddocument.getElementById(%22mgcontainer-99afea272c2b0e8626489674ddb7a0bb%22)%3Bn%26%26function()%7Bvar%20a%3Dn.getElementsByClassName(%22mediago-placement-track%22)%3Bif(a%26%26a.length)%7Bvar%20t%2Ce%3Dfunction(t)%7Bvar%20e%2Cn%2Co%2Ci%2Cc%2Cr%3B%22object%22%3D%3D%3Df(r%3Da%5Bt%5D)%26%26(e%3Dfunction(t)%7Btry%7Bvar%20e%3Dt.getBoundingClientRect()%2Cn%3De%26%26e.top%7C%7C-1%2Co%3De%26%26e.left%7C%7C-1%2Ci%3Ddocument.body.scrollWidth%7C%7C-1%2Ce%3Ddocument.body.scrollHeight%7C%7C-1%3Breturn%7Btop%3An.toFixed(0)%2Cleft%3Ao.toFixed(0)%2Cpage_width%3Ai%2Cpage_height%3Ae%7D%7Dcatch(o)%7Breturn%20l(%7Btn%3As%2Cwinloss%3A1%2Cfe%3A2%2Cpos_err_c%3A1001%2Cpos_err_m%3Ao.toString()%7D)%2C%7Btop%3A%22-1%22%2Cleft%3A%22-1%22%2Cpage_width%3A%22-1%22%2Cpage_height%3A%22-1%22%7D%7D%7D(r)%2C(n%3Dd%5Bt%5D)%26%26g(n%2C0%2Ce)%2Co%3Dp%5Bt%5D%2Ci%3D!1%2C(c%3Dfunction()%7BsetTimeout(function()%7Bvar%20t%2Ce%3B!i%26%26(t%3Dr%2Ce%3Dwindow.innerHeight%7C%7Cdocument.documentElement.clientHeight%7C%7Cdocument.body.clientHeight%2C(t.getBoundingClientRect()%26%26t.getBoundingClientRect().top)%3C%3De-.75*(t.offsetHeight%7C%7Ct.clientHeight))%3F(i%3D!0%2Co%26%26g(o))%3Ac()%7D%2C500)%7D)())%7D%3Bfor(t%20in%20a)e(t)%7D%7D()%7D()'; + temp += + '!function()%7B%22use%20strict%22%3Bfunction%20f(t)%7Breturn(f%3D%22function%22%3D%3Dtypeof%20Symbol%26%26%22symbol%22%3D%3Dtypeof%20Symbol.iterator%3Ffunction(t)%7Breturn%20typeof%20t%7D%3Afunction(t)%7Breturn%20t%26%26%22function%22%3D%3Dtypeof%20Symbol%26%26t.constructor%3D%3D%3DSymbol%26%26t!%3D%3DSymbol.prototype%3F%22symbol%22%3Atypeof%20t%7D)(t)%7Dfunction%20l(t)%7Bvar%20e%3D0%3Carguments.length%26%26void%200!%3D%3Dt%3Ft%3A%7B%7D%3Btry%7Be.random_t%3D(new%20Date).getTime()%2Cg(function(t)%7Bvar%20e%3D1%3Carguments.length%26%26void%200!%3D%3Darguments%5B1%5D%3Farguments%5B1%5D%3A%22%22%3Bif(%22object%22!%3D%3Df(t))return%20e%3Bvar%20n%3Dfunction(t)%7Bfor(var%20e%2Cn%3D%5B%5D%2Co%3D0%2Ci%3DObject.keys(t)%3Bo%3Ci.length%3Bo%2B%2B)e%3Di%5Bo%5D%2Cn.push(%22%22.concat(e%2C%22%3D%22).concat(t%5Be%5D))%3Breturn%20n%7D(t).join(%22%26%22)%2Co%3De.indexOf(%22%23%22)%2Ci%3De%2Ct%3D%22%22%3Breturn-1!%3D%3Do%26%26(i%3De.slice(0%2Co)%2Ct%3De.slice(o))%2Cn%26%26(i%26%26-1!%3D%3Di.indexOf(%22%3F%22)%3Fi%2B%3D%22%26%22%2Bn%3Ai%2B%3D%22%3F%22%2Bn)%2Ci%2Bt%7D(e%2C%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Flog%2Ftrack%22))%7Dcatch(t)%7B%7D%7Dfunction%20g(t%2Ce%2Cn)%7B(t%3Dt%3Ft.split(%22%3B%3B%3B%22)%3A%5B%5D).map(function(t)%7Btry%7B0%3C%3Dt.indexOf(%22%2Fapi%2Fbidder%2Ftrack%22)%26%26n%26%26(t%2B%3D%22%26inIframe%3D%22.concat(!(!self.frameElement%7C%7C%22IFRAME%22!%3Dself.frameElement.tagName)%7C%7Cwindow.frames.length!%3Dparent.frames.length%7C%7Cself!%3Dtop)%2Ct%2B%3D%22%26pos_x%3D%22.concat(n.left%2C%22%26pos_y%3D%22).concat(n.top%2C%22%26page_w%3D%22).concat(n.page_width%2C%22%26page_h%3D%22).concat(n.page_height))%7Dcatch(t)%7Bl(%7Btn%3As%2Cwinloss%3A1%2Cfe%3A2%2Cpos_err_c%3A1002%2Cpos_err_m%3At.toString()%7D)%7Dvar%20e%3Dnew%20Image%3Be.src%3Dt%2Ce.style.display%3D%22none%22%2Ce.style.visibility%3D%22hidden%22%2Ce.width%3D0%2Ce.height%3D0%2Cdocument.body.appendChild(e)%7D)%7Dvar%20d%3D%5B%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Fbidder%2Ftrack%3Ftn%3D39934c2bda4debbe4c680be1dd02f5d3%26price%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26evt%3D101%26rid%3D6e28cfaf115a354ea1ad8e1304d6d7b8%26campaignid%3D1339145%26impid%3D44-300x250-1%26offerid%3D24054386%26test%3D0%26time%3D1660789795%26cp%3DjZDh1xu6_QqJLlKVtCkiHIP_TER6gL9jeTrlHCBoxOM%26acid%3D599%26trackingid%3D99afea272c2b0e8626489674ddb7a0bb%26uid%3Da865b9ae-fa9e-4c09-8204-2db99ac7c8f7%26bm%3D2%26la%3Den%26cn%3Dus%26cid%3D3998296%26info%3DSi3oM-qfCbw2iZRYs01BkUWyH6c5CQWHrA8CQLE0VHcXAcf4ljY9dyLzQ4vAlTWd6-j_ou4ySor3e70Ll7wlKiiauQKaUkZqNoTizHm73C4FK8DYJSTP3VkhJV8RzrYk%26sid%3D128__110__1__12__28__38__163__96__58__24__47__99%26sp%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26scp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26acu%3DUSD%26scu%3DUSD%26sgcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26gprice%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26gcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26ah%3D%26de%3Dwjh.popin.cc%26iv%3D0%22%2C%22%24%7BITRACKER2%7D%22%2C%22%24%7BITRACKER3%7D%22%2C%22%24%7BITRACKER4%7D%22%2C%22%24%7BITRACKER5%7D%22%2C%22%24%7BITRACKER6%7D%22%5D%2Cp%3D%5B%22https%3A%2F%2Ftrace.mediago.io%2Fapi%2Fbidder%2Ftrack%3Ftn%3D39934c2bda4debbe4c680be1dd02f5d3%26price%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26evt%3D104%26rid%3D6e28cfaf115a354ea1ad8e1304d6d7b8%26campaignid%3D1339145%26impid%3D44-300x250-1%26offerid%3D24054386%26test%3D0%26time%3D1660789795%26cp%3DjZDh1xu6_QqJLlKVtCkiHIP_TER6gL9jeTrlHCBoxOM%26acid%3D599%26trackingid%3D99afea272c2b0e8626489674ddb7a0bb%26uid%3Da865b9ae-fa9e-4c09-8204-2db99ac7c8f7%26sid%3D128__110__1__12__28__38__163__96__58__24__47__99%26format%3D%26crid%3Dff32b6f9b3bbc45c00b78b6674a2952e%26bm%3D2%26la%3Den%26cn%3Dus%26cid%3D3998296%26info%3DSi3oM-qfCbw2iZRYs01BkUWyH6c5CQWHrA8CQLE0VHcXAcf4ljY9dyLzQ4vAlTWd6-j_ou4ySor3e70Ll7wlKiiauQKaUkZqNoTizHm73C4FK8DYJSTP3VkhJV8RzrYk%26sp%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26scp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26acu%3DUSD%26scu%3DUSD%26sgcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26gprice%3DdjUJcggeuWWfbm28q4WXHdgMFkO28DrGw49FnubQ0Bk%26gcp%3DzK0DRYY1UV-syqSpmcMYBpOebtoQJV9ZEJT0JFqbTQg%26ah%3D%26de%3Dwjh.popin.cc%26iv%3D0%22%2C%22%24%7BVTRACKER2%7D%22%2C%22%24%7BVTRACKER3%7D%22%2C%22%24%7BVTRACKER4%7D%22%2C%22%24%7BVTRACKER5%7D%22%2C%22%24%7BVTRACKER6%7D%22%5D%2Cs%3D%22f9f2b1ef23fe2759c2cad0953029a94b%22%2Cn%3Ddocument.getElementById(%22mgcontainer-99afea272c2b0e8626489674ddb7a0bb%22)%3Bn%26%26function()%7Bvar%20a%3Dn.getElementsByClassName(%22mediago-placement-track%22)%3Bif(a%26%26a.length)%7Bvar%20t%2Ce%3Dfunction(t)%7Bvar%20e%2Cn%2Co%2Ci%2Cc%2Cr%3B%22object%22%3D%3D%3Df(r%3Da%5Bt%5D)%26%26(e%3Dfunction(t)%7Btry%7Bvar%20e%3Dt.getBoundingClientRect()%2Cn%3De%26%26e.top%7C%7C-1%2Co%3De%26%26e.left%7C%7C-1%2Ci%3Ddocument.body.scrollWidth%7C%7C-1%2Ce%3Ddocument.body.scrollHeight%7C%7C-1%3Breturn%7Btop%3An.toFixed(0)%2Cleft%3Ao.toFixed(0)%2Cpage_width%3Ai%2Cpage_height%3Ae%7D%7Dcatch(o)%7Breturn%20l(%7Btn%3As%2Cwinloss%3A1%2Cfe%3A2%2Cpos_err_c%3A1001%2Cpos_err_m%3Ao.toString()%7D)%2C%7Btop%3A%22-1%22%2Cleft%3A%22-1%22%2Cpage_width%3A%22-1%22%2Cpage_height%3A%22-1%22%7D%7D%7D(r)%2C(n%3Dd%5Bt%5D)%26%26g(n%2C0%2Ce)%2Co%3Dp%5Bt%5D%2Ci%3D!1%2C(c%3Dfunction()%7BsetTimeout(function()%7Bvar%20t%2Ce%3B!i%26%26(t%3Dr%2Ce%3Dwindow.innerHeight%7C%7Cdocument.documentElement.clientHeight%7C%7Cdocument.body.clientHeight%2C(t.getBoundingClientRect()%26%26t.getBoundingClientRect().top)%3C%3De-.75*(t.offsetHeight%7C%7Ct.clientHeight))%3F(i%3D!0%2Co%26%26g(o))%3Ac()%7D%2C500)%7D)())%7D%3Bfor(t%20in%20a)e(t)%7D%7D()%7D()'; temp += '%3B%3C%2Fscri'; temp += 'pt%3E'; adm += decodeURIComponent(temp); @@ -101,13 +185,13 @@ describe('mediago:BidAdapterTests', function () { cid: '1339145', crid: 'ff32b6f9b3bbc45c00b78b6674a2952e', w: 300, - h: 250, - }, - ], - }, + h: 250 + } + ] + } ], - cur: 'USD', - }, + cur: 'USD' + } }; let bids = spec.interpretResponse(serverResponse); From cb82c47030435998deb4eed41627ff3cc3e1c015 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Thu, 7 Dec 2023 19:03:37 +0300 Subject: [PATCH 252/760] Sovrn Bid Adapter : send coppa signal (#10729) * set coppa from config * minor fix * get coppa from ortb2.regs instead of config --- modules/sovrnBidAdapter.js | 5 ++++ test/spec/modules/sovrnBidAdapter_spec.js | 35 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index f72c482735a..a7f4166579b 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -172,6 +172,11 @@ export const spec = { deepSetValue(sovrnBidReq, 'source.tid', tid) } + const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa'); + if (coppa) { + deepSetValue(sovrnBidReq, 'regs.coppa', 1); + } + if (bidderRequest.gdprConsent) { deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 032f959e559..f165a6da6d1 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -318,6 +318,41 @@ describe('sovrnBidAdapter', function() { expect(data.regs.ext['us_privacy']).to.equal(bidderRequest.uspConsent) }) + it('should not set coppa when coppa is undefined', function () { + const bidderRequest = { + ...baseBidderRequest, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + bids: [baseBidRequest], + gdprConsent: { + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==', + gdprApplies: true + }, + } + const {regs} = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.coppa).to.be.undefined + }) + + it('should set coppa to 1 when coppa is provided with value true', function () { + const bidderRequest = { + ...baseBidderRequest, + ortb2: { + regs: { + coppa: true + } + }, + bidderCode: 'sovrn', + auctionId: '1d1a030790a475', + bidderRequestId: '22edbae2733bf6', + timeout: 3000, + bids: [baseBidRequest] + } + const {regs} = JSON.parse(spec.buildRequests([baseBidRequest], bidderRequest).data) + expect(regs.coppa).to.equal(1) + }) + it('should send gpp info in OpenRTB 2.6 location when gppConsent defined', function () { const bidderRequest = { ...baseBidderRequest, From 4fe6292e874229160b298f6705db6e67bf8f7377 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Thu, 7 Dec 2023 17:12:53 +0100 Subject: [PATCH 253/760] Adnuntius Bid Adapter : forces gdpr=1 flag if consent is required (#10767) * Removed linting issues * Fixed merge issues. --- modules/adnuntiusBidAdapter.js | 46 ++++++++++--------- test/spec/modules/adnuntiusBidAdapter_spec.js | 2 +- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index b2653f16daf..8f841006524 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -18,17 +18,17 @@ const VALID_BID_TYPES = ['netBid', 'grossBid']; const META_DATA_KEY = 'adn.metaData'; export const misc = { - getUnixTimestamp: function(addDays, asMinutes) { + getUnixTimestamp: function (addDays, asMinutes) { const multiplication = addDays / (asMinutes ? 1440 : 1); return Date.now() + (addDays && addDays > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); } }; -const storageTool = (function() { - const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const storageTool = (function () { + const storage = getStorageManager({ bidderCode: BIDDER_CODE }); let metaInternal; - const getMetaInternal = function() { + const getMetaInternal = function () { if (!storage.localStorageIsEnabled()) { return {}; } @@ -57,24 +57,24 @@ const storageTool = (function() { return filteredEntries; }; - const setMetaInternal = function(apiResponse) { + const setMetaInternal = function (apiResponse) { if (!storage.localStorageIsEnabled()) { return; } - const updateVoidAuIds = function(currentVoidAuIds, auIdsAsString) { + const updateVoidAuIds = function (currentVoidAuIds, auIdsAsString) { const newAuIds = auIdsAsString ? auIdsAsString.split(';') : []; const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => { return newAuIds.indexOf(auIdObj.value) < -1; }) || []; const oneDayFromNow = misc.getUnixTimestamp(1); const apiIdsArray = newAuIds.map(auId => { - return {exp: oneDayFromNow, auId: auId}; + return { exp: oneDayFromNow, auId: auId }; }) || []; return notNewExistingAuIds.concat(apiIdsArray) || []; } - const metaAsObj = getMetaInternal().reduce((a, entry) => ({...a, [entry.key]: {value: entry.value, exp: entry.exp}}), {}); + const metaAsObj = getMetaInternal().reduce((a, entry) => ({ ...a, [entry.key]: { value: entry.value, exp: entry.exp } }), {}); for (const key in apiResponse) { if (key !== 'voidAuIds') { metaAsObj[key] = { @@ -85,7 +85,7 @@ const storageTool = (function() { } const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiResponse.voidAuIds || []); if (currentAuIds.length > 0) { - metaAsObj.voidAuIds = {value: currentAuIds}; + metaAsObj.voidAuIds = { value: currentAuIds }; } const metaDataForSaving = Object.entries(metaAsObj).map((entrySet) => { if (entrySet[0] === 'voidAuIds') { @@ -103,7 +103,7 @@ const storageTool = (function() { storage.setDataInLocalStorage(META_DATA_KEY, JSON.stringify(metaDataForSaving)); }; - const getUsi = function(meta, ortb2) { + const getUsi = function (meta, ortb2) { let usi = (meta && meta.usi) ? meta.usi : false; if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id @@ -128,9 +128,9 @@ const storageTool = (function() { } return { - refreshStorage: function(bidderRequest) { + refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; - metaInternal = getMetaInternal().reduce((a, entry) => ({...a, [entry.key]: entry.value}), {}); + metaInternal = getMetaInternal().reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); metaInternal.usi = getUsi(metaInternal, ortb2); if (!metaInternal.usi) { delete metaInternal.usi; @@ -142,21 +142,21 @@ const storageTool = (function() { } metaInternal.segments = getSegmentsFromOrtb(ortb2); }, - saveToStorage: function(serverData) { + saveToStorage: function (serverData) { setMetaInternal(serverData); }, - getUrlRelatedData: function() { - const {segments, usi, voidAuIdsArray} = metaInternal; - return {segments, usi, voidAuIdsArray}; + getUrlRelatedData: function () { + const { segments, usi, voidAuIdsArray } = metaInternal; + return { segments, usi, voidAuIdsArray }; }, - getPayloadRelatedData: function() { - const {segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData} = metaInternal; + getPayloadRelatedData: function () { + const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = metaInternal; return payloadRelatedData; } }; })(); -const validateBidType = function(bidTypeOption) { +const validateBidType = function (bidTypeOption) { return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; } @@ -177,10 +177,13 @@ export const spec = { const queryParamsAndValues = []; queryParamsAndValues.push('tzo=' + new Date().getTimezoneOffset()) queryParamsAndValues.push('format=json') - const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); - if (gdprApplies !== undefined) queryParamsAndValues.push('consentString=' + consentString); + if (gdprApplies !== undefined) { + const flag = gdprApplies ? '1' : '0' + queryParamsAndValues.push('consentString=' + consentString); + queryParamsAndValues.push('gdpr=' + flag); + } storageTool.refreshStorage(bidderRequest); @@ -194,6 +197,7 @@ export const spec = { const bidRequests = {}; const networks = {}; + for (let i = 0; i < validBidRequests.length; i++) { const bid = validBidRequests[i]; if ((urlRelatedMetaData.voidAuIdsArray && (urlRelatedMetaData.voidAuIdsArray.indexOf(bid.params.auId) > -1 || urlRelatedMetaData.voidAuIdsArray.indexOf(bid.params.auId.padStart(16, '0')) > -1))) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 8fa719f7af6..e4902ca11b2 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -42,7 +42,7 @@ describe('adnuntiusBidAdapter', function() { const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&gdpr=1&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ From 8ab882f324ae71ff38252ebc9e7c3a7ac73b485c Mon Sep 17 00:00:00 2001 From: GeoEdge-r-and-d <72186958+GeoEdge-r-and-d@users.noreply.github.com> Date: Thu, 7 Dec 2023 18:23:01 +0200 Subject: [PATCH 254/760] GeoEdge RTD module: collect CPM and curency from the winning bid (#10765) * Collect CPM and cuurency from the winnning bid * Export getMacros for test * Update tests --------- Co-authored-by: daniel manan --- modules/geoedgeRtdProvider.js | 6 ++++-- test/spec/modules/geoedgeRtdProvider_spec.js | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 646d2f4e786..afaaef93169 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -103,7 +103,7 @@ export function wrapHtml(wrapper, html) { * @param {string} key * @return {Object} */ -function getMacros(bid, key) { +export function getMacros(bid, key) { return { '${key}': key, '%%ADUNIT%%': bid.adUnitCode, @@ -116,7 +116,9 @@ function getMacros(bid, key) { '%_hbadomains': bid.meta && bid.meta.advertiserDomains, '%%PATTERN:hb_pb%%': bid.pbHg, '%%SITE%%': location.hostname, - '%_pimp%': PV_ID + '%_pimp%': PV_ID, + '%_hbCpm!': bid.cpm, + '%_hbCurrency!': bid.currency }; } diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 96da2e3dbd7..b0307a60a3c 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -6,6 +6,7 @@ import { getInPageUrl, htmlPlaceholder, setWrapper, + getMacros, wrapper, WRAPPER_URL } from '../../../modules/geoedgeRtdProvider.js'; @@ -117,6 +118,15 @@ describe('Geoedge RTD module', function () { expect(wrapper).to.equal(mockWrapper); }); }); + describe('getMacros', function () { + it('return a dictionary of macros replaced with values from bid object', function () { + let bid = mockBid('testBidder'); + let dict = getMacros(bid, key); + let hasCpm = dict['%_hbCpm!'] === bid.cpm; + let hasCurrency = dict['%_hbCurrency!'] === bid.currency; + expect(hasCpm && hasCurrency); + }); + }); describe('onBidResponseEvent', function () { let bidFromA = mockBid('bidderA'); it('should wrap bid html when bidder is configured', function () { From c6a13cadfc848930f5c6253bab7aa1a95c747fd2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 7 Dec 2023 08:36:22 -0800 Subject: [PATCH 255/760] userId: fix bug where pubProvidedId UIDs are duplicated (#10795) --- modules/userId/eids.js | 4 ++-- test/spec/modules/userId_spec.js | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/userId/eids.js b/modules/userId/eids.js index aad570f20df..e5f7e3b8fb2 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,4 +1,4 @@ -import {deepAccess, isFn, isPlainObject, isStr} from '../../src/utils.js'; +import {deepAccess, deepClone, isFn, isPlainObject, isStr} from '../../src/utils.js'; export const EID_CONFIG = new Map(); @@ -45,7 +45,7 @@ export function createEidsArray(bidRequestUserId) { Object.entries(bidRequestUserId).forEach(([name, values]) => { values = Array.isArray(values) ? values : [values]; - const eids = name === 'pubProvidedId' ? values : values.map(value => createEidObject(value, name)); + const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name)); eids.filter(eid => eid != null).forEach(collect); }) return Object.values(allEids); diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 17a865796a2..18f49f4943e 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -448,7 +448,25 @@ describe('User ID', function () { ] } ]) - }) + }); + + it('when merging with pubCommonId, should not alter its eids', () => { + const uid = { + pubProvidedId: [ + { + source: 'mock1Source', + uids: [ + {id: 'uid2'} + ] + } + ], + mockId1: 'uid1', + }; + const eids = createEidsArray(uid); + expect(eids).to.have.length(1); + expect(eids[0].uids.map(u => u.id)).to.have.members(['uid1', 'uid2']); + expect(uid.pubProvidedId[0].uids).to.eql([{id: 'uid2'}]); + }); }) it('pbjs.getUserIds', function (done) { From 37b7e05bc5044ec452ea224e4cd3f5ea939783d2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 7 Dec 2023 17:16:14 +0000 Subject: [PATCH 256/760] Prebid 8.27.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e08d24d42d5..79a6983b8a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.27.0-pre", + "version": "8.27.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a7dbe2f6905..f0550299023 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.27.0-pre", + "version": "8.27.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 7f815d219b172e426414239c0bc56b4abc606ee0 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 7 Dec 2023 17:16:15 +0000 Subject: [PATCH 257/760] Increment version to 8.28.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79a6983b8a4..1e0990121a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.27.0", + "version": "8.28.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index f0550299023..bca287c1a03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.27.0", + "version": "8.28.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 32ff2569521a04029eb55a8bfb04ccc9551681a0 Mon Sep 17 00:00:00 2001 From: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Thu, 7 Dec 2023 23:11:57 +0200 Subject: [PATCH 258/760] Support UID 2.0 pgamssp module (#10771) * new adapter PGAMSSP * upd * support UID 2.0 * del obj --- modules/pgamsspBidAdapter.js | 19 ++++++++++++++++++- test/spec/modules/pgamsspBidAdapter_spec.js | 1 + 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index 7d285daf3c6..b38131c1c18 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -35,9 +35,14 @@ function getPlacementReqData(bid) { const placement = { bidId, schain, - bidfloor + bidfloor, + eids: [] }; + if (bid.userId) { + getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); + } + if (placementId) { placement.placementId = placementId; placement.type = 'publisher'; @@ -91,6 +96,18 @@ function getBidFloor(bid) { return 0; } } +function getUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } + eids.push({ + source, + uids: [ uid ] + }); + } +} export const spec = { code: BIDDER_CODE, diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 7e2323d4b81..0766219eda8 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -145,6 +145,7 @@ describe('PGAMBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.an('array'); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); From b47a38bb1b47934afd6544a889cf89c9dafe1df9 Mon Sep 17 00:00:00 2001 From: John Ivan Bauzon Date: Fri, 8 Dec 2023 14:14:48 -0800 Subject: [PATCH 259/760] ADSS-1756-pass-gpid-to-video-requests (#10809) Co-authored-by: John Bauzon --- modules/gumgumBidAdapter.js | 6 +++--- test/spec/modules/gumgumBidAdapter_spec.js | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 83fd726fde5..deee906298e 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -345,9 +345,6 @@ function buildRequests(validBidRequests, bidderRequest) { // ADTS-134 Retrieve ID envelopes for (const eid in eids) data[eid] = eids[eid]; - // ADJS-1024 & ADSS-1297 & ADTS-175 - gpid && (data.gpid = gpid); - if (mediaTypes.banner) { sizes = mediaTypes.banner.sizes; } else if (mediaTypes.video) { @@ -355,6 +352,9 @@ function buildRequests(validBidRequests, bidderRequest) { data = _getVidParams(mediaTypes.video); } + // ADJS-1024 & ADSS-1297 & ADTS-175 + gpid && (data.gpid = gpid); + if (pageViewId) { data.pv = pageViewId; } diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index f541a69abe8..e0529a895f5 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -297,6 +297,14 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.gpid).to.equal(pbadslot); }); + it('should set the global placement id (gpid) if media type is video', function () { + const pbadslot = 'cde456' + const req = { ...bidRequests[0], ortb2Imp: { ext: { data: { pbadslot } } }, params: zoneParam, mediaTypes: vidMediaTypes } + const bidRequest = spec.buildRequests([req])[0]; + expect(bidRequest.data).to.have.property('gpid'); + expect(bidRequest.data.gpid).to.equal(pbadslot); + }); + it('should set the bid floor if getFloor module is not present but static bid floor is defined', function () { const req = { ...bidRequests[0], params: { bidfloor: 42 } } const bidRequest = spec.buildRequests([req])[0]; From 260269d33c6d1125c7a9926d24e52f34021c8f27 Mon Sep 17 00:00:00 2001 From: Omer Dotan <54346241+omerBrowsi@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:49:43 +0200 Subject: [PATCH 260/760] Browsi RTD : fix for targeting (#10814) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * browsi-rtd-targeting-fix --- modules/browsiRtdProvider.js | 4 ++-- test/spec/modules/browsiRtdProvider_spec.js | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 4a61f40600d..1f37c7c5bc0 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -88,6 +88,7 @@ export function collectData() { let predictorData = { ...{ sk: _moduleParams.siteKey, + pk: _moduleParams.pubKey, sw: (win.screen && win.screen.width) || -1, sh: (win.screen && win.screen.height) || -1, url: `${doc.location.protocol}//${doc.location.host}${doc.location.pathname}`, @@ -134,7 +135,6 @@ function getRTD(auc) { const adSlot = getSlotByCode(uc); const identifier = adSlot ? getMacroId(_browsiData['pmd'], adSlot) : uc; const _pd = _bp[identifier]; - rp[uc] = getKVObject(-1); if (!_pd) { return rp } @@ -275,7 +275,7 @@ function getPredictionsFromServer(url) { if (req.status === 200) { try { const data = JSON.parse(response); - if (data && data.p && data.kn) { + if (data) { setData({p: data.p, kn: data.kn, pmd: data.pmd, bet: data.bet}); } else { setData({}); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 75120aa7505..5fcc78f4322 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -89,12 +89,6 @@ describe('browsi Real time data sub module', function () { expect(browsiRTD.browsiSubmodule.getTargetingData([], null, null, auction)).to.eql({}); }); - it('should return NA if no prediction for ad unit', function () { - makeSlot({code: 'adMock', divId: 'browsiAd_2'}); - browsiRTD.setData({}); - expect(browsiRTD.browsiSubmodule.getTargetingData(['adMock'], null, null, auction)).to.eql({adMock: {bv: 'NA'}}); - }); - it('should return prediction from server', function () { makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { From 2493f985e9049c8f0071e02a1f6d45c1fc0a856d Mon Sep 17 00:00:00 2001 From: Malkov Mikhail Date: Mon, 11 Dec 2023 17:30:25 +0300 Subject: [PATCH 261/760] nextMillennium Bid Adapter : add pbjs version and support for user.eids (#10812) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua --- modules/nextMillenniumBidAdapter.js | 40 +++++++++- .../modules/nextMillenniumBidAdapter_spec.js | 73 +++++++++++++++++++ 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 26d417b9baf..5dea4f9b651 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -14,6 +14,7 @@ import { triggerPixel, } from '../src/utils.js'; +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'; @@ -22,7 +23,7 @@ import * as events from '../src/events.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '3.0.0'; +const NM_VERSION = '3.1.0'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; @@ -47,6 +48,7 @@ const ALLOWED_ORTB2_PARAMETERS = [ 'site.pagecat', 'site.content.cat', 'site.content.language', + 'device.sua', ]; const sendingDataStatistic = initSendingDataStatistic(); @@ -93,6 +95,7 @@ export const spec = { nextMillennium: { nm_version: NM_VERSION, + pbjs_version: getGlobal()?.version || undefined, refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++, elOffsets: getBoundingClient(bid), scrollTop: window.pageYOffset || document.documentElement.scrollTop, @@ -107,6 +110,7 @@ export const spec = { postBody.imp.push(getImp(bid, id)); setConsentStrings(postBody, bidderRequest); setOrtb2Parameters(postBody, bidderRequest?.ortb2); + setEids(postBody, bid); const urlParameters = parseUrl(getWindowTop().location.href).search; const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; @@ -312,6 +316,12 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) { } } +export function setEids(postBody, bid) { + if (!isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; + + deepSetValue(postBody, 'user.eids', bid.userIdAsEids); +} + export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') { const { consentString = '', gdprApplies = false } = gdprConsent; const gdpr = Number(gdprApplies); @@ -391,7 +401,7 @@ function getAd(bid) { } else if (bid.nurl) { adUrl = bid.nurl; }; - } + }; return {ad, adUrl, vastXml, vastUrl}; } @@ -399,10 +409,21 @@ function getAd(bid) { function getSiteObj() { const refInfo = (getRefererInfo && getRefererInfo()) || {}; + let language = navigator.language; + let content; + if (language) { + // get ISO-639-1-alpha-2 (2 character language) + language = language.split('-')[0]; + content = { + language, + }; + }; + return { page: refInfo.page, ref: refInfo.ref, - domain: refInfo.domain + domain: refInfo.domain, + content, }; } @@ -410,6 +431,19 @@ function getDeviceObj() { return { w: window.innerWidth || window.document.documentElement.clientWidth || window.document.body.clientWidth || 0, h: window.innerHeight || window.document.documentElement.clientHeight || window.document.body.clientHeight || 0, + ua: window.navigator.userAgent || undefined, + sua: getSua(), + }; +} + +function getSua() { + let {brands, mobile, platform} = (window?.navigator?.userAgentData || {}); + if (!(brands && platform)) return undefined; + + return { + brands, + mobile: Number(!!mobile), + platform: (platform && {brand: platform}) || undefined, }; } diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 8b512218d56..f57db82aedc 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -4,6 +4,7 @@ import { replaceUsersyncMacros, setConsentStrings, setOrtb2Parameters, + setEids, spec, } from 'modules/nextMillenniumBidAdapter.js'; @@ -403,6 +404,78 @@ describe('nextMillenniumBidAdapterTests', () => { setOrtb2Parameters(postBody, ortb2); expect(postBody).to.deep.equal(expected); }); + }; + }); + + describe('function setEids', () => { + const dataTests = [ + { + title: 'setEids - userIdAsEids is empty', + data: { + postBody: {}, + bid: { + userIdAsEids: undefined, + }, + }, + + expected: {}, + }, + + { + title: 'setEids - userIdAsEids - array is empty', + data: { + postBody: {}, + bid: { + userIdAsEids: [], + }, + }, + + expected: {}, + }, + + { + title: 'setEids - userIdAsEids is', + data: { + postBody: {}, + bid: { + userIdAsEids: [ + { + source: '33across.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + + { + source: 'utiq.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + }, + + expected: { + user: { + eids: [ + { + source: '33across.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + + { + source: 'utiq.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + }, + }, + ]; + + for (let { title, data, expected } of dataTests) { + it(title, () => { + const { postBody, bid } = data; + setEids(postBody, bid); + expect(postBody).to.deep.equal(expected); + }); } }); From eecdc2e92d0f186ce897dda5f4aa0ea9fec66529 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 11 Dec 2023 16:07:26 -0500 Subject: [PATCH 262/760] Update deepintentBidAdapter.js (#10818) --- modules/deepintentBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index e062686b320..7c24cd6a8f6 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -2,6 +2,7 @@ import { generateUUID, deepSetValue, deepAccess, isArray, isInteger, logError, l import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'deepintent'; +const GVL_ID = 541; const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html'; const DI_M_V = '1.0.0'; @@ -32,6 +33,7 @@ export const ORTB_VIDEO_PARAMS = { }; export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [BANNER, VIDEO], aliases: [], From 222b4428c36a71e898836ff3477ed2a3b88cea01 Mon Sep 17 00:00:00 2001 From: Chris Southern <79725079+southern-growthcode@users.noreply.github.com> Date: Mon, 11 Dec 2023 16:14:46 -0500 Subject: [PATCH 263/760] GC-158 When accessing the TCF it is pulling from the wrong location, use userConsent.gdpr.consentString (#10808) --- modules/growthCodeRtdProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/growthCodeRtdProvider.js b/modules/growthCodeRtdProvider.js index e7dce81f7d0..b12b25a0951 100644 --- a/modules/growthCodeRtdProvider.js +++ b/modules/growthCodeRtdProvider.js @@ -81,8 +81,8 @@ function callServer(configParams, items, expiresAt, userConsent) { url = tryAppendQueryString(url, 'pid', configParams.pid); url = tryAppendQueryString(url, 'u', window.location.href); url = tryAppendQueryString(url, 'gcid', gcid); - if ((userConsent !== null) && (userConsent.gdpr !== null) && (userConsent.gdpr.consentData.getTCData.tcString)) { - url = tryAppendQueryString(url, 'tcf', userConsent.gdpr.consentData.getTCData.tcString) + if ((userConsent !== null) && (userConsent.gdpr !== null) && (userConsent.gdpr.consentString)) { + url = tryAppendQueryString(url, 'tcf', userConsent.gdpr.consentString) } ajax.ajaxBuilder()(url, { From 3ec81e61e6cb51c4a9420427c1ca9c504a7848a3 Mon Sep 17 00:00:00 2001 From: Olivier Date: Mon, 11 Dec 2023 22:44:48 +0100 Subject: [PATCH 264/760] Adagio Analytics Adapter: listen to AUCTION_END (#10798) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adagio Analytics Adapter: add tracker on auction_end * Adagio Analytics Adapter: refacto metadata transfer * Adagio Analytics Adapter: update pba props --------- Co-authored-by: François Rotta --- modules/adagioAnalyticsAdapter.js | 58 +++++++++- .../modules/adagioAnalyticsAdapter_spec.js | 109 +++++++++++++----- 2 files changed, 135 insertions(+), 32 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 9c4c0e8fea7..ee2368fde08 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -22,6 +22,12 @@ const cache = { getAuction: function(auctionId, adUnitCode) { return this.auctions[auctionId][adUnitCode]; }, + getBiddersFromAuction: function(auctionId, adUnitCode) { + return this.getAuction(auctionId, adUnitCode).bdrs.split(','); + }, + getAllAdUnitCodes: function(auctionId) { + return Object.keys(this.auctions[auctionId]); + }, updateAuction: function(auctionId, adUnitCode, values) { this.auctions[auctionId][adUnitCode] = { ...this.auctions[auctionId][adUnitCode], @@ -74,7 +80,8 @@ const adagioEnqueue = function adagioEnqueue(action, data) { const guard = { adagio: (value) => isAdagio(value), - bidTracked: (auctionId, adUnitCode) => deepAccess(cache, `auctions.${auctionId}.${adUnitCode}`, false) + bidTracked: (auctionId, adUnitCode) => deepAccess(cache, `auctions.${auctionId}.${adUnitCode}`, false), + auctionTracked: (auctionId) => deepAccess(cache, `auctions.${auctionId}`, false) }; function removeDuplicates(arr, getKey) { @@ -105,6 +112,19 @@ function getMediaTypeAlias(mediaType) { return mediaTypesMap[mediaType] || mediaType; }; +function addKeyPrefix(obj, prefix) { + return Object.keys(obj).reduce((acc, key) => { + // We don't want to prefix already prefixed keys. + if (key.startsWith(prefix)) { + acc[key] = obj[key]; + return acc; + } + + acc[`${prefix}${key}`] = obj[key]; + return acc; + }, {}); +} + /** * sendRequest to Adagio. It filter null values and encode each query param. * @param {Object} qp @@ -146,6 +166,7 @@ function getTargetedAuctionId(bid) { * HANDLERS * - handlerAuctionInit * - handlerBidResponse + * - handlerAuctionEnd * - handlerBidWon * - handlerAdRender * @@ -227,11 +248,10 @@ function handlerAuctionInit(event) { auct_id: adagioAuctionId, adu_code: adUnitCode, url_dmn: w.location.hostname, - dvc: params.environment, pgtyp: params.pagetype, plcmt: params.placement, - tname: params.testName || null, - tvname: params.testVariationName || null, + t_n: params.testName || null, + t_v: params.testVersion || null, mts: mediaTypesKeys.join(','), ban_szs: bannerSizes.join(','), bdrs: bidders.map(bidder => getAdapterNameForAlias(bidder.bidder)).sort().join(','), @@ -257,11 +277,33 @@ function handlerBidResponse(event) { return; } + if (!event.pba) { + return; + } + cache.updateAuction(event.auctionId, event.adUnitCode, { - adg_sid: event.seatId || null + ...addKeyPrefix(event.pba, 'e_') }); }; +function handlerAuctionEnd(event) { + const { auctionId } = event; + + if (!guard.auctionTracked(auctionId)) { + return; + } + + const adUnitCodes = cache.getAllAdUnitCodes(auctionId); + adUnitCodes.forEach(adUnitCode => { + const mapper = (bidder) => event.bidsReceived.find(bid => bid.adUnitCode === adUnitCode && bid.bidder === bidder) ? '1' : '0'; + + cache.updateAuction(auctionId, adUnitCode, { + bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(mapper).join(',') + }); + sendNewBeacon(auctionId, adUnitCode); + }); +} + function handlerBidWon(event) { let auctionId = getTargetedAuctionId(event); @@ -340,10 +382,14 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { case CONSTANTS.EVENTS.BID_RESPONSE: handlerBidResponse(args); break; + case CONSTANTS.EVENTS.AUCTION_END: + handlerAuctionEnd(args); + break; case CONSTANTS.EVENTS.BID_WON: handlerBidWon(args); break; - case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + // AD_RENDER_SUCCEEDED seems redundant with BID_WON. + // case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: case CONSTANTS.EVENTS.AD_RENDER_FAILED: handlerAdRender(args, eventType === CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED); break; diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 8359a34baa0..5ffd7b0b685 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -211,7 +211,10 @@ const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { meta: { advertiserDomains: ['example.com'] }, - seatId: '42', + pba: { + sid: '42', + e_pba_test: true + } }); const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { @@ -256,7 +259,9 @@ const PARAMS_ADG = { pageviewId: 'a68e6d70-213b-496c-be0a-c468ff387106', environment: 'desktop', pagetype: 'article', - placement: 'pave_top' + placement: 'pave_top', + testName: 'test', + testVersion: 'version', }; const AUCTION_INIT_ANOTHER = { @@ -285,6 +290,11 @@ const AUCTION_INIT_ANOTHER = { 'params': { 'publisherId': '1001' }, + }, { + 'bidder': 'nobid', + 'params': { + 'publisherId': '1002' + }, }, { 'bidder': 'adagio', 'params': { @@ -354,6 +364,24 @@ const AUCTION_INIT_ANOTHER = { 'auctionId': AUCTION_ID, 'src': 'client', 'bidRequestsCount': 1 + }, { + 'bidder': 'nobid', + 'params': { + 'publisherId': '1001' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/footer-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 } ], 'timeout': 3000, @@ -533,6 +561,14 @@ const AUCTION_INIT_CACHE = { 'timeout': 3000 }; +const AUCTION_END_ANOTHER = Object.assign({}, AUCTION_INIT_ANOTHER, { + bidsReceived: [BID_ANOTHER, BID_ADAGIO] +}); + +const AUCTION_END_ANOTHER_NOBID = Object.assign({}, AUCTION_INIT_ANOTHER, { + bidsReceived: [] +}); + const MOCK = { SET_TARGETING: { [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, @@ -546,6 +582,10 @@ const MOCK = { adagio: BID_ADAGIO, another: BID_ANOTHER }, + AUCTION_END: { + another: AUCTION_END_ANOTHER, + another_nobid: AUCTION_END_ANOTHER_NOBID + }, BID_WON: { adagio: Object.assign({}, BID_ADAGIO, { 'status': 'rendered' @@ -569,6 +609,12 @@ const MOCK = { bid: BID_CACHED } }, + AD_RENDER_FAILED: { + bidcached: { + adId: 'fake_ad_id_2', + bid: BID_CACHED + } + } }; describe('adagio analytics adapter', () => { @@ -616,10 +662,11 @@ describe('adagio analytics adapter', () => { events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another); events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); - expect(server.requests.length).to.equal(3); + expect(server.requests.length).to.equal(3, 'requests count'); { const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); @@ -633,12 +680,11 @@ describe('adagio analytics adapter', () => { expect(search.site).to.equal('test-com'); expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); - expect(search.dvc).to.equal('desktop'); expect(search.pgtyp).to.equal('article'); expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another'); + expect(search.bdrs).to.equal('adagio,another,nobid'); expect(search.adg_mts).to.equal('ban'); } @@ -648,9 +694,19 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.e_sid).to.equal('42'); + expect(search.e_pba_test).to.equal('true'); + expect(search.bdrs_bid).to.equal('1,1,0'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('3'); expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); - expect(search.adg_sid).to.equal('42'); expect(search.win_bdr).to.equal('another'); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); @@ -661,17 +717,6 @@ describe('adagio analytics adapter', () => { expect(search.og_cur).to.equal('GBP'); expect(search.og_cur_rate).to.equal('1.6'); } - - { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); - expect(protocol).to.equal('https'); - expect(hostname).to.equal('c.4dex.io'); - expect(pathname).to.equal('/pba.gif'); - expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); - expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); - expect(search.rndr).to.equal('1'); - } }); it('builds and sends auction data with a cached bid win', () => { @@ -691,10 +736,11 @@ describe('adagio analytics adapter', () => { events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another_nobid); events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.bidcached); - events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.bidcached); + events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED.bidcached); - expect(server.requests.length).to.equal(4); + expect(server.requests.length).to.equal(5, 'requests count'); { const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); @@ -708,13 +754,14 @@ describe('adagio analytics adapter', () => { expect(search.site).to.equal('test-com'); expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); - expect(search.dvc).to.equal('desktop'); expect(search.pgtyp).to.equal('article'); expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another'); expect(search.adg_mts).to.equal('ban'); + expect(search.t_n).to.equal('test'); + expect(search.t_v).to.equal('version'); } { @@ -730,12 +777,11 @@ describe('adagio analytics adapter', () => { expect(search.site).to.equal('test-com'); expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); expect(search.url_dmn).to.equal(window.location.hostname); - expect(search.dvc).to.equal('desktop'); expect(search.pgtyp).to.equal('article'); expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another'); + expect(search.bdrs).to.equal('adagio,another,nobid'); expect(search.adg_mts).to.equal('ban'); } @@ -745,10 +791,20 @@ describe('adagio analytics adapter', () => { expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.e_sid).to.equal('42'); + expect(search.e_pba_test).to.equal('true'); + expect(search.bdrs_bid).to.equal('0,0,0'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('3'); expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); - expect(search.adg_sid).to.equal('42'); expect(search.win_bdr).to.equal('adagio'); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); @@ -758,18 +814,19 @@ describe('adagio analytics adapter', () => { expect(search.og_cpm).to.equal('1.42'); expect(search.og_cur).to.equal('USD'); expect(search.og_cur_rate).to.equal('1'); + expect(search.rndr).to.not.exist; } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); - expect(search.v).to.equal('3'); + expect(search.v).to.equal('4'); expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); - expect(search.rndr).to.equal('1'); + expect(search.rndr).to.equal('0'); } }); }); From 6275c00cdc47fefd521b4a1f8cbae44944560367 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Tue, 12 Dec 2023 03:31:13 +0530 Subject: [PATCH 265/760] PubMaticAnalyticAdapter : Added new fields related to floors (fetch status, source of floor, provider and unique id) in analytics call (#10768) * Added floors field to logger call * Added sid in logger records --- modules/pubmaticAnalyticsAdapter.js | 30 ++++++++--- src/constants.json | 4 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 51 +++++++++++++++++++ 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index a8f2b8d20ee..fbc45f1a8d7 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {_each, isArray, isStr, logError, logWarn, pick} from '../src/utils.js'; +import {_each, isArray, isStr, logError, logWarn, pick, generateUUID} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import CONSTANTS from '../src/constants.json'; @@ -286,7 +286,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, '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 ? bid.bidResponse.floorData.floorRuleValue : undefined) : undefined), + 'frv': bid.bidResponse ? bid.bidResponse.floorData?.floorRuleValue : undefined, 'md': bid.bidResponse ? getMetadata(bid.bidResponse.meta) : undefined }); }); @@ -337,11 +337,10 @@ function executeBidsLoggerCall(e, highestCpmBids) { let auctionId = e.auctionId; let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; let auctionCache = cache.auctions[auctionId]; - let floorData = auctionCache.floorData; + let floorData = auctionCache?.floorData; + let floorFetchStatus = getFloorFetchStatus(auctionCache?.floorData); let outputObj = { s: [] }; let pixelURL = END_POINT_BID_LOGGER; - // will return true if floor data is present. - let fetchStatus = getFloorFetchStatus(auctionCache.floorData); if (!auctionCache) { return; @@ -364,7 +363,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['tgid'] = getTgId(); outputObj['pbv'] = getGlobal()?.version || '-1'; - if (floorData && fetchStatus) { + if (floorData && floorFetchStatus) { outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; outputObj['ft'] = floorData.floorResponseData ? (floorData.floorResponseData.enforcements.enforceJS == false ? 0 : 1) : undefined; } @@ -379,8 +378,25 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'mt': getAdUnitAdFormats(origAdUnit), 'sz': getSizesForAdUnit(adUnit, adUnitId), 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), - 'fskp': (floorData && fetchStatus) ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, + 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, + 'sid': generateUUID() }; + if (floorData?.floorRequestData) { + const { location, fetchStatus, floorProvider } = floorData?.floorRequestData; + slotObject.ffs = { + [CONSTANTS.FLOOR_VALUES.SUCCESS]: 1, + [CONSTANTS.FLOOR_VALUES.ERROR]: 2, + [CONSTANTS.FLOOR_VALUES.TIMEOUT]: 4, + undefined: 0 + }[fetchStatus]; + slotObject.fsrc = { + [CONSTANTS.FLOOR_VALUES.FETCH]: 2, + [CONSTANTS.FLOOR_VALUES.NO_DATA]: 2, + [CONSTANTS.FLOOR_VALUES.AD_UNIT]: 1, + [CONSTANTS.FLOOR_VALUES.SET_CONFIG]: 1 + }[location]; + slotObject.fp = floorProvider; + } slotsArray.push(slotObject); return slotsArray; }, []); diff --git a/src/constants.json b/src/constants.json index ad0f5b3a71b..c8266ff85f8 100644 --- a/src/constants.json +++ b/src/constants.json @@ -176,6 +176,8 @@ "AD_UNIT": "adUnit", "SET_CONFIG": "setConfig", "FETCH": "fetch", - "SUCCESS": "success" + "SUCCESS": "success", + "ERROR": "error", + "TIMEOUT": "timeout" } } diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 7fce08fc24f..6af497bd2e9 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -374,6 +374,10 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); + expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -403,6 +407,10 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -583,6 +591,10 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); + expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); @@ -702,6 +714,13 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(0);// test group id should be an INT between 0-15 else set to 0 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + + expect(data.s[1].sid).not.to.be.undefined; + + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -784,6 +803,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -846,6 +869,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[1].ps[0].bc).to.equal('pubmatic'); @@ -893,6 +917,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -952,6 +980,7 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1006,6 +1035,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1063,6 +1096,7 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1121,7 +1155,11 @@ describe('pubmatic analytics adapter', function () { // Testing only for rejected bid as other scenarios will be covered under other TCs expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1196,7 +1234,11 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); @@ -1226,7 +1268,11 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1318,7 +1364,11 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); + expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); @@ -1348,6 +1398,7 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); From 048f735ccea9907ca72f068042bd35394c58689b Mon Sep 17 00:00:00 2001 From: pm-priyanka-deshmane <107103300+pm-priyanka-deshmane@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:05:14 +0530 Subject: [PATCH 266/760] Pubmatic Analytics Adapter : skip duplicate data for pubmatic bids in logger and tracker (#10787) * Adding config for openwrap metadapter * Changed from http to https and timeout to 500 * Timeout value update * 1.Added safechecks for s2s metadapter case 2.Skipped firing client side tracker for pubmatic 3.Skipped adding pubmatic bid in logger * Analytics adapter changes to avoid duplicate data in case of OW S2S setup * Moved to bottom * kick off tests --------- Co-authored-by: Chris Huie --- modules/pubmaticAnalyticsAdapter.js | 38 ++- .../modules/pubmaticAnalyticsAdapter_spec.js | 248 ++++++++++++++++-- 2 files changed, 259 insertions(+), 27 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index fbc45f1a8d7..66898301cd0 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -9,6 +9,7 @@ import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; +const VENDOR_OPENWRAP = 'openwrap'; const SEND_TIMEOUT = 2000; const END_POINT_HOST = 'https://t.pubmatic.com/'; const END_POINT_BID_LOGGER = END_POINT_HOST + 'wl?'; @@ -258,12 +259,27 @@ function isS2SBidder(bidder) { return (s2sBidders.indexOf(bidder) > -1) ? 1 : 0 } +function isOWPubmaticBid(adapterName) { + let s2sConf = config.getConfig('s2sConfig'); + let s2sConfArray = isArray(s2sConf) ? s2sConf : [s2sConf]; + return s2sConfArray.some(conf => { + if (adapterName === ADAPTER_CODE && conf.defaultVendor === VENDOR_OPENWRAP && + conf.bidders.indexOf(ADAPTER_CODE) > -1) { + return true; + } + }) +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { + let adapterName = getAdapterNameForAlias(bid.adapterCode || bid.bidder); + if (isOWPubmaticBid(adapterName) && isS2SBidder(bid.bidder)) { + return; + } partnerBids.push({ - 'pn': getAdapterNameForAlias(bid.adapterCode || bid.bidder), + 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, 'bidid': bid.bidId || bidId, 'db': bid.bidResponse ? 0 : 1, @@ -418,13 +434,20 @@ function executeBidsLoggerCall(e, highestCpmBids) { function executeBidWonLoggerCall(auctionId, adUnitId) { const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; const winningBids = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; - let winningBid = winningBids[0]; + if (!winningBids) { + logWarn(LOG_PRE_FIX + 'Could not find winningBids for : ', auctionId); + return; + } + let winningBid = winningBids[0]; if (winningBids.length > 1) { winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId].adUnitCodes[adUnitId].bidWonAdId)[0]; } const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); + if (isOWPubmaticBid(adapterName) && isS2SBidder(winningBid.bidder)) { + return; + } let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; let auctionCache = cache.auctions[auctionId]; let floorData = auctionCache.floorData; @@ -477,7 +500,10 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { function auctionInitHandler(args) { s2sBidders = (function() { let s2sConf = config.getConfig('s2sConfig'); - return (s2sConf && isArray(s2sConf.bidders)) ? s2sConf.bidders : []; + let s2sBidders = []; + (s2sConf || []) && + isArray(s2sConf) ? s2sConf.map(conf => s2sBidders.push(...conf.bidders)) : s2sBidders.push(...s2sConf.bidders); + return s2sBidders || []; }()); let cacheEntry = pick(args, [ 'timestamp', @@ -508,6 +534,10 @@ function bidRequestedHandler(args) { } function bidResponseHandler(args) { + if (!args.requestId) { + logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); + return; + } let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); @@ -572,7 +602,7 @@ function auctionEndHandler(args) { let highestCpmBids = getGlobal().getHighestCpmBids() || []; setTimeout(() => { executeBidsLoggerCall.call(this, args, highestCpmBids); - }, (cache.auctions[args.auctionId].bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); + }, (cache.auctions[args.auctionId]?.bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); } function bidTimeoutHandler(args) { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 6af497bd2e9..fe31361d3ba 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -380,8 +380,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); - expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -394,8 +394,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(1); expect(data.s[0].ps[0].t).to.equal(0); @@ -429,7 +429,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -467,7 +467,7 @@ describe('pubmatic analytics adapter', function () { expect(data.af).to.equal('video'); }); - it('Logger: do not log floor fields when prebids floor shows noData in location property', function() { + it('Logger : do not log floor fields when prebids floor shows noData in location property', function() { const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); BID_REQUESTED_COPY['bids'][1]['floorData']['location'] = 'noData'; @@ -597,7 +597,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); @@ -671,7 +671,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -823,8 +823,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(0); - expect(data.s[0].ps[0].ol1).to.equal(0); + expect(data.s[0].ps[0].l1).to.equal(0); + expect(data.s[0].ps[0].ol1).to.equal(0); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(1); @@ -885,7 +885,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -938,7 +938,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -997,7 +997,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1056,7 +1056,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1113,7 +1113,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1177,7 +1177,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1240,7 +1240,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); @@ -1255,7 +1255,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1289,8 +1289,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1370,7 +1370,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('groupm'); @@ -1385,7 +1385,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1416,7 +1416,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1501,4 +1501,206 @@ describe('pubmatic analytics adapter', function () { expect(metadataObj).to.equal(undefined); }); }); + + describe('OW S2S', function() { + this.beforeEach(function() { + pubmaticAnalyticsAdapter.enableAnalytics({ + options: { + publisherId: 9999, + profileId: 1111, + profileVersionId: 20 + } + }); + config.setConfig({ + s2sConfig: { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap', + timeout: 500 + } + }); + }); + + this.afterEach(function() { + pubmaticAnalyticsAdapter.disableAnalytics(); + }); + + it('Pubmatic Won: No tracker fired', function() { + this.timeout(5000) + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(1); // only logger is fired + let request = requests[0]; + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + }); + + it('Non-pubmatic won: logger, tracker fired', function() { + const APPNEXUS_BID = Object.assign({}, BID, { + 'bidder': 'appnexus', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '2ecff0db240757', + 'hb_pb': 1.20, + 'hb_size': '640x480', + 'hb_source': 'server' + } + }); + + const MOCK_AUCTION_INIT_APPNEXUS = { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'appnexus', + 'params': { + 'publisherId': '1001' + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'appnexus', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'appnexus', + 'params': { + 'publisherId': '1001', + 'kgpv': 'this-is-a-kgpv' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 + }; + + const MOCK_BID_REQUESTED_APPNEXUS = { + 'bidder': 'appnexus', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'appnexus', + 'adapterCode': 'appnexus', + 'bidderCode': 'appnexus', + 'params': { + 'publisherId': '1001', + 'video': { + 'minduration': 30, + 'skippable': true + } + }, + 'mediaType': 'video', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ], + 'auctionStart': 1519149536560, + 'timeout': 5000, + 'start': 1519149562216, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'gdprConsent': { + 'consentString': 'here-goes-gdpr-consent-string', + 'gdprApplies': true + } + }; + + this.timeout(5000) + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [APPNEXUS_BID] + }); + + events.emit(AUCTION_INIT, MOCK_AUCTION_INIT_APPNEXUS); + events.emit(BID_REQUESTED, MOCK_BID_REQUESTED_APPNEXUS); + events.emit(BID_RESPONSE, APPNEXUS_BID); + events.emit(BIDDER_DONE, { + 'bidderCode': 'appnexus', + 'bids': [ + APPNEXUS_BID, + Object.assign({}, APPNEXUS_BID, { + 'serverResponseTimeMs': 42, + }) + ] + }); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, { + [APPNEXUS_BID.adUnitCode]: APPNEXUS_BID.adserverTargeting, + }); + events.emit(BID_WON, Object.assign({}, APPNEXUS_BID, { + 'status': 'rendered' + })); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(2); // logger as well as tracker is fired + let request = requests[1]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(decodeURIComponent(data.purl)).to.equal('http://www.test.com/page.html'); + + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('appnexus'); + expect(data.s[0].ps[0].bc).to.equal('appnexus'); + }) + }); }); From 947a487550e3fea398ae6887287635cf755bacb8 Mon Sep 17 00:00:00 2001 From: Marco Muths Date: Tue, 12 Dec 2023 17:50:29 +0100 Subject: [PATCH 267/760] support ad unit name (#10825) --- modules/teadsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index ba16c6ddf82..4d8a55f6031 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -262,7 +262,7 @@ function buildRequestObject(bid) { reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); reqObj.placementId = parseInt(placementId, 10); reqObj.pageId = parseInt(pageId, 10); - reqObj.adUnitCode = getBidIdParameter('adUnitCode', bid); + reqObj.adUnitCode = getValue(bid.params, 'adUnitCode') || getBidIdParameter('adUnitCode', bid); reqObj.transactionId = bid.ortb2Imp?.ext?.tid || ''; if (gpid) { reqObj.gpid = gpid; } if (videoPlcmt) { reqObj.videoPlcmt = videoPlcmt; } From 5dbbf1ba39ae894cc9628ac1e9aafc62e79feef9 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Tue, 12 Dec 2023 18:13:38 +0100 Subject: [PATCH 268/760] PBjs Core : new event fired before add bid response (#10685) * new event before add bid response * test new event is emit * change new event name --- modules/rtdModule/index.js | 3 ++- src/auction.js | 2 +- src/constants.json | 3 ++- test/spec/unit/pbjs_api_spec.js | 7 +++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 633c4f4cdc1..fb6e65e8a65 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -215,7 +215,8 @@ const setEventsListeners = (function () { [CONSTANTS.EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], [CONSTANTS.EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], [CONSTANTS.EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], - [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'] + [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'], + [CONSTANTS.EVENTS.BID_ACCEPTED]: ['onBidAcceptedEvent'] }).forEach(([ev, [handler, preprocess]]) => { events.on(ev, (args) => { preprocess && preprocess(args); diff --git a/src/auction.js b/src/auction.js index 0d256bc3cd7..10b28ad44c9 100644 --- a/src/auction.js +++ b/src/auction.js @@ -455,7 +455,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM function acceptBidResponse(adUnitCode, bid) { handleBidResponse(adUnitCode, bid, (done) => { let bidResponse = getPreparedBidForAuction(bid); - + events.emit(CONSTANTS.EVENTS.BID_ACCEPTED, bidResponse); if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { tryAddVideoBid(auctionInstance, bidResponse, done); } else { diff --git a/src/constants.json b/src/constants.json index c8266ff85f8..24882c160f8 100644 --- a/src/constants.json +++ b/src/constants.json @@ -46,7 +46,8 @@ "AUCTION_DEBUG": "auctionDebug", "BID_VIEWABLE": "bidViewable", "STALE_RENDER": "staleRender", - "BILLABLE_EVENT": "billableEvent" + "BILLABLE_EVENT": "billableEvent", + "BID_ACCEPTED": "bidAccepted" }, "AD_RENDER_FAILED_REASON": { "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 00eedb9a2b5..39123d4aa41 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -2728,6 +2728,13 @@ describe('Unit: Prebid Module', function () { events.on.restore(); }); + it('should emit event BID_ACCEPTED when invoked', function () { + var callback = sinon.spy(); + $$PREBID_GLOBAL$$.onEvent('bidAccepted', callback); + events.emit(CONSTANTS.EVENTS.BID_ACCEPTED); + sinon.assert.calledOnce(callback); + }); + describe('beforeRequestBids', function () { let bidRequestedHandler; let beforeRequestBidsHandler; From 85817e7ca7240d5b753a933f1d778def0fc344f1 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 12 Dec 2023 12:57:56 -0500 Subject: [PATCH 269/760] Revert "Pubmatic Analytics Adapter : skip duplicate data for pubmatic bids in logger and tracker (#10787)" (#10830) This reverts commit 048f735ccea9907ca72f068042bd35394c58689b. --- modules/pubmaticAnalyticsAdapter.js | 38 +-- .../modules/pubmaticAnalyticsAdapter_spec.js | 248 ++---------------- 2 files changed, 27 insertions(+), 259 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 66898301cd0..fbc45f1a8d7 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -9,7 +9,6 @@ import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; -const VENDOR_OPENWRAP = 'openwrap'; const SEND_TIMEOUT = 2000; const END_POINT_HOST = 'https://t.pubmatic.com/'; const END_POINT_BID_LOGGER = END_POINT_HOST + 'wl?'; @@ -259,27 +258,12 @@ function isS2SBidder(bidder) { return (s2sBidders.indexOf(bidder) > -1) ? 1 : 0 } -function isOWPubmaticBid(adapterName) { - let s2sConf = config.getConfig('s2sConfig'); - let s2sConfArray = isArray(s2sConf) ? s2sConf : [s2sConf]; - return s2sConfArray.some(conf => { - if (adapterName === ADAPTER_CODE && conf.defaultVendor === VENDOR_OPENWRAP && - conf.bidders.indexOf(ADAPTER_CODE) > -1) { - return true; - } - }) -} - function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { - let adapterName = getAdapterNameForAlias(bid.adapterCode || bid.bidder); - if (isOWPubmaticBid(adapterName) && isS2SBidder(bid.bidder)) { - return; - } partnerBids.push({ - 'pn': adapterName, + 'pn': getAdapterNameForAlias(bid.adapterCode || bid.bidder), 'bc': bid.bidderCode || bid.bidder, 'bidid': bid.bidId || bidId, 'db': bid.bidResponse ? 0 : 1, @@ -434,20 +418,13 @@ function executeBidsLoggerCall(e, highestCpmBids) { function executeBidWonLoggerCall(auctionId, adUnitId) { const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; const winningBids = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; - if (!winningBids) { - logWarn(LOG_PRE_FIX + 'Could not find winningBids for : ', auctionId); - return; - } - let winningBid = winningBids[0]; + if (winningBids.length > 1) { winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId].adUnitCodes[adUnitId].bidWonAdId)[0]; } const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); - if (isOWPubmaticBid(adapterName) && isS2SBidder(winningBid.bidder)) { - return; - } let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; let auctionCache = cache.auctions[auctionId]; let floorData = auctionCache.floorData; @@ -500,10 +477,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { function auctionInitHandler(args) { s2sBidders = (function() { let s2sConf = config.getConfig('s2sConfig'); - let s2sBidders = []; - (s2sConf || []) && - isArray(s2sConf) ? s2sConf.map(conf => s2sBidders.push(...conf.bidders)) : s2sBidders.push(...s2sConf.bidders); - return s2sBidders || []; + return (s2sConf && isArray(s2sConf.bidders)) ? s2sConf.bidders : []; }()); let cacheEntry = pick(args, [ 'timestamp', @@ -534,10 +508,6 @@ function bidRequestedHandler(args) { } function bidResponseHandler(args) { - if (!args.requestId) { - logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); - return; - } let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); @@ -602,7 +572,7 @@ function auctionEndHandler(args) { let highestCpmBids = getGlobal().getHighestCpmBids() || []; setTimeout(() => { executeBidsLoggerCall.call(this, args, highestCpmBids); - }, (cache.auctions[args.auctionId]?.bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); + }, (cache.auctions[args.auctionId].bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); } function bidTimeoutHandler(args) { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index fe31361d3ba..6af497bd2e9 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -380,8 +380,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); - expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -394,8 +394,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(1); expect(data.s[0].ps[0].t).to.equal(0); @@ -429,7 +429,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -467,7 +467,7 @@ describe('pubmatic analytics adapter', function () { expect(data.af).to.equal('video'); }); - it('Logger : do not log floor fields when prebids floor shows noData in location property', function() { + it('Logger: do not log floor fields when prebids floor shows noData in location property', function() { const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); BID_REQUESTED_COPY['bids'][1]['floorData']['location'] = 'noData'; @@ -597,7 +597,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); @@ -671,7 +671,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -823,8 +823,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(0); - expect(data.s[0].ps[0].ol1).to.equal(0); + expect(data.s[0].ps[0].l1).to.equal(0); + expect(data.s[0].ps[0].ol1).to.equal(0); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(1); @@ -885,7 +885,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -938,7 +938,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -997,7 +997,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1056,7 +1056,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1113,7 +1113,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1177,7 +1177,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1240,7 +1240,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); @@ -1255,7 +1255,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1289,8 +1289,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1370,7 +1370,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('groupm'); @@ -1385,7 +1385,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1416,7 +1416,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1501,206 +1501,4 @@ describe('pubmatic analytics adapter', function () { expect(metadataObj).to.equal(undefined); }); }); - - describe('OW S2S', function() { - this.beforeEach(function() { - pubmaticAnalyticsAdapter.enableAnalytics({ - options: { - publisherId: 9999, - profileId: 1111, - profileVersionId: 20 - } - }); - config.setConfig({ - s2sConfig: { - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap', - timeout: 500 - } - }); - }); - - this.afterEach(function() { - pubmaticAnalyticsAdapter.disableAnalytics(); - }); - - it('Pubmatic Won: No tracker fired', function() { - this.timeout(5000) - - sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { - return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] - }); - - config.setConfig({ - testGroupId: 15 - }); - - events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); - events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, MOCK.SET_TARGETING); - events.emit(BID_WON, MOCK.BID_WON[0]); - - clock.tick(2000 + 1000); - expect(requests.length).to.equal(1); // only logger is fired - let request = requests[0]; - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); - let data = getLoggerJsonFromRequest(request.requestBody); - expect(data.pubid).to.equal('9999'); - expect(data.pid).to.equal('1111'); - expect(data.pdvid).to.equal('20'); - }); - - it('Non-pubmatic won: logger, tracker fired', function() { - const APPNEXUS_BID = Object.assign({}, BID, { - 'bidder': 'appnexus', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '2ecff0db240757', - 'hb_pb': 1.20, - 'hb_size': '640x480', - 'hb_source': 'server' - } - }); - - const MOCK_AUCTION_INIT_APPNEXUS = { - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'timestamp': 1519767010567, - 'auctionStatus': 'inProgress', - 'adUnits': [ { - 'code': '/19968336/header-bid-tag-1', - 'sizes': [[640, 480]], - 'bids': [ { - 'bidder': 'appnexus', - 'params': { - 'publisherId': '1001' - } - } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' - } - ], - 'adUnitCodes': ['/19968336/header-bid-tag-1'], - 'bidderRequests': [ { - 'bidderCode': 'appnexus', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [ { - 'bidder': 'appnexus', - 'params': { - 'publisherId': '1001', - 'kgpv': 'this-is-a-kgpv' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] - } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - } - ], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 3000 - }; - - const MOCK_BID_REQUESTED_APPNEXUS = { - 'bidder': 'appnexus', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', - 'bidderRequestId': '1be65d7958826a', - 'bids': [ - { - 'bidder': 'appnexus', - 'adapterCode': 'appnexus', - 'bidderCode': 'appnexus', - 'params': { - 'publisherId': '1001', - 'video': { - 'minduration': 30, - 'skippable': true - } - }, - 'mediaType': 'video', - 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' - } - ], - 'auctionStart': 1519149536560, - 'timeout': 5000, - 'start': 1519149562216, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - }, - 'gdprConsent': { - 'consentString': 'here-goes-gdpr-consent-string', - 'gdprApplies': true - } - }; - - this.timeout(5000) - - sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { - return [APPNEXUS_BID] - }); - - events.emit(AUCTION_INIT, MOCK_AUCTION_INIT_APPNEXUS); - events.emit(BID_REQUESTED, MOCK_BID_REQUESTED_APPNEXUS); - events.emit(BID_RESPONSE, APPNEXUS_BID); - events.emit(BIDDER_DONE, { - 'bidderCode': 'appnexus', - 'bids': [ - APPNEXUS_BID, - Object.assign({}, APPNEXUS_BID, { - 'serverResponseTimeMs': 42, - }) - ] - }); - events.emit(AUCTION_END, MOCK.AUCTION_END); - events.emit(SET_TARGETING, { - [APPNEXUS_BID.adUnitCode]: APPNEXUS_BID.adserverTargeting, - }); - events.emit(BID_WON, Object.assign({}, APPNEXUS_BID, { - 'status': 'rendered' - })); - - clock.tick(2000 + 1000); - expect(requests.length).to.equal(2); // logger as well as tracker is fired - let request = requests[1]; // logger is executed late, trackers execute first - expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); - let data = getLoggerJsonFromRequest(request.requestBody); - expect(data.pubid).to.equal('9999'); - expect(data.pid).to.equal('1111'); - expect(data.pdvid).to.equal('20'); - - let firstTracker = requests[0].url; - expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); - firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); - expect(data.pubid).to.equal('9999'); - expect(decodeURIComponent(data.purl)).to.equal('http://www.test.com/page.html'); - - expect(data.s).to.be.an('array'); - expect(data.s.length).to.equal(1); - expect(data.s[0].ps[0].pn).to.equal('appnexus'); - expect(data.s[0].ps[0].bc).to.equal('appnexus'); - }) - }); }); From fd35856f8ff929399e9f88bf61244530aa3d3eed Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 12 Dec 2023 12:30:56 -0700 Subject: [PATCH 270/760] Revert "support ad unit name (#10825)" (#10831) This reverts commit 947a487550e3fea398ae6887287635cf755bacb8. --- modules/teadsBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 4d8a55f6031..ba16c6ddf82 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -262,7 +262,7 @@ function buildRequestObject(bid) { reqObj.bidderRequestId = getBidIdParameter('bidderRequestId', bid); reqObj.placementId = parseInt(placementId, 10); reqObj.pageId = parseInt(pageId, 10); - reqObj.adUnitCode = getValue(bid.params, 'adUnitCode') || getBidIdParameter('adUnitCode', bid); + reqObj.adUnitCode = getBidIdParameter('adUnitCode', bid); reqObj.transactionId = bid.ortb2Imp?.ext?.tid || ''; if (gpid) { reqObj.gpid = gpid; } if (videoPlcmt) { reqObj.videoPlcmt = videoPlcmt; } From 1c4bc7a82ac4e56a1d729e82098f165db6aad1bf Mon Sep 17 00:00:00 2001 From: gabrielIvbs <112690249+gabrielIvbs@users.noreply.github.com> Date: Tue, 12 Dec 2023 23:05:34 +0200 Subject: [PATCH 271/760] Invibes Bid Adapter : infinite scroll tcf (#10764) * added prevent page view event param in order to differentiate from the first and following request on multi request; added disableUserSyncs in order to disable that functionality as default and added the option to activate it from the bidRequest * added infinite scroll and page refresh params, updated tcf, changed randomId to guid, updated the subdomain * reverted subdomain * Update invibesBidAdapter.js with code review suggestion * Moved new variables to invibes object * Update invibesBidAdapter.js add placementIds to invibes object * initialized window.invibes --- modules/invibesBidAdapter.js | 56 ++++++++++-- test/spec/modules/invibesBidAdapter_spec.js | 99 +++++++++++++++++++++ 2 files changed, 149 insertions(+), 6 deletions(-) diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 0c0d1cdef87..1d608d7136b 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -9,7 +9,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 10, + PREBID_VERSION: 11, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -49,14 +49,36 @@ registerBidder(spec); // some state info is required: cookie info, unique user visit id const topWin = getTopMostWindow(); let invibes = topWin.invibes = topWin.invibes || {}; -invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false]; -invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false]; +invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false, false]; +invibes.legitimateInterests = invibes.legitimateInterests || [false, false, false, false, false, false, false, false, false, false, false]; invibes.placementBids = invibes.placementBids || []; invibes.pushedCids = invibes.pushedCids || {}; let preventPageViewEvent = false; +let isInfiniteScrollPage = false; +let isPlacementRefresh = false; let _customUserSync; let _disableUserSyncs; +function updateInfiniteScrollFlag() { + const { scrollHeight } = document.documentElement; + + if (invibes.originalURL === undefined) { + invibes.originalURL = window.location.href; + return; + } + + if (invibes.originalScrollHeight === undefined) { + invibes.originalScrollHeight = scrollHeight; + return; + } + + const currentURL = window.location.href; + + if (scrollHeight > invibes.originalScrollHeight && invibes.originalURL !== currentURL) { + isInfiniteScrollPage = true; + } +} + function isBidRequestValid(bid) { if (typeof bid.params !== 'object') { return false; @@ -87,10 +109,24 @@ function buildRequest(bidRequests, bidderRequest) { const _placementIds = []; const _adUnitCodes = []; let _customEndpoint, _userId, _domainId; - let _ivAuctionStart = bidderRequest.auctionStart || Date.now(); + let _ivAuctionStart = Date.now(); + window.invibes = window.invibes || {}; + window.invibes.placementIds = window.invibes.placementIds || []; + + if (isInfiniteScrollPage == false) { + updateInfiniteScrollFlag(); + } bidRequests.forEach(function (bidRequest) { bidRequest.startTime = new Date().getTime(); + + if (window.invibes.placementIds.includes(bidRequest.params.placementId)) { + isPlacementRefresh = true; + } + + window.invibes.placementIds.push(bidRequest.params.placementId); + + _placementIds.push(bidRequest.params.placementId); _placementIds.push(bidRequest.params.placementId); _adUnitCodes.push(bidRequest.adUnitCode); _domainId = _domainId || bidRequest.params.domainId; @@ -138,6 +174,8 @@ function buildRequest(bidRequests, bidderRequest) { tc: invibes.gdpr_consent, isLocalStorageEnabled: storage.hasLocalStorage(), preventPageViewEvent: preventPageViewEvent, + isPlacementRefresh: isPlacementRefresh, + isInfiniteScrollPage: isInfiniteScrollPage, }; let lid = readFromLocalStorage('ivbsdid'); @@ -368,7 +406,9 @@ function addMeta(bidModelMeta) { } function generateRandomId() { - return (Math.round(Math.random() * 1e12)).toString(36).substring(0, 10); + return '10000000100040008000100000000000'.replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); } function getDocumentLocation(bidderRequest) { @@ -568,7 +608,7 @@ function readGdprConsent(gdprConsent) { } let legitimateInterests = getLegitimateInterests(gdprConsent.vendorData); - tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, 10); + tryCopyValueToArray(legitimateInterests, invibes.legitimateInterests, purposesLength); let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10); let vendorConsents = getVendorConsents(gdprConsent.vendorData); @@ -621,6 +661,10 @@ function tryCopyValueToArray(value, target, length) { function getPurposeConsentsCounter(vendorData) { if (vendorData.purpose && vendorData.purpose.consents) { + if (vendorData.tcfPolicyVersion >= 4) { + return 11; + } + return 10; } diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 7ee6b464996..056255c7738 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -44,6 +44,78 @@ describe('invibesBidAdapter:', function () { } ]; + let bidRequestsWithDuplicatedplacementId = [ + { + bidId: 'b1', + bidder: BIDDER_CODE, + bidderRequestId: 'r1', + params: { + placementId: PLACEMENT_ID, + disableUserSyncs: false + + }, + adUnitCode: 'test-div1', + auctionId: 'a1', + sizes: [ + [300, 250], + [400, 300], + [125, 125] + ], + transactionId: 't1' + }, { + bidId: 'b2', + bidder: BIDDER_CODE, + bidderRequestId: 'r2', + params: { + placementId: PLACEMENT_ID, + disableUserSyncs: false + }, + adUnitCode: 'test-div2', + auctionId: 'a2', + sizes: [ + [300, 250], + [400, 300] + ], + transactionId: 't2' + } + ]; + + let bidRequestsWithUniquePlacementId = [ + { + bidId: 'b1', + bidder: BIDDER_CODE, + bidderRequestId: 'r1', + params: { + placementId: 'PLACEMENT_ID_1', + disableUserSyncs: false + + }, + adUnitCode: 'test-div1', + auctionId: 'a1', + sizes: [ + [300, 250], + [400, 300], + [125, 125] + ], + transactionId: 't1' + }, { + bidId: 'b2', + bidder: BIDDER_CODE, + bidderRequestId: 'r2', + params: { + placementId: 'PLACEMENT_ID_2', + disableUserSyncs: false + }, + adUnitCode: 'test-div2', + auctionId: 'a2', + sizes: [ + [300, 250], + [400, 300] + ], + transactionId: 't2' + } + ]; + let bidRequestsWithUserId = [ { bidId: 'b1', @@ -185,17 +257,44 @@ describe('invibesBidAdapter:', function () { expect(request.data.preventPageViewEvent).to.be.false; }); + it('sends isPlacementRefresh as false when the placement ids are used for the first time', function () { + let request = spec.buildRequests(bidRequestsWithUniquePlacementId, bidderRequestWithPageInfo); + expect(request.data.isPlacementRefresh).to.be.false; + }); + it('sends preventPageViewEvent as true on 2nd call', function () { let request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); expect(request.data.preventPageViewEvent).to.be.true; }); + it('sends isPlacementRefresh as true on multi requests on the same placement id', function () { + let request = spec.buildRequests(bidRequestsWithDuplicatedplacementId, bidderRequestWithPageInfo); + expect(request.data.isPlacementRefresh).to.be.true; + }); + + it('sends isInfiniteScrollPage as false initially', function () { + let request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); + expect(request.data.isInfiniteScrollPage).to.be.false; + }); + + it('sends isPlacementRefresh as true on multi requests multiple calls with the same placement id from second call', function () { + let request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); + expect(request.data.isInfiniteScrollPage).to.be.false; + let duplicatedRequest = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); + expect(duplicatedRequest.data.isPlacementRefresh).to.be.true; + }); + it('sends bid request to ENDPOINT via GET', function () { const request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); expect(request.url).to.equal(ENDPOINT); expect(request.method).to.equal('GET'); }); + it('generates a visitId of length 32', function () { + spec.buildRequests(bidRequests, bidderRequestWithPageInfo); + expect(top.window.invibes.visitId.length).to.equal(32); + }); + it('sends bid request to custom endpoint via GET', function () { const request = spec.buildRequests([{ bidId: 'b1', From 2e0789671b5a080d969da8878dbf8f0c47a0c3b6 Mon Sep 17 00:00:00 2001 From: BaronJHYu <254878848@qq.com> Date: Wed, 13 Dec 2023 21:12:30 +0800 Subject: [PATCH 272/760] Mediago Bid Adapter : switch domain name (#10833) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mediago Bid Adapter:new adapter * remove console * change spec file to fix CircleCI * change spec file to fix CircleCI * change spec file * Update mediagoBidAdapter.js * Update mediagoBidAdapter.js * rerun CurcleCi * update mediagoBidAdapter * update discoveryBidAdapter * Discovery Bid Adapter : parameter updates * Mediago Bid Adapter : parameter updates * Mediago Bid Adapter : code style format * rerun circleci * rerun circleci * rerun circleci * rerun circleci * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * Update mediagoBidAdapter & discoveryBidAdapter:report eids to server * update Mediago & Discovery BidAdapter:remove size filter * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:code format * update Mediago & Discovery BidAdapter:add param in banner format * update mediago & discovery:first party data * update mediago & discovery:first party data * update mediago & discovery:first party data * fix(mediago & discovery): update param tagid * fix(mediago & discovery): update param tagid * feat:add imp params for analysis * fix:add imp params for analysis * code format * test:add test data & test * format code * rerun circleci * feat: 1. add cookieSync 2. add gdpr * code format * rerun circleci * add gvlid * feat:add param 1.ppuid 2.content 3.cat 4.reqtime * fix: getUserSyncs param fixed * fix: getUserSyncs param fixed * feat: add param 1. adUnitCode 2. referrer * feat: Switch domain * rerun circle ci --------- Co-authored-by: BaronYu --- modules/mediagoBidAdapter.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 72b06dfe7ed..a9c670f35d8 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -10,10 +10,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'mediago'; // const PROTOCOL = window.document.location.protocol; -const ENDPOINT_URL = - // ((PROTOCOL === 'https:') ? 'https' : 'http') + - 'https://rtb-us.mediago.io/api/bid?tn='; -const COOKY_SYNC_URL = 'https://trace.mediago.io/ju/cs/eplist'; +const ENDPOINT_URL = 'https://gbid.mediago.io/api/bid?tn='; +const COOKY_SYNC_URL = 'https://gtrace.mediago.io/ju/cs/eplist'; const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; const TIME_TO_LIVE = 500; From 75cb1663dc229811513fddf400d5934654455ee8 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Wed, 13 Dec 2023 20:34:15 +0200 Subject: [PATCH 273/760] Bizzclick Bid Adapter : update adapter, used ortbConverter lib (#10828) * update bizzclick adapter, used ortbConverter lib * fix: removed gdpr, ccpa, coppa --- modules/bizzclickBidAdapter.js | 355 ++--------- modules/bizzclickBidAdapter.md | 183 +++--- test/spec/modules/bizzclickBidAdapter_spec.js | 569 +++++++----------- 3 files changed, 378 insertions(+), 729 deletions(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index dc7731231ab..d2eba3f0f81 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -1,322 +1,77 @@ -import {_map, deepAccess, deepSetValue, getDNT, logMessage, logWarn} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { config } from '../src/config.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'bizzclick'; -const ACCOUNTID_MACROS = '[account_id]'; -const URL_ENDPOINT = `https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=${ACCOUNTID_MACROS}`; -const NATIVE_ASSET_IDS = { 0: 'title', 2: 'icon', 3: 'image', 5: 'sponsoredBy', 4: 'body', 1: 'cta' }; -const NATIVE_PARAMS = { - title: { - id: 0, - name: 'title' - }, - icon: { - id: 2, - type: 1, - name: 'img' - }, - image: { - id: 3, - type: 3, - name: 'img' +const SOURCE_ID_MACRO = '[sourceid]'; +const ACCOUNT_ID_MACRO = '[accountid]'; +const HOST_MACRO = '[host]'; +const URL = `https://${HOST_MACRO}.bizzclick.com/bid?rtb_seat_id=${SOURCE_ID_MACRO}&secret_key=${ACCOUNT_ID_MACRO}&integration_type=prebidjs`; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_HOST = 'us-e-node1'; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 20, }, - sponsoredBy: { - id: 5, - name: 'data', - type: 1 + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + if (!imp.bidfloor) imp.bidfloor = bidRequest.params.bidfloor || 0; + imp.ext = { + [BIDDER_CODE]: { + accountId: bidRequest.params.accountId, + sourceId: bidRequest.params.sourceId, + host: bidRequest.params.host || DEFAULT_HOST, + } + } + return imp; }, - body: { - id: 4, - name: 'data', - type: 2 + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + request.test = config.getConfig('debug') ? 1 : 0; + if (!request.cur) request.cur = [bid.params.currency || DEFAULT_CURRENCY]; + return request; }, - cta: { - id: 1, - type: 12, - name: 'data' + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.cur = bid.cur || DEFAULT_CURRENCY; + return bidResponse; } -}; -const NATIVE_VERSION = '1.2'; +}); + export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + isBidRequestValid: (bid) => { - return Boolean(bid.params.accountId) && Boolean(bid.params.placementId) + return Boolean(bid.params.sourceId) && Boolean(bid.params.accountId); }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ - buildRequests: (validBidRequests, bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - if (validBidRequests && validBidRequests.length === 0) return [] - let accuontId = validBidRequests[0].params.accountId; - const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); - let winTop = window; - let location; - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let bids = []; - for (let bidRequest of validBidRequests) { - let impObject = prepareImpObject(bidRequest); - let data = { - id: bidRequest.bidId, - test: config.getConfig('debug') ? 1 : 0, - at: 1, - cur: ['USD'], - device: { - w: winTop.screen.width, - h: winTop.screen.height, - dnt: getDNT() ? 1 : 0, - language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', - }, - site: { - page: location.pathname, - host: location.host - }, - source: { - tid: bidRequest.ortb2Imp?.ext?.tid, - ext: { - schain: {} - } - }, - regs: { - coppa: config.getConfig('coppa') === true ? 1 : 0, - ext: {} - }, - user: { - ext: {} - }, - ext: { - ts: Date.now() - }, - tmax: bidRequest.timeout, - imp: [impObject], - }; - - let connection = navigator.connection || navigator.webkitConnection; - if (connection && connection.effectiveType) { - data.device.connectiontype = connection.effectiveType; - } - if (bidRequest) { - if (bidRequest.schain) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - - if (bidRequest.gdprConsent && bidRequest.gdprConsent.gdprApplies) { - deepSetValue(data, 'regs.ext.gdpr', bidRequest.gdprConsent.gdprApplies ? 1 : 0); - deepSetValue(data, 'user.ext.consent', bidRequest.gdprConsent.consentString); - } - - if (bidRequest.uspConsent !== undefined) { - deepSetValue(data, 'regs.ext.us_privacy', bidRequest.uspConsent); - } - } - bids.push(data) - } + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return []; + const { sourceId, accountId } = validBidRequests[0].params; + const host = validBidRequests[0].params.host || 'USE'; + const endpointURL = URL.replace(HOST_MACRO, host || DEFAULT_HOST) + .replace(ACCOUNT_ID_MACRO, accountId) + .replace(SOURCE_ID_MACRO, sourceId); + const request = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); return { method: 'POST', url: endpointURL, - data: bids + data: request }; }, - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - if (!serverResponse || !serverResponse.body) return [] - let bizzclickResponse = serverResponse.body; - let bids = []; - for (let response of bizzclickResponse) { - let mediaType = response.seatbid[0].bid[0].ext && response.seatbid[0].bid[0].ext.mediaType ? response.seatbid[0].bid[0].ext.mediaType : BANNER; - let bid = { - requestId: response.id, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ttl: response.ttl || 1200, - currency: response.cur || 'USD', - netRevenue: true, - creativeId: response.seatbid[0].bid[0].crid, - dealId: response.seatbid[0].bid[0].dealid, - mediaType: mediaType - }; - - bid.meta = {}; - if (response.seatbid[0].bid[0].adomain && response.seatbid[0].bid[0].adomain.length > 0) { - bid.meta.advertiserDomains = response.seatbid[0].bid[0].adomain; - } - switch (mediaType) { - case VIDEO: - bid.vastXml = response.seatbid[0].bid[0].adm - bid.vastUrl = response.seatbid[0].bid[0].ext.vastUrl - break - case NATIVE: - bid.native = parseNative(response.seatbid[0].bid[0].adm) - break - default: - bid.ad = response.seatbid[0].bid[0].adm - } - bids.push(bid); + interpretResponse: (response, request) => { + if (response?.body) { + const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + return bids; } - return bids; + return []; }, }; -/** - * Determine type of request - * - * @param bidRequest - * @param type - * @returns {boolean} - */ -const checkRequestType = (bidRequest, type) => { - return (typeof deepAccess(bidRequest, `mediaTypes.${type}`) !== 'undefined'); -} -const parseNative = admObject => { - const { assets, link, imptrackers, jstracker } = admObject.native; - const result = { - clickUrl: link.url, - clickTrackers: link.clicktrackers || undefined, - impressionTrackers: imptrackers || undefined, - javascriptTrackers: jstracker ? [ jstracker ] : undefined - }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } - }); - return result; -} -const prepareImpObject = (bidRequest) => { - let impObject = { - id: bidRequest.transactionId, - secure: 1, - ext: { - placementId: bidRequest.params.placementId - } - }; - if (checkRequestType(bidRequest, BANNER)) { - impObject.banner = addBannerParameters(bidRequest); - } - if (checkRequestType(bidRequest, VIDEO)) { - impObject.video = addVideoParameters(bidRequest); - } - if (checkRequestType(bidRequest, NATIVE)) { - impObject.native = { - ver: NATIVE_VERSION, - request: addNativeParameters(bidRequest) - }; - } - return impObject -}; -const addNativeParameters = bidRequest => { - let impObject = { - // TODO: top-level ID is not in ORTB native 1.2, is this intentional? - // (despite the name, this appears to be an ORTB native request - not an imp - object) - id: bidRequest.bidId, - ver: NATIVE_VERSION, - }; - const assets = _map(bidRequest.mediaTypes.native, (bidParams, key) => { - const props = NATIVE_PARAMS[key]; - const asset = { - required: bidParams.required & 1, - }; - if (props) { - asset.id = props.id; - let wmin, hmin; - let aRatios = bidParams.aspect_ratios; - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); - wmin = sizes[0]; - hmin = sizes[1]; - } - asset[props.name] = {}; - if (bidParams.len) asset[props.name]['len'] = bidParams.len; - if (props.type) asset[props.name]['type'] = props.type; - if (wmin) asset[props.name]['wmin'] = wmin; - if (hmin) asset[props.name]['hmin'] = hmin; - return asset; - } - }).filter(Boolean); - impObject.assets = assets; - return impObject -} -const addBannerParameters = (bidRequest) => { - let bannerObject = {}; - const size = parseSizes(bidRequest, 'banner'); - bannerObject.w = size[0]; - bannerObject.h = size[1]; - return bannerObject; -}; -const parseSizes = (bid, mediaType) => { - let mediaTypes = bid.mediaTypes; - if (mediaType === 'video') { - let size = []; - if (mediaTypes.video && mediaTypes.video.w && mediaTypes.video.h) { - size = [ - mediaTypes.video.w, - mediaTypes.video.h - ]; - } else if (Array.isArray(deepAccess(bid, 'mediaTypes.video.playerSize')) && bid.mediaTypes.video.playerSize.length === 1) { - size = bid.mediaTypes.video.playerSize[0]; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0 && Array.isArray(bid.sizes[0]) && bid.sizes[0].length > 1) { - size = bid.sizes[0]; - } - return size; - } - let sizes = []; - if (Array.isArray(mediaTypes.banner.sizes)) { - sizes = mediaTypes.banner.sizes[0]; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizes = bid.sizes - } else { - logWarn('no sizes are setup or found'); - } - return sizes -} -const addVideoParameters = (bidRequest) => { - let videoObj = {}; - let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] - for (let param of supportParamsList) { - if (bidRequest.mediaTypes.video[param] !== undefined) { - videoObj[param] = bidRequest.mediaTypes.video[param]; - } - } - const size = parseSizes(bidRequest, 'video'); - videoObj.w = size[0]; - videoObj.h = size[1]; - return videoObj; -} -const flatten = arr => { - return [].concat(...arr); -} + registerBidder(spec); diff --git a/modules/bizzclickBidAdapter.md b/modules/bizzclickBidAdapter.md index 6fc1bebf546..ad342f34e07 100644 --- a/modules/bizzclickBidAdapter.md +++ b/modules/bizzclickBidAdapter.md @@ -11,94 +11,99 @@ Maintainer: support@bizzclick.com Module that connects to BizzClick SSP demand sources # Test Parameters -``` - var adUnits = [{ - code: 'placementId', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]] - } - }, - bids: [{ - bidder: 'bizzclick', - params: { - placementId: 'hash', - accountId: 'accountId' - } - }] - }, - { - code: 'native_example', - // sizes: [[1, 1]], - mediaTypes: { - native: { - title: { - required: true, - len: 800 - }, - image: { - required: true, - len: 80 - }, - sponsoredBy: { - required: true - }, - clickUrl: { - required: true - }, - privacyLink: { - required: false - }, - body: { - required: true - }, - icon: { - required: true, - sizes: [50, 50] - } - } - }, - bids: [ { - bidder: 'bizzclick', - params: { - placementId: 'hash', - accountId: 'accountId' - } - }] - }, - { - code: 'video1', - sizes: [640,480], - mediaTypes: { video: { - minduration:0, - maxduration:999, - boxingallowed:1, - skip:0, - mimes:[ - 'application/javascript', - 'video/mp4' - ], - w:1920, - h:1080, - protocols:[ - 2 - ], - linearity:1, - api:[ - 1, - 2 - ] - } }, +```js +const adUnits = [ + { + code: "placementId", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bids: [ + { + bidder: "bizzclick", + params: { + placementId: "hash", + accountId: "accountId", + host: "host", + }, + }, + ], + }, + { + code: "native_example", + // sizes: [[1, 1]], + mediaTypes: { + native: { + title: { + required: true, + len: 800, + }, + image: { + required: true, + len: 80, + }, + sponsoredBy: { + required: true, + }, + clickUrl: { + required: true, + }, + privacyLink: { + required: false, + }, + body: { + required: true, + }, + icon: { + required: true, + sizes: [50, 50], + }, + }, + }, bids: [ - { - bidder: 'bizzclick', - params: { - placementId: 'hash', - accountId: 'accountId' - } - } - ] - } - ]; -``` \ No newline at end of file + { + bidder: "bizzclick", + params: { + placementId: "hash", + accountId: "accountId", + host: "host", + }, + }, + ], + }, + { + code: "video1", + sizes: [640, 480], + mediaTypes: { + video: { + minduration: 0, + maxduration: 999, + boxingallowed: 1, + skip: 0, + mimes: ["application/javascript", "video/mp4"], + w: 1920, + h: 1080, + protocols: [2], + linearity: 1, + api: [1, 2], + }, + }, + bids: [ + { + bidder: "bizzclick", + params: { + placementId: "hash", + accountId: "accountId", + host: "host", + }, + }, + ], + }, +]; +``` diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/bizzclickBidAdapter_spec.js index f80051b0a50..f8e66caf657 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/bizzclickBidAdapter_spec.js @@ -1,6 +1,102 @@ import { expect } from 'chai'; -import { spec } from 'modules/bizzclickBidAdapter.js'; -import {config} from 'src/config.js'; +import { spec } from 'modules/bizzclickBidAdapter'; +import 'modules/priceFloors.js'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; + +// load modules that register ORTB processors +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; + +const SIMPLE_BID_REQUEST = { + bidder: 'bizzclick', + params: { + accountId: 'testAccountId', + sourceId: 'testSourceId', + host: 'USE', + }, + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1499748733608-0', + transactionId: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + bidId: '33e9500b21129f', + bidderRequestId: '2772c1e566670b', + auctionId: '192721e36a0239', + sizes: [[300, 250], [160, 600]], + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', + }, +} + +const BANNER_BID_REQUEST = { + bidder: 'bizzclick', + params: { + accountId: 'testAccountId', + sourceId: 'testSourceId', + host: 'USE', + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + code: 'banner_example', + timeout: 1000, +} + +const VIDEO_BID_REQUEST = { + placementCode: '/DfpAccount1/slotVideo', + bidId: 'test-bid-id-2', + mediaTypes: { + video: { + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + bidder: 'bizzclick', + params: { + accountId: '123', + sourceId: '123', + host: 'USE', + }, + adUnitCode: '/adunit-code/test-path', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, +} const NATIVE_BID_REQUEST = { code: 'native_example', @@ -34,386 +130,179 @@ const NATIVE_BID_REQUEST = { }, bidder: 'bizzclick', params: { - placementId: 'hash', - accountId: 'accountId' - }, - timeout: 1000 - -}; - -const BANNER_BID_REQUEST = { - code: 'banner_example', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'example.com', - sid: '164', - hp: 1 - } - ] - }, - bidder: 'bizzclick', - params: { - placementId: 'hash', - accountId: 'accountId' + accountId: 'testAccountId', + sourceId: 'testSourceId', + host: 'USE', }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', timeout: 1000, - gdprConsent: { - consentString: 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', - gdprApplies: 1, - }, uspConsent: 'uspConsent' -} +}; -const bidRequest = { +const bidderRequest = { refererInfo: { - referer: 'test.com' - } -} - -const VIDEO_BID_REQUEST = { - code: 'video1', - sizes: [640, 480], - mediaTypes: { video: { - minduration: 0, - maxduration: 999, - boxingallowed: 1, - skip: 0, - mimes: [ - 'application/javascript', - 'video/mp4' - ], - w: 1920, - h: 1080, - protocols: [ - 2 - ], - linearity: 1, - api: [ - 1, - 2 - ] + page: 'https://publisher.com/home', + ref: 'https://referrer' } - }, - - bidder: 'bizzclick', - params: { - placementId: 'hash', - accountId: 'accountId' - }, - timeout: 1000 - -} - -const BANNER_BID_RESPONSE = { - id: 'request_id', - bidid: 'request_imp_id', - seatbid: [{ - bid: [{ - id: 'bid_id', - impid: 'request_imp_id', - price: 5, - adomain: ['example.com'], - adm: 'admcode', - crid: 'crid', - ext: { - mediaType: 'banner' - } - }], - }], -}; - -const VIDEO_BID_RESPONSE = { - id: 'request_id', - bidid: 'request_imp_id', - seatbid: [{ - bid: [{ - id: 'bid_id', - impid: 'request_imp_id', - price: 5, - adomain: ['example.com'], - adm: 'admcode', - crid: 'crid', - ext: { - mediaType: 'video', - vastUrl: 'http://example.vast', - } - }], - }], }; -let imgData = { - url: `https://example.com/image`, - w: 1200, - h: 627 -}; +const gdprConsent = { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', +} -const NATIVE_BID_RESPONSE = { - id: 'request_id', - bidid: 'request_imp_id', - seatbid: [{ - bid: [{ - id: 'bid_id', - impid: 'request_imp_id', - price: 5, - adomain: ['example.com'], - adm: { native: - { - assets: [ - {id: 0, title: 'dummyText'}, - {id: 3, image: imgData}, - { - id: 5, - data: {value: 'organization.name'} - } - ], - link: {url: 'example.com'}, - imptrackers: ['tracker1.com', 'tracker2.com', 'tracker3.com'], - jstracker: 'tracker1.com' - } - }, - crid: 'crid', - ext: { - mediaType: 'native' - } - }], - }], -}; +describe('bizzclickAdapter', 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('BizzclickAdapter', function() { - describe('with COPPA', function() { - beforeEach(function() { + describe('with user privacy regulations', function () { + it('should send the Coppa "required" flag set to "1" in the request', function () { sinon.stub(config, 'getConfig') .withArgs('coppa') .returns(true); - }); - afterEach(function() { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(serverRequest.data.regs.coppa).to.equal(1); config.getConfig.restore(); }); - it('should send the Coppa "required" flag set to "1" in the request', function () { - let serverRequest = spec.buildRequests([BANNER_BID_REQUEST]); - expect(serverRequest.data[0].regs.coppa).to.equal(1); + it('should send the GDPR Consent data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); }); - }); - describe('isBidRequestValid', function() { - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(NATIVE_BID_REQUEST)).to.equal(true); - }); - - it('should return false when required params are not passed', function () { - let bid = Object.assign({}, NATIVE_BID_REQUEST); - delete bid.params; - bid.params = { - 'IncorrectParam': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + it('should send the CCPA data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); }); }); - describe('build Native Request', function () { - const request = spec.buildRequests([NATIVE_BID_REQUEST], bidRequest); - - it('Creates a ServerRequest object with method, URL and data', function () { - expect(request).to.exist; - expect(request.method).to.exist; - expect(request.url).to.exist; - expect(request.data).to.exist; - }); - - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); - }); - - it('Returns valid URL', function () { - expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(true); }); - it('Returns empty data if no valid requests are passed', function () { - let serverRequest = spec.buildRequests([]); - expect(serverRequest).to.be.an('array').that.is.empty; + it('should return false when accountID/sourceId is missing', function () { + let localbid = Object.assign({}, BANNER_BID_REQUEST); + delete localbid.params.accountId; + delete localbid.params.sourceId; + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(false); }); }); - describe('build Banner Request', function () { - const request = spec.buildRequests([BANNER_BID_REQUEST]); - - it('Creates a ServerRequest object with method, URL and data', function () { - expect(request).to.exist; - expect(request.method).to.exist; - expect(request.url).to.exist; - expect(request.data).to.exist; + describe('build request', function () { + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); }); - it('sends bid request to our endpoint via POST', function () { + it('should return a valid bid request object', function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request).to.not.equal('array'); + expect(request.data).to.be.an('object'); expect(request.method).to.equal('POST'); + expect(request.url).to.not.equal(''); + expect(request.url).to.not.equal(undefined); + expect(request.url).to.not.equal(null); + + expect(request.data.site).to.have.property('page'); + expect(request.data.site).to.have.property('domain'); + expect(request.data).to.have.property('id'); + expect(request.data).to.have.property('imp'); + expect(request.data).to.have.property('device'); }); - it('check consent and ccpa string is set properly', function() { - expect(request.data[0].regs.ext.gdpr).to.equal(1); - expect(request.data[0].user.ext.consent).to.equal(BANNER_BID_REQUEST.gdprConsent.consentString); - expect(request.data[0].regs.ext.us_privacy).to.equal(BANNER_BID_REQUEST.uspConsent); - }) - - it('check schain is set properly', function() { - expect(request.data[0].source.ext.schain.complete).to.equal(1); - expect(request.data[0].source.ext.schain.ver).to.equal('1.0'); - }) - - it('Returns valid URL', function () { - expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + it('should return a valid bid BANNER request object', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].banner).to.exist; + expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); + expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); }); - }); - describe('build Video Request', function () { - const request = spec.buildRequests([VIDEO_BID_REQUEST]); - - it('Creates a ServerRequest object with method, URL and data', function () { - expect(request).to.exist; - expect(request.method).to.exist; - expect(request.url).to.exist; - expect(request.data).to.exist; - }); + if (FEATURES.VIDEO) { + it('should return a valid bid VIDEO request object', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].video).to.exist; + expect(request.data.imp[0].video.w).to.be.an('number'); + expect(request.data.imp[0].video.h).to.be.an('number'); + }); + } - it('sends bid request to our endpoint via POST', function () { - expect(request.method).to.equal('POST'); + it('should return a valid bid NATIVE request object', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0]).to.be.an('object'); }); + }) - it('Returns valid URL', function () { - expect(request.url).to.equal('https://us-e-node1.bizzclick.com/bid?rtb_seat_id=prebidjs&secret_key=accountId'); + describe('interpretResponse', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidId': '28ffdk2B952532', + 'bidder': 'bizzclick', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '123', + 'commonId': 'commonIdValue' + } + }, + 'adUnitCode': 'adunit-code', + 'params': { + 'publisherId': 'publisherIdValue' + } + }]; + bidderRequest = {}; }); - }); - describe('interpretResponse', function () { - it('Empty response must return empty array', function() { + it('Empty response must return empty array', function () { const emptyResponse = null; - let response = spec.interpretResponse(emptyResponse); + let response = spec.interpretResponse(emptyResponse, BANNER_BID_REQUEST); expect(response).to.be.an('array').that.is.empty; }) it('Should interpret banner response', function () { - const bannerResponse = { - body: [BANNER_BID_RESPONSE] - } - - const expectedBidResponse = { - requestId: BANNER_BID_RESPONSE.id, - cpm: BANNER_BID_RESPONSE.seatbid[0].bid[0].price, - width: BANNER_BID_RESPONSE.seatbid[0].bid[0].w, - height: BANNER_BID_RESPONSE.seatbid[0].bid[0].h, - ttl: BANNER_BID_RESPONSE.ttl || 1200, - currency: BANNER_BID_RESPONSE.cur || 'USD', - netRevenue: true, - creativeId: BANNER_BID_RESPONSE.seatbid[0].bid[0].crid, - dealId: BANNER_BID_RESPONSE.seatbid[0].bid[0].dealid, - - meta: {advertiserDomains: BANNER_BID_RESPONSE.seatbid[0].bid[0].adomain}, - mediaType: 'banner', - ad: BANNER_BID_RESPONSE.seatbid[0].bid[0].adm - } - - let bannerResponses = spec.interpretResponse(bannerResponse); - - expect(bannerResponses).to.be.an('array').that.is.not.empty; - let dataItem = bannerResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'meta', 'mediaType'); - expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); - expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.ad).to.equal(expectedBidResponse.ad); - expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); - expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); - expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal(expectedBidResponse.currency); - expect(dataItem.width).to.equal(expectedBidResponse.width); - expect(dataItem.height).to.equal(expectedBidResponse.height); - }); - - it('Should interpret video response', function () { - const videoResponse = { - body: [VIDEO_BID_RESPONSE] - } - - const expectedBidResponse = { - requestId: VIDEO_BID_RESPONSE.id, - cpm: VIDEO_BID_RESPONSE.seatbid[0].bid[0].price, - width: VIDEO_BID_RESPONSE.seatbid[0].bid[0].w, - height: VIDEO_BID_RESPONSE.seatbid[0].bid[0].h, - ttl: VIDEO_BID_RESPONSE.ttl || 1200, - currency: VIDEO_BID_RESPONSE.cur || 'USD', - netRevenue: true, - creativeId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid, - dealId: VIDEO_BID_RESPONSE.seatbid[0].bid[0].dealid, - mediaType: 'video', - vastXml: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm, - meta: {advertiserDomains: VIDEO_BID_RESPONSE.seatbid[0].bid[0].adomain}, - vastUrl: VIDEO_BID_RESPONSE.seatbid[0].bid[0].ext.vastUrl - } - - let videoResponses = spec.interpretResponse(videoResponse); - - expect(videoResponses).to.be.an('array').that.is.not.empty; - let dataItem = videoResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastXml', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'meta', 'mediaType'); - expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); - expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.vastXml).to.equal(expectedBidResponse.vastXml) - expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); - expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); - expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal(expectedBidResponse.currency); - expect(dataItem.width).to.equal(expectedBidResponse.width); - expect(dataItem.height).to.equal(expectedBidResponse.height); - }); - - it('Should interpret native response', function () { - const nativeResponse = { - body: [NATIVE_BID_RESPONSE] - } - - const expectedBidResponse = { - requestId: NATIVE_BID_RESPONSE.id, - cpm: NATIVE_BID_RESPONSE.seatbid[0].bid[0].price, - width: NATIVE_BID_RESPONSE.seatbid[0].bid[0].w, - height: NATIVE_BID_RESPONSE.seatbid[0].bid[0].h, - ttl: NATIVE_BID_RESPONSE.ttl || 1200, - currency: NATIVE_BID_RESPONSE.cur || 'USD', - netRevenue: true, - creativeId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].crid, - dealId: NATIVE_BID_RESPONSE.seatbid[0].bid[0].dealid, - mediaType: 'native', - meta: {advertiserDomains: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adomain}, - native: {clickUrl: NATIVE_BID_RESPONSE.seatbid[0].bid[0].adm.native.link.url} - } - - let nativeResponses = spec.interpretResponse(nativeResponse); - - expect(nativeResponses).to.be.an('array').that.is.not.empty; - let dataItem = nativeResponses[0]; - expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); - expect(dataItem.requestId).to.equal(expectedBidResponse.requestId); - expect(dataItem.cpm).to.equal(expectedBidResponse.cpm); - expect(dataItem.meta.advertiserDomains).to.equal(expectedBidResponse.meta.advertiserDomains); - expect(dataItem.native.clickUrl).to.equal(expectedBidResponse.native.clickUrl) - expect(dataItem.ttl).to.equal(expectedBidResponse.ttl); - expect(dataItem.creativeId).to.equal(expectedBidResponse.creativeId); - expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal(expectedBidResponse.currency); - expect(dataItem.width).to.equal(expectedBidResponse.width); - expect(dataItem.height).to.equal(expectedBidResponse.height); - }); + const serverResponse = { + body: { + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'impid': '28ffdk2B952532', + 'price': 97, + 'adm': '', + 'w': 300, + 'h': 250, + 'crid': 'creative0' + }] + }] + } + }; + it('should interpret server response', function () { + const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(serverResponse, bidRequest); + expect(bids).to.be.an('array'); + const bid = bids[0]; + expect(bid).to.be.an('object'); + expect(bid.currency).to.equal('USD'); + expect(bid.cpm).to.equal(97); + expect(bid.ad).to.equal(ad) + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('creative0'); + }); + }) }); -}) +}); From dc530cf23527852f7ca8ead50e6e02c2c298b4f2 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:11:46 +0530 Subject: [PATCH 274/760] PubMatic Bid Adapter : passing a unique wiid to pubmatic ssp and logger call (#10821) * Added wiid as UUID * Handled undefined wiid in logger and tracker * updated test cases * updated test cases --------- Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 9 +++++++-- modules/pubmaticBidAdapter.js | 4 +++- test/spec/modules/pubmaticBidAdapter_spec.js | 8 ++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index fbc45f1a8d7..9b910caa992 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -337,6 +337,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { let auctionId = e.auctionId; let referrer = config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''; let auctionCache = cache.auctions[auctionId]; + let wiid = auctionCache?.wiid || auctionId; let floorData = auctionCache?.floorData; let floorFetchStatus = getFloorFetchStatus(auctionCache?.floorData); let outputObj = { s: [] }; @@ -352,7 +353,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { pixelURL += 'pubid=' + publisherId; outputObj['pubid'] = '' + publisherId; - outputObj['iid'] = '' + auctionId; + outputObj['iid'] = '' + wiid; outputObj['to'] = '' + auctionCache.timeout; outputObj['purl'] = referrer; outputObj['orig'] = getDomainFromUrl(referrer); @@ -428,6 +429,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, 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; @@ -436,7 +438,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += 'pubid=' + publisherId; pixelURL += '&purl=' + enc(config.getConfig('pageUrl') || cache.auctions[auctionId].referer || ''); pixelURL += '&tst=' + Math.round((new window.Date()).getTime() / 1000); - pixelURL += '&iid=' + enc(auctionId); + pixelURL += '&iid=' + enc(wiid); pixelURL += '&bidid=' + enc(winningBidId); pixelURL += '&pid=' + enc(profileId); pixelURL += '&pdvid=' + enc(profileVersionId); @@ -500,6 +502,9 @@ function bidRequestedHandler(args) { dimensions: bid.sizes }; } + if (bid.bidder === 'pubmatic' && !!bid?.params?.wiid) { + cache.auctions[args.auctionId].wiid = bid.params.wiid; + } cache.auctions[args.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId] = [copyRequiredBidDetails(bid)]; if (bid.floorData) { cache.auctions[args.auctionId].floorData['floorRequestData'] = bid.floorData; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index ee80de03c03..a6c7dda510c 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,4 +1,4 @@ -import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, uniques, isPlainObject, isInteger } from '../src/utils.js'; +import { getBidRequest, logWarn, isBoolean, isStr, isArray, inIframe, mergeDeep, deepAccess, isNumber, deepSetValue, logInfo, logError, deepClone, uniques, isPlainObject, isInteger, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -1081,8 +1081,10 @@ export const spec = { var bid; var blockedIabCategories = []; var allowedIabCategories = []; + var wiid = generateUUID(); validBidRequests.forEach(originalBid => { + originalBid.params.wiid = originalBid.params.wiid || bidderRequest.auctionId || wiid; bid = deepClone(originalBid); bid.params.adSlot = bid.params.adSlot || ''; _parseAdSlot(bid); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 154a8e1253b..544ba3d0fbf 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -104,6 +104,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5890', adSlot: 'Div1@0x0', // ad_id or tagid + wiid: 'new-unique-wiid', video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, @@ -154,6 +155,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5890', adSlot: 'Div1@640x480', // ad_id or tagid + wiid: '1234567890', video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, @@ -213,6 +215,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5670', adSlot: '/43743431/NativeAutomationPrebid@1x1', + wiid: 'new-unique-wiid' }, bidId: '2a5571261281d4', requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', @@ -278,6 +281,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5670', adSlot: '/43743431/NativeAutomationPrebid@1x1', + wiid: 'new-unique-wiid' }, bidId: '2a5571261281d4', requestId: 'B68287E1-DC39-4B38-9790-FE4F179739D6', @@ -304,6 +308,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5670', adSlot: '/43743431/NativeAutomationPrebid@1x1', + wiid: 'new-unique-wiid' } }]; @@ -344,6 +349,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '5670', adSlot: '/43743431/NativeAutomationPrebid@1x1', + wiid: 'new-unique-wiid' } }]; @@ -502,6 +508,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '301', adSlot: '/15671365/DMDemo@300x250:0', + wiid: 'new-unique-wiid', video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, @@ -572,6 +579,7 @@ describe('PubMatic adapter', function () { params: { publisherId: '301', adSlot: '/15671365/DMDemo@300x250:0', + wiid: 'new-unique-wiid', video: { mimes: ['video/mp4', 'video/x-flv'], skippable: true, From a631892df98179b9d69299bc90b8bd3d2722dd78 Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 14 Dec 2023 14:16:45 +0100 Subject: [PATCH 275/760] Criteo Bid Adapter: Add support for app.publisher.id in bid request (#10837) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 1 + test/spec/modules/criteoBidAdapter_spec.js | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 45769edaf61..4fe9ca7d0f4 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -607,6 +607,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { }; request.user = bidderRequest.ortb2?.user || {}; request.site = bidderRequest.ortb2?.site || {}; + request.app = bidderRequest.ortb2?.app || {}; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 97b80ce95db..9987df34f2b 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1122,6 +1122,30 @@ describe('The Criteo bidding adapter', function () { expect(request.data.user.uspIab).to.equal('1YNY'); }); + it('should properly build a request with site and app ortb fields', function () { + const bidRequests = []; + let app = { + publisher: { + id: 'appPublisherId' + } + }; + let site = { + publisher: { + id: 'sitePublisherId' + } + }; + const bidderRequest = { + ortb2: { + app: app, + site: site + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + + expect(request.data.app).to.equal(app); + expect(request.data.site).to.equal(site); + }); + it('should properly build a request with device sua field', function () { const sua = {} const bidRequests = [ From 3e57af399bf1d085d3043ff28a133ea8313922f0 Mon Sep 17 00:00:00 2001 From: barRubi <151519394+barRubi@users.noreply.github.com> Date: Thu, 14 Dec 2023 16:44:30 +0200 Subject: [PATCH 276/760] providing visibility in case of adapter timeout and bidError (#10807) Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> --- modules/taboolaBidAdapter.js | 8 ++++ test/spec/modules/taboolaBidAdapter_spec.js | 46 ++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 0d56d6daec7..0bdb0568a10 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -15,6 +15,7 @@ export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; +export const EVENT_ENDPOINT = 'https://beacon.bidder.taboola.com'; /** * extract User Id by that order: @@ -190,6 +191,13 @@ export const spec = { } return syncs; }, + onTimeout: (timeoutData) => { + ajax(EVENT_ENDPOINT + '/timeout', null, JSON.stringify(timeoutData), {method: 'POST'}); + }, + + onBidderError: ({ error, bidderRequest }) => { + ajax(EVENT_ENDPOINT + '/bidError', null, JSON.stringify(error, bidderRequest), {method: 'POST'}); + }, }; function getSiteProperties({publisherId}, refererInfo, ortb2) { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 1b6104599d7..2bfe93bf880 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai'; -import {spec, internal, END_POINT_URL, userData} from 'modules/taboolaBidAdapter.js'; +import {spec, internal, END_POINT_URL, userData, EVENT_ENDPOINT} from 'modules/taboolaBidAdapter.js'; import {config} from '../../../src/config' import * as utils from '../../../src/utils' import {server} from '../../mocks/xhr' @@ -113,6 +113,50 @@ describe('Taboola Adapter', function () { }); }); + describe('onTimeout', function () { + it('onTimeout exist as a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should send timeout', function () { + const timeoutData = [{ + bidder: 'taboola', + bidId: 'da43860a-4644-442a-b5e0-93f268cf8d19', + params: [{ + publisherId: 'publisherId' + }], + adUnitCode: 'adUnit-code', + timeout: 3000, + auctionId: '12a34b56c' + }] + spec.onTimeout(timeoutData); + expect(server.requests[0].method).to.equal('POST'); + expect(server.requests[0].url).to.equal(EVENT_ENDPOINT + '/timeout'); + expect(JSON.parse(server.requests[0].requestBody)).to.deep.equal(timeoutData); + }); + }); + + describe('onBidderError', function () { + it('onBidderError exist as a function', () => { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + it('should send bidder error', function () { + const error = { + status: 204, + statusText: 'No Content' + }; + const bidderRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + } + } + 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); + }); + }); + describe('buildRequests', function () { const defaultBidRequest = { ...createBidRequest(), From f58a0a32fc5f0191f81649420b16c9d28d64255e Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 14 Dec 2023 18:05:30 +0000 Subject: [PATCH 277/760] Prebid 8.28.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e0990121a8..b3612fba397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.28.0-pre", + "version": "8.28.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bca287c1a03..9e68756b23f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.28.0-pre", + "version": "8.28.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2732783683ffb6b161a56fdd52fede78809ddd47 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 14 Dec 2023 18:05:30 +0000 Subject: [PATCH 278/760] Increment version to 8.29.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3612fba397..592fcb3d16d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.28.0", + "version": "8.29.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9e68756b23f..62e99c75911 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.28.0", + "version": "8.29.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 5292b63e820bf1147d1d2c30a2b5048dc703fcc0 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Fri, 15 Dec 2023 18:01:52 +0300 Subject: [PATCH 279/760] Adding support for video.plcmt (#10841) --- modules/yieldmoBidAdapter.js | 4 ++-- test/spec/modules/yieldmoBidAdapter_spec.js | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index e72a671ac74..9109c6e2a80 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -29,7 +29,7 @@ const VIDEO_PATH = '/exchange/prebidvideo'; const STAGE_DOMAIN = 'https://ads-stg.yieldmo.com'; const PROD_DOMAIN = 'https://ads.yieldmo.com'; const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js'; -const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api', +const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'plcmt', 'skipafter', 'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable']; const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords']; const LOCAL_WINDOW = getWindowTop(); @@ -449,7 +449,7 @@ function openRtbImpression(bidRequest) { imp.video.skip = 1; delete imp.video.skippable; } - if (imp.video.placement !== 1) { + if (imp.video.plcmt !== 1 || imp.video.placement !== 1) { imp.video.startdelay = DEFAULT_START_DELAY; imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ]; } diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index a80d0a842b7..edb3ef3af27 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -47,7 +47,7 @@ describe('YieldmoAdapter', function () { video: { playerSize: [640, 480], context: 'instream', - mimes: ['video/mp4'] + mimes: ['video/mp4'], }, }, params: { @@ -61,11 +61,11 @@ describe('YieldmoAdapter', function () { api: [2, 3], skipppable: true, playbackmethod: [1, 2], - ...videoParams - } + ...videoParams, + }, }, transactionId: '54a58774-7a41-494e-8cbc-fa7b79164f0c', - ...rootParams + ...rootParams, }); const mockBidderRequest = (params = {}, bids = [mockBannerBid()]) => ({ @@ -460,6 +460,16 @@ describe('YieldmoAdapter', function () { expect(buildVideoBidAndGetVideoParam().minduration).to.deep.equal(['video/mp4']); }); + it('should add plcmt value to the imp.video', function () { + const videoBid = mockVideoBid({}, {}, { plcmt: 1 }); + expect(utils.deepAccess(videoBid, 'params.video')['plcmt']).to.equal(1); + }); + + it('should add start delay if plcmt value is not 1', function () { + const videoBid = mockVideoBid({}, {}, { plcmt: 2 }); + expect(build([videoBid])[0].data.imp[0].video.startdelay).to.equal(0); + }); + it('should override mediaTypes.video.mimes prop if params.video.mimes is present', function () { utils.deepAccess(videoBid, 'mediaTypes.video')['mimes'] = ['video/mp4']; utils.deepAccess(videoBid, 'params.video')['mimes'] = ['video/mkv']; From a90d6c851f512b4f44c6f11b58dbd798a3adb302 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 09:27:48 -0700 Subject: [PATCH 280/760] Bump github/codeql-action from 2 to 3 (#10856) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 84c97376a3e..3bee8f7c947 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config-file: ./.github/codeql/codeql-config.yml @@ -57,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -70,4 +70,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 5f8ff5f1bbedfe42c923e9b436f3b7abbe80c929 Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Mon, 18 Dec 2023 08:47:18 -0800 Subject: [PATCH 281/760] HadronId Module: implementing user consent in backend calls (#10845) * Implementing user consent in HadronId module * fixing tests accepting just the start of url --- modules/hadronIdSystem.js | 34 +++++++++++++++++++++--- test/spec/modules/hadronIdSystem_spec.js | 4 +-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index c60f0f812a4..4141e9a01f8 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -9,8 +9,11 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isFn, isStr, isPlainObject, logError, logInfo} from '../src/utils.js'; +import { config } from '../src/config.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; +const LOG_PREFIX = '[hadronIdSystem]'; const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; const AU_GVLID = 561; @@ -42,6 +45,8 @@ const urlAddParams = (url, params) => { return url + (url.indexOf('?') > -1 ? '&' : '?') + params } +const isDebug = config.getConfig('debug') || false; + /** @type {Submodule} */ export const hadronIdSubmodule = { /** @@ -88,7 +93,7 @@ export const hadronIdSubmodule = { } catch (error) { logError(error); } - logInfo(`Response from backend is ${responseObj}`); + logInfo(LOG_PREFIX, `Response from backend is ${response}`, responseObj); hadronId = responseObj['hadronId']; storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); responseObj = {id: {hadronId}}; @@ -100,13 +105,34 @@ export const hadronIdSubmodule = { callback(); } }; - logInfo('HadronId not found in storage, calling backend...'); - const url = urlAddParams( + let url = urlAddParams( // config.params.url and config.params.urlArg are not documented // since their use is for debugging purposes only paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid` + `partner_id=${partnerId}&_it=prebid&t=1&src=id` // src=id => the backend was called from getId ); + if (isDebug) { + url += '&debug=1' + } + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent) { + url += `${gdprConsent.consentString ? '&gdprString=' + encodeURIComponent(gdprConsent.consentString) : ''}`; + url += `&gdpr=${gdprConsent.gdprApplies === true ? 1 : 0}`; + } + + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + url += `&us_privacy=${encodeURIComponent(usPrivacyString)}`; + } + + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + url += `${gppConsent.gppString ? '&gpp=' + encodeURIComponent(gppConsent.gppString) : ''}`; + url += `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; + } + + logInfo(LOG_PREFIX, `hadronId not found in storage, calling home (${url})`); + ajax(url, callbacks, undefined, {method: 'GET'}); }; return {callback: resp}; diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index cc0118d4659..85c8cc11c9e 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -22,7 +22,7 @@ describe('HadronIdSystem', function () { const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.eq(`https://id.hadron.ad.gt/api/v1/pbhid?partner_id=0&_it=prebid`); + expect(request.url).to.match(/^https:\/\/id\.hadron\.ad\.gt\/api\/v1\/pbhid/); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); @@ -47,7 +47,7 @@ describe('HadronIdSystem', function () { const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.eq('https://hadronid.publync.com/?partner_id=0&_it=prebid'); + expect(request.url).to.match(/^https:\/\/hadronid\.publync\.com\//); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); From 9a0f012cf4a08a63bc4dbfc50afb570507b49aa1 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:50:09 +0100 Subject: [PATCH 282/760] Eids Docs : add missing EID examples (#10844) * Add missing brackets * Add missing examples * Add back missing ext --- modules/userId/eids.md | 45 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 04073923ed1..eb37c50f0a3 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -77,6 +77,7 @@ userIdAsEids = [ uids: [{ id: 'the-ids-object-stringified', atype: 1 + }] }, { @@ -117,6 +118,50 @@ userIdAsEids = [ }] }, + { + source: 'liveintent.indexexchange.com', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { + provider: 'liveintent.com' + } + }] + }, + + { + source: 'liveintent.sovrn.com'', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { + provider: 'liveintent.com' + } + }] + }, + + { + source: 'openx.net'', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { + provider: 'liveintent.com' + } + }] + }, + + { + source: 'pubmatic.com'', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { + provider: 'liveintent.com' + } + }] + }, + { source: 'media.net', uids: [{ From 332a7d266f0c9e488f822d8d542ad5b74e0a77af Mon Sep 17 00:00:00 2001 From: AdmixerTech <35560933+AdmixerTech@users.noreply.github.com> Date: Tue, 19 Dec 2023 16:43:25 +0200 Subject: [PATCH 283/760] Admixer Bid Adaper: add admixerwl alias (#10859) * Update README.md update * Add admixerwl alias for admixerBidAdapter. --------- Co-authored-by: Yaroslav Masenko --- modules/admixerBidAdapter.js | 10 +++-- modules/admixerBidAdapter.md | 45 +++++++++++++++++++++ test/spec/modules/admixerBidAdapter_spec.js | 43 ++++++++++++++++++-- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index 6cbc36c1dcd..f5f0b5bf665 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -14,6 +14,7 @@ const ALIASES = [ {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, + {code: 'admixerwl', endpoint: 'https://inv-nets-adxwl.admixer.com/adxwlprebid.aspx'}, ]; export const spec = { code: BIDDER_CODE, @@ -23,7 +24,9 @@ export const spec = { * Determines whether or not the given bid request is valid. */ isBidRequestValid: function (bid) { - return !!bid.params.zone; + return bid.bidder === 'admixerwl' + ? !!bid.params.clientId && !!bid.params.endpointId + : !!bid.params.zone; }, /** * Make a server request from the list of BidRequests. @@ -76,10 +79,11 @@ export const spec = { imp.ortb2 && delete imp.ortb2; payload.imps.push(imp); }); + + let urlForRequest = endpointUrl || getEndpointUrl(bidderRequest.bidderCode) return { method: 'POST', - url: - endpointUrl || getEndpointUrl(bidderRequest.bidderCode), + url: bidderRequest.bidderCode === 'admixerwl' ? `${urlForRequest}?client=${payload.imps[0]?.params?.clientId}` : urlForRequest, data: payload, }; }, diff --git a/modules/admixerBidAdapter.md b/modules/admixerBidAdapter.md index 682f5629115..64f8dd64ee4 100644 --- a/modules/admixerBidAdapter.md +++ b/modules/admixerBidAdapter.md @@ -50,3 +50,48 @@ Please use ```admixer``` as the bidder code. }, ]; ``` + +### AdmixerWL Test Parameters +``` + var adUnits = [ + { + code: 'desktop-banner-ad-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "admixer", + params: { + endpointId: 41512, + clientId: 62 + } + } + ] + },{ + code: 'mobile-banner-ad-div', + sizes: [[300, 50]], // a mobile size + bids: [ + { + bidder: "admixer", + params: { + endpointId: 41512, + clientId: 62 + } + } + ] + },{ + code: 'video-ad', + sizes: [[300, 50]], + mediaType: 'video', + bids: [ + { + bidder: "admixer", + params: { + endpointId: 41512, + clientId: 62 + } + } + ] + }, + ]; +``` + diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 8cf433460b7..85538efc957 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,11 +4,12 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; -const BIDDER_CODE_ADX = 'admixeradx'; +const WL_BIDDER_CODE = 'admixerwl' const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ENDPOINT_URL_CUSTOM = 'https://custom.admixer.net/prebid.aspx'; -const ENDPOINT_URL_ADX = 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'; const ZONE_ID = '2eb6bd58-865c-47ce-af7f-a918108c3fd2'; +const CLIENT_ID = 5124; +const ENDPOINT_ID = 81264; describe('AdmixerAdapter', function () { const adapter = newBidder(spec); @@ -36,9 +37,28 @@ describe('AdmixerAdapter', function () { auctionId: '1d1a030790a475', }; + let wlBid = { + bidder: WL_BIDDER_CODE, + params: { + clientId: CLIENT_ID, + endpointId: ENDPOINT_ID, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [300, 600], + ], + bidId: '30b31c1838de1e', + bidderRequestId: '22edbae2733bf6', + auctionId: '1d1a030790a475', + }; + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when params required by WL found', function () { + expect(spec.isBidRequestValid(wlBid)).to.equal(true); + }); it('should return false when required params are not passed', function () { let bid = Object.assign({}, bid); @@ -48,6 +68,14 @@ describe('AdmixerAdapter', function () { }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); + it('should return false when params required by WL are not passed', function () { + let wlBid = Object.assign({}, wlBid); + delete wlBid.params; + wlBid.params = { + clientId: 0, + }; + expect(spec.isBidRequestValid(wlBid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -105,7 +133,10 @@ describe('AdmixerAdapter', function () { validRequest: [ { bidder: bidder, - params: { + params: bidder === 'admixerwl' ? { + clientId: CLIENT_ID, + endpointId: ENDPOINT_ID + } : { zone: ZONE_ID, }, adUnitCode: 'adunit-code', @@ -168,6 +199,12 @@ describe('AdmixerAdapter', function () { expect(request.url).to.equal('https://inv-nets.admixer.net/adxprebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); + it('build request for admixerwl', function () { + const requestParams = requestParamsFor('admixerwl'); + const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); + expect(request.url).to.equal(`https://inv-nets-adxwl.admixer.com/adxwlprebid.aspx?client=${CLIENT_ID}`); + expect(request.method).to.equal('POST'); + }); }); describe('checkFloorGetting', function () { From cd787ebd7caab60888e8bdf731afa2dcecffa53a Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 19 Dec 2023 16:05:05 +0100 Subject: [PATCH 284/760] AdagioAnalyticsAdapter: fix function param check (#10860) --- modules/adagioAnalyticsAdapter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index ee2368fde08..807937ba3dd 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -97,6 +97,10 @@ function getAdapterNameForAlias(aliasName) { }; function isAdagio(value) { + if (!value) { + return false + } + return value.toLowerCase().includes('adagio') || getAdapterNameForAlias(value).toLowerCase().includes('adagio'); }; From 0b59d4d7fbd2224a9225bbdb0e9819aac0cdf970 Mon Sep 17 00:00:00 2001 From: redaguermas Date: Wed, 20 Dec 2023 04:35:29 -0800 Subject: [PATCH 285/760] NoBid Analytics Adapter: support for counting blocked requests for the Optimizer (#10842) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Delete nobidBidAdapter.js.orig * Delete a * Delete .jsdtscope * Delete org.eclipse.wst.jsdt.ui.superType.container * Delete org.eclipse.wst.jsdt.ui.superType.name * Delete .project * Added support for counting blocked requests for the Optimizer. * Added missing function for testing. * Added unit tests --------- Co-authored-by: Reda Guermas --- modules/nobidAnalyticsAdapter.js | 67 +++++++++++++++---- .../modules/nobidAnalyticsAdapter_spec.js | 8 ++- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index 27ec1cd9451..3a7912c37e1 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -6,10 +6,10 @@ import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const VERSION = '1.0.4'; +const VERSION = '1.1.0'; const MODULE_NAME = 'nobidAnalyticsAdapter'; -const ANALYTICS_DATA_NAME = 'analytics.nobid.io'; -const RETENTION_SECONDS = 7 * 24 * 3600; +const ANALYTICS_OPT_FLUSH_TIMEOUT_SECONDS = 5 * 1000; +const RETENTION_SECONDS = 1 * 24 * 3600; const TEST_ALLOCATION_PERCENTAGE = 5; // dont block 5% of the time; window.nobidAnalyticsVersion = VERSION; const analyticsType = 'endpoint'; @@ -148,7 +148,7 @@ nobidAnalytics = { return isExpired(data, this.retentionSeconds); }, isAnalyticsDisabled () { - let stored = storage.getDataFromLocalStorage(ANALYTICS_DATA_NAME); + let stored = storage.getDataFromLocalStorage(this.ANALYTICS_DATA_NAME); if (!isJson(stored)) return false; stored = JSON.parse(stored); if (this.isExpired(stored)) return false; @@ -157,8 +157,10 @@ nobidAnalytics = { processServerResponse (response) { if (!isJson(response)) return; const resp = JSON.parse(response); - storage.setDataInLocalStorage(ANALYTICS_DATA_NAME, JSON.stringify({ ...resp, ts: Date.now() })); - } + storage.setDataInLocalStorage(this.ANALYTICS_DATA_NAME, JSON.stringify({ ...resp, ts: Date.now() })); + }, + ANALYTICS_DATA_NAME: 'analytics.nobid.io', + ANALYTICS_OPT_NAME: 'analytics.nobid.io.optData' } adapterManager.registerAnalyticsAdapter({ @@ -166,33 +168,74 @@ adapterManager.registerAnalyticsAdapter({ code: 'nobidAnalytics', gvlid: GVLID }); +nobidAnalytics.originalAdUnits = {}; window.nobidCarbonizer = { getStoredLocalData: function () { - return storage.getDataFromLocalStorage(ANALYTICS_DATA_NAME); + const a = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_DATA_NAME); + const b = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_OPT_NAME); + const ret = {}; + if (a) ret[nobidAnalytics.ANALYTICS_DATA_NAME] = a; + if (b) ret[nobidAnalytics.ANALYTICS_OPT_NAME] = b + return ret; }, isActive: function () { - let stored = storage.getDataFromLocalStorage(ANALYTICS_DATA_NAME); + let stored = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_DATA_NAME); if (!isJson(stored)) return false; stored = JSON.parse(stored); if (isExpired(stored, nobidAnalytics.retentionSeconds)) return false; return stored.carbonizer_active || false; }, carbonizeAdunits: function (adunits, skipTestGroup) { + function processBlockedBidders (blockedBidders) { + function sendOptimizerData() { + let optData = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_OPT_NAME); + storage.removeDataFromLocalStorage(nobidAnalytics.ANALYTICS_OPT_NAME); + if (isJson(optData)) { + optData = JSON.parse(optData); + if (Object.getOwnPropertyNames(optData).length > 0) { + const event = { o_bidders: optData }; + if (nobidAnalytics.topLocation) event.topLocation = nobidAnalytics.topLocation; + sendEvent(event, 'optData'); + } + } + } + if (blockedBidders && blockedBidders.length > 0) { + let optData = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_OPT_NAME); + optData = isJson(optData) ? JSON.parse(optData) : {}; + const bidders = blockedBidders.map(rec => rec.bidder); + if (bidders && bidders.length > 0) { + bidders.forEach(bidder => { + if (!optData[bidder]) optData[bidder] = 1; + else optData[bidder] += 1; + }); + storage.setDataInLocalStorage(nobidAnalytics.ANALYTICS_OPT_NAME, JSON.stringify(optData)); + if (window.nobidAnalyticsOptTimer) return; + window.nobidAnalyticsOptTimer = setInterval(sendOptimizerData, ANALYTICS_OPT_FLUSH_TIMEOUT_SECONDS); + } + } + } function carbonizeAdunit (adunit) { - let stored = storage.getDataFromLocalStorage(ANALYTICS_DATA_NAME); + let stored = storage.getDataFromLocalStorage(nobidAnalytics.ANALYTICS_DATA_NAME); if (!isJson(stored)) return; stored = JSON.parse(stored); if (isExpired(stored, nobidAnalytics.retentionSeconds)) return; const carbonizerBidders = stored.bidders || []; - const allowedBidders = adunit.bids.filter(rec => carbonizerBidders.includes(rec.bidder)); + let originalAdUnit = null; + if (nobidAnalytics.originalAdUnits && nobidAnalytics.originalAdUnits[adunit.code]) originalAdUnit = nobidAnalytics.originalAdUnits[adunit.code]; + const allowedBidders = originalAdUnit.bids.filter(rec => carbonizerBidders.includes(rec.bidder)); + const blockedBidders = originalAdUnit.bids.filter(rec => !carbonizerBidders.includes(rec.bidder)); + processBlockedBidders(blockedBidders); adunit.bids = allowedBidders; } + for (const adunit of adunits) { + if (!nobidAnalytics.originalAdUnits[adunit.code]) nobidAnalytics.originalAdUnits[adunit.code] = JSON.parse(JSON.stringify(adunit)); + }; if (this.isActive()) { // 5% of the time do not block; if (!skipTestGroup && Math.floor(Math.random() * 101) <= TEST_ALLOCATION_PERCENTAGE) return; - adunits.forEach(adunit => { + for (const adunit of adunits) { carbonizeAdunit(adunit); - }); + }; } } }; diff --git a/test/spec/modules/nobidAnalyticsAdapter_spec.js b/test/spec/modules/nobidAnalyticsAdapter_spec.js index 742b4c16abb..06a39ffd020 100644 --- a/test/spec/modules/nobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/nobidAnalyticsAdapter_spec.js @@ -466,8 +466,8 @@ describe('NoBid Prebid Analytic', function () { const previousRetention = nobidAnalytics.retentionSeconds; nobidAnalytics.retentionSeconds = 3; nobidAnalytics.processServerResponse(JSON.stringify({carbonizer_active: true})); - const stored = nobidCarbonizer.getStoredLocalData(); - expect(stored).to.contain(`{"carbonizer_active":true,"ts":`); + let stored = nobidCarbonizer.getStoredLocalData(); + expect(stored[nobidAnalytics.ANALYTICS_DATA_NAME]).to.contain(`{"carbonizer_active":true,"ts":`); clock.tick(5000); active = nobidCarbonizer.isActive(adunits, true); expect(active).to.equal(false); @@ -486,6 +486,10 @@ describe('NoBid Prebid Analytic', function () { } ] nobidCarbonizer.carbonizeAdunits(adunits, true); + stored = nobidCarbonizer.getStoredLocalData(); + expect(stored[nobidAnalytics.ANALYTICS_DATA_NAME]).to.contain('{"carbonizer_active":true,"ts":'); + expect(stored[nobidAnalytics.ANALYTICS_OPT_NAME]).to.contain('{"bidder1":1,"bidder2":1}'); + clock.tick(5000); expect(adunits[0].bids.length).to.equal(0); done(); From a5c2e06c836ab366112457aa04a2961a8d59990e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 20 Dec 2023 10:36:32 -0800 Subject: [PATCH 286/760] consentManagementGpp: fix handling of CMP errors (#10811) --- modules/consentManagementGpp.js | 13 +++++++++---- test/spec/modules/consentManagementGpp_spec.js | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 8160ee2378c..f696ce25902 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -70,13 +70,18 @@ export class GPPClient { * - a promise to GPP data. */ static init(mkCmp = cmpClient) { - if (this.INST == null) { - this.INST = this.ping(mkCmp).catch(e => { - this.INST = null; + let inst = this.INST; + if (!inst) { + let err; + const reset = () => err && (this.INST = null); + inst = this.INST = this.ping(mkCmp).catch(e => { + err = true; + reset(); throw e; }); + reset(); } - return this.INST.then(([client, pingData]) => [ + return inst.then(([client, pingData]) => [ client, client.initialized ? client.refresh() : client.init(pingData) ]); diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index 99d4f94f502..93a876d0233 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -290,7 +290,7 @@ describe('consentManagementGpp', function () { }); it('should not re-use errors', (done) => { - cmpResult = Promise.reject(new Error()); + cmpResult = GreedyPromise.reject(new Error()); GPPClient.init(makeCmp).catch(() => { cmpResult = {signalStatus: 'ready'}; return GPPClient.init(makeCmp).then(([client]) => { From 104f53f0b7b8716c97b7758f58af48d61c219c30 Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Wed, 20 Dec 2023 20:37:03 +0200 Subject: [PATCH 287/760] Taboola Bid Adapter: implement Iframe user sync (#10789) * iframe-sync * iframe-sync-add-tests --- modules/taboolaBidAdapter.js | 8 ++++++++ test/spec/modules/taboolaBidAdapter_spec.js | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 0bdb0568a10..a8745449ee4 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -12,6 +12,7 @@ const GVLID = 42; const CURRENCY = 'USD'; export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; export const USER_SYNC_IMG_URL = 'https://trc.taboola.com/sg/prebidJS/1/cm'; +export const USER_SYNC_IFRAME_URL = 'https://cdn.taboola.com/scripts/prebid_iframe_sync.html'; const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; @@ -183,6 +184,13 @@ export const spec = { queryParams.push('gpp=' + encodeURIComponent(gppConsent)); } + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: USER_SYNC_IFRAME_URL + (queryParams.length ? '?' + queryParams.join('&') : '') + }); + } + if (syncOptions.pixelEnabled) { syncs.push({ type: 'image', diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 2bfe93bf880..402b4990166 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -835,17 +835,28 @@ describe('Taboola Adapter', function () { describe('getUserSyncs', function () { const usersyncUrl = 'https://trc.taboola.com/sg/prebidJS/1/cm'; + const iframeUrl = 'https://cdn.taboola.com/scripts/prebid_iframe_sync.html'; - it('should not return user sync if pixelEnabled is false', function () { - const res = spec.getUserSyncs({pixelEnabled: false}); + it('should not return user sync if pixelEnabled is false and iframe disabled', function () { + const res = spec.getUserSyncs({pixelEnabled: false, iframeEnabled: false}); expect(res).to.be.an('array').that.is.empty; }); it('should return user sync if pixelEnabled is true', function () { - const res = spec.getUserSyncs({pixelEnabled: true}); + const res = spec.getUserSyncs({pixelEnabled: true, iframeEnabled: false}); expect(res).to.deep.equal([{type: 'image', url: usersyncUrl}]); }); + it('should return user sync if iframeEnabled is true', function () { + const res = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}); + expect(res).to.deep.equal([{type: 'iframe', url: iframeUrl}]); + }); + + it('should return both user syncs if iframeEnabled is true and pixelEnabled is true', function () { + const res = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}); + expect(res).to.deep.equal([{type: 'iframe', url: iframeUrl}, {type: 'image', url: usersyncUrl}]); + }); + it('should pass consent tokens values', function() { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {gdprApplies: true, consentString: 'GDPR_CONSENT'}, 'USP_CONSENT')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` From 2548949ec03ee755536356a1d12d2acc05e6af71 Mon Sep 17 00:00:00 2001 From: Nick Jacob Date: Wed, 20 Dec 2023 13:37:44 -0500 Subject: [PATCH 288/760] AMX bid adapter: fix timeout handler, bump version (#10744) * amx bid adapter: fix timeout handler, bump version * restore package-lock to master * remove sendbeacon, use mock xhr * add keepalive option for ajax * fix firefox test * CR changes * CR changes: restore files * CR changes --- modules/amxBidAdapter.js | 116 ++++++++++++--- test/spec/modules/amxBidAdapter_spec.js | 187 ++++++++++++++++-------- 2 files changed, 220 insertions(+), 83 deletions(-) diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index a773ac70559..6e14f65b0c8 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -14,20 +14,31 @@ import { } from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { fetch } from '../src/ajax.js'; const BIDDER_CODE = 'amx'; const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const SIMPLE_TLD_TEST = /\.com?\.\w{2,4}$/; const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; -const VERSION = 'pba1.3.3'; +const VERSION = 'pba1.3.4'; const VAST_RXP = /^\s*<\??(?:vast|xml)/i; -const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; +const TRACKING_BASE = 'https://1x1.a-mo.net/'; +const TRACKING_ENDPOINT = TRACKING_BASE + 'hbx/'; +const POST_TRACKING_ENDPOINT = TRACKING_BASE + 'e'; const AMUID_KEY = '__amuidpb'; function getLocation(request) { return parseUrl(request.refererInfo?.topmostLocation || window.location.href); } +function getTimeoutSize(timeoutData) { + if (timeoutData.sizes == null || timeoutData.sizes.length === 0) { + return [0, 0]; + } + + return timeoutData.sizes[0]; +} + const largestSize = (sizes, mediaTypes) => { const allSizes = sizes .concat(deepAccess(mediaTypes, `${BANNER}.sizes`, []) || []) @@ -149,7 +160,9 @@ function convertRequest(bid) { const tid = deepAccess(bid, 'params.tagId'); const au = - bid.params != null && typeof bid.params.adUnitId === 'string' && bid.params.adUnitId !== '' + bid.params != null && + typeof bid.params.adUnitId === 'string' && + bid.params.adUnitId !== '' ? bid.params.adUnitId : bid.adUnitCode; @@ -202,7 +215,10 @@ function isSyncEnabled(syncConfigP, syncType) { return false; } - if (syncConfig.bidders === '*' || (isArray(syncConfig.bidders) && syncConfig.bidders.indexOf('amx') !== -1)) { + if ( + syncConfig.bidders === '*' || + (isArray(syncConfig.bidders) && syncConfig.bidders.indexOf('amx') !== -1) + ) { return syncConfig.filter == null || syncConfig.filter === 'include'; } @@ -219,12 +235,17 @@ function getSyncSettings() { d: 0, l: 0, t: 0, - e: true + e: true, }; } - const settings = { d: syncConfig.syncDelay, l: syncConfig.syncsPerBidder, t: 0, e: syncConfig.syncEnabled } - const all = isSyncEnabled(syncConfig.filterSettings, 'all') + const settings = { + d: syncConfig.syncDelay, + l: syncConfig.syncsPerBidder, + t: 0, + e: syncConfig.syncEnabled, + }; + const all = isSyncEnabled(syncConfig.filterSettings, 'all'); if (all) { settings.t = SYNC_IMAGE & SYNC_IFRAME; @@ -256,12 +277,14 @@ function getGpp(bidderRequest) { return bidderRequest.gppConsent; } - return bidderRequest?.ortb2?.regs?.gpp ?? { gppString: '', applicableSections: '' }; + return ( + bidderRequest?.ortb2?.regs?.gpp ?? { gppString: '', applicableSections: '' } + ); } function buildReferrerInfo(bidderRequest) { if (bidderRequest.refererInfo == null) { - return { r: '', t: false, c: '', l: 0, s: [] } + return { r: '', t: false, c: '', l: 0, s: [] }; } const re = bidderRequest.refererInfo; @@ -272,7 +295,7 @@ function buildReferrerInfo(bidderRequest) { l: re.numIframes, s: re.stack, c: re.canonicalUrl, - } + }; } const isTrue = (boolValue) => @@ -358,28 +381,35 @@ export const spec = { return { data: payload, method: 'POST', + browsingTopics: true, url: deepAccess(bidRequests[0], 'params.endpoint', DEFAULT_ENDPOINT), withCredentials: true, }; }, - getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + getUserSyncs( + syncOptions, + serverResponses, + gdprConsent, + uspConsent, + gppConsent + ) { const qp = { gdpr_consent: enc(gdprConsent?.consentString || ''), gdpr: enc(gdprConsent?.gdprApplies ? 1 : 0), us_privacy: enc(uspConsent || ''), gpp: enc(gppConsent?.gppString || ''), - gpp_sid: enc(gppConsent?.applicableSections || '') + gpp_sid: enc(gppConsent?.applicableSections || ''), }; const iframeSync = { url: `https://prebid.a-mo.net/isyn?${formatQS(qp)}`, - type: 'iframe' + type: 'iframe', }; if (serverResponses == null || serverResponses.length === 0) { if (syncOptions.iframeEnabled) { - return [iframeSync] + return [iframeSync]; } return []; @@ -394,7 +424,10 @@ export const spec = { const pixelType = syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image'; if (syncOptions.iframeEnabled || pixelType === 'image') { - hasFrame = hasFrame || (pixelType === 'iframe') || (syncPixel.indexOf('cchain') !== -1) + hasFrame = + hasFrame || + pixelType === 'iframe' || + syncPixel.indexOf('cchain') !== -1; output.push({ url: syncPixel, type: pixelType, @@ -405,7 +438,7 @@ export const spec = { }); if (!hasFrame && output.length < 2) { - output.push(iframeSync) + output.push(iframeSync); } return output; @@ -470,19 +503,58 @@ export const spec = { aud: targetingData.requestId, a: targetingData.adUnitCode, c2: nestedQs(targetingData.adserverTargeting), + cn3: targetingData.timeToRespond, }); }, onTimeout(timeoutData) { - if (timeoutData == null) { + if (timeoutData == null || !timeoutData.length) { return; } - trackEvent('pbto', { - A: timeoutData.bidder, - bid: timeoutData.bidId, - a: timeoutData.adUnitCode, - cn: timeoutData.timeout, + let common = null; + const events = timeoutData.map((timeout) => { + const params = timeout.params || {}; + const size = getTimeoutSize(timeout); + const { domain, page, ref } = + timeout.ortb2 != null && timeout.ortb2.site != null + ? timeout.ortb2.site + : {}; + + if (common == null) { + common = { + do: domain, + u: page, + U: getUIDSafe(), + re: ref, + V: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', + }; + } + + return { + A: timeout.bidder, + mid: params.tagId, + a: params.adunitId || timeout.adUnitCode, + bid: timeout.bidId, + n: 'g_pbto', + aud: timeout.transactionId, + w: size[0], + h: size[1], + cn: timeout.timeout, + cn2: timeout.bidderRequestsCount, + cn3: timeout.bidderWinsCount, + }; + }); + + const payload = JSON.stringify({ c: common, e: events }); + fetch(POST_TRACKING_ENDPOINT, { + body: payload, + keepalive: true, + withCredentials: true, + method: 'POST' + }).catch((_e) => { + // do nothing; ignore errors }); }, diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 984c443344d..21fa2e2617c 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec } from 'modules/amxBidAdapter.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; +import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; const sampleRequestId = '82c91e127a9b93e'; @@ -11,7 +12,7 @@ const sampleDisplayCRID = '78827819'; // minimal example vast const sampleVideoAd = (addlImpression) => ` -00:00:15${addlImpression} +00:00:15${addlImpression} `.replace(/\n+/g, ''); const sampleFPD = { @@ -37,7 +38,7 @@ const sampleBidderRequest = { }, gppConsent: { gppString: 'example', - applicableSections: 'example' + applicableSections: 'example', }, auctionId: null, @@ -209,10 +210,12 @@ describe('AmxBidAdapter', () => { describe('getUserSync', () => { it('Will perform an iframe sync even if there is no server response..', () => { const syncs = spec.getUserSyncs({ iframeEnabled: true }); - expect(syncs).to.eql([{ - type: 'iframe', - url: 'https://prebid.a-mo.net/isyn?gdpr_consent=&gdpr=0&us_privacy=&gpp=&gpp_sid=' - }]); + expect(syncs).to.eql([ + { + type: 'iframe', + url: 'https://prebid.a-mo.net/isyn?gdpr_consent=&gdpr=0&us_privacy=&gpp=&gpp_sid=', + }, + ]); }); it('will return valid syncs from a server response', () => { @@ -276,8 +279,13 @@ describe('AmxBidAdapter', () => { }); it('will attach additional referrer info data', () => { - const { data } = spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); - expect(data.ri.r).to.equal(sampleBidderRequest.refererInfo.topmostLocation); + const { data } = spec.buildRequests( + [sampleBidRequestBase], + sampleBidderRequest + ); + expect(data.ri.r).to.equal( + sampleBidderRequest.refererInfo.topmostLocation + ); expect(data.ri.t).to.equal(sampleBidderRequest.refererInfo.reachedTop); expect(data.ri.l).to.equal(sampleBidderRequest.refererInfo.numIframes); expect(data.ri.s).to.equal(sampleBidderRequest.refererInfo.stack); @@ -315,7 +323,7 @@ describe('AmxBidAdapter', () => { [sampleBidRequestBase], sampleBidderRequest ); - delete data.m; // don't deal with "m" in this test + delete data.m; // don't deal with 'm' in this test expect(data.gs).to.equal(sampleBidderRequest.gdprConsent.gdprApplies); expect(data.gc).to.equal(sampleBidderRequest.gdprConsent.consentString); expect(data.usp).to.equal(sampleBidderRequest.uspConsent); @@ -343,10 +351,8 @@ describe('AmxBidAdapter', () => { }); it('will attach sync configuration', () => { - const request = () => spec.buildRequests( - [sampleBidRequestBase], - sampleBidderRequest - ); + const request = () => + spec.buildRequests([sampleBidRequestBase], sampleBidderRequest); const setConfig = (filterSettings) => config.setConfig({ @@ -355,56 +361,73 @@ describe('AmxBidAdapter', () => { syncDelay: 2300, syncEnabled: true, filterSettings, - } + }, }); const test = (filterSettings) => { setConfig(filterSettings); return request().data.sync; - } + }; const base = { d: 2300, l: 2, e: true }; - const tests = [[ - undefined, - { ...base, t: 0 } - ], [{ - image: { - bidders: '*', - filter: 'include' - }, - iframe: { - bidders: '*', - filter: 'include' - } - }, { ...base, t: 3 }], [{ - image: { - bidders: ['amx'], - }, - iframe: { - bidders: '*', - filter: 'include' - } - }, { ...base, t: 3 }], [{ - image: { - bidders: ['other'], - }, - iframe: { - bidders: '*' - } - }, { ...base, t: 2 }], [{ - image: { - bidders: ['amx'] - }, - iframe: { - bidders: ['amx'], - filter: 'exclude' - } - }, { ...base, t: 1 }]] + const tests = [ + [undefined, { ...base, t: 0 }], + [ + { + image: { + bidders: '*', + filter: 'include', + }, + iframe: { + bidders: '*', + filter: 'include', + }, + }, + { ...base, t: 3 }, + ], + [ + { + image: { + bidders: ['amx'], + }, + iframe: { + bidders: '*', + filter: 'include', + }, + }, + { ...base, t: 3 }, + ], + [ + { + image: { + bidders: ['other'], + }, + iframe: { + bidders: '*', + }, + }, + { ...base, t: 2 }, + ], + [ + { + image: { + bidders: ['amx'], + }, + iframe: { + bidders: ['amx'], + filter: 'exclude', + }, + }, + { ...base, t: 1 }, + ], + ]; for (let i = 0, l = tests.length; i < l; i++) { const [result, expected] = tests[i]; - expect(test(result), `input: ${JSON.stringify(result)}`).to.deep.equal(expected); + expect(test(result), `input: ${JSON.stringify(result)}`).to.deep.equal( + expected + ); } }); @@ -497,7 +520,15 @@ describe('AmxBidAdapter', () => { it('can build a video request', () => { const { data } = spec.buildRequests( - [{ ...sampleBidRequestVideo, params: { ...sampleBidRequestVideo.params, adUnitId: 'custom-auid' } }], + [ + { + ...sampleBidRequestVideo, + params: { + ...sampleBidRequestVideo.params, + adUnitId: 'custom-auid', + }, + }, + ], sampleBidderRequest ); expect(Object.keys(data.m).length).to.equal(1); @@ -659,15 +690,49 @@ describe('AmxBidAdapter', () => { }); it('will log an event for timeout', () => { - spec.onTimeout({ - bidder: 'example', - bidId: 'test-bid-id', - adUnitCode: 'div-gpt-ad', - timeout: 300, - auctionId: utils.getUniqueIdentifierStr(), + // this will use sendBeacon.. + spec.onTimeout([ + { + bidder: 'example', + bidId: 'test-bid-id', + adUnitCode: 'div-gpt-ad', + ortb2: { + site: { + ref: 'https://example.com', + }, + }, + params: { + tagId: 'tag-id', + }, + timeout: 300, + auctionId: utils.getUniqueIdentifierStr(), + }, + ]); + + const [request] = server.requests; + request.respond(204, {'Content-Type': 'text/html'}, null); + expect(request.url).to.equal('https://1x1.a-mo.net/e'); + + if (typeof Request !== 'undefined' && 'keepalive' in Request.prototype) { + expect(request.fetch.request.keepalive).to.equal(true); + } + + const {c: common, e: events} = JSON.parse(request.requestBody) + expect(common).to.deep.equal({ + V: '$prebid.version$', + vg: '$$PREBID_GLOBAL$$', + U: null, + re: 'https://example.com', }); - expect(firedPixels.length).to.equal(1); - expect(firedPixels[0]).to.match(/\/hbx\/g_pbto/); + + expect(events.length).to.equal(1); + const [event] = events; + expect(event.n).to.equal('g_pbto') + expect(event.A).to.equal('example'); + expect(event.mid).to.equal('tag-id'); + expect(event.cn).to.equal(300); + expect(event.bid).to.equal('test-bid-id'); + expect(event.a).to.equal('div-gpt-ad'); }); it('will log an event for prebid win', () => { From 2b8aacbd7886be767722c285a233906ace280634 Mon Sep 17 00:00:00 2001 From: jackhsiehucf <77815341+jackhsiehucf@users.noreply.github.com> Date: Thu, 21 Dec 2023 03:09:09 +0800 Subject: [PATCH 289/760] Ucfunnel Bid Adapter: add format support (#10862) * Add a new ucfunnel Adapter and test page * Add a new ucfunnel Adapter and test page * 1. Use prebid lib in the repo to keep updated 2. Replace var with let 3. Put JSON.parse(JSON.stringify()) into try catch block * utils.getTopWindowLocation is a function * Change to modules from adapters * Migrate to module design * [Dev Fix] Remove width and height which can be got from ad unit id * Update ucfunnelBidAdapter to fit into new spec * Correct the endpoint. Fix the error of query string * Add test case for ucfunnelBidAdapter * Fix lint error * Update version number * Combine all checks on bid request * Add GDPR support for ucfunnel adapter * Add in-stream video and native support for ucfunnel adapter * Remove demo page. Add more test cases. * Change request method from POST to GET * Remove unnecessary comment * Support vastXml and vastUrl for video request * update TTL to 30 mins * Avoid using arrow function which is not discuraged in mocha * ucfunnel tdid support * ucfunnel fix error message in debug mode * ucfunnel adapter add bidfloor parameter * ucfunnel adapter support CCPA * ucfunnel adapter native support clicktrackers * ucfunnel adapter change cookie sync setting * ucfunnel adapter update request parameter * Update ucfunnelBidAdapter * ucfunnel adapter add currency in ad response * ucfunnel adapter support uid2 * ucfunnel Bid Adapter: add support for FLoC and Verizon Media ConnectID * ucfunnel Bid Adapter: add support Price Floors Module * ucfunnel Bid Adapter: add GPID support and fix page and domain parameter. * ucfunnel Bid Adapter: add format support. --------- Co-authored-by: root Co-authored-by: Ryan Chou Co-authored-by: ucfunnel Co-authored-by: jack.hsieh --- modules/ucfunnelBidAdapter.js | 53 ++++++++++---------- test/spec/modules/ucfunnelBidAdapter_spec.js | 18 +++---- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 2225deaa900..67f3cebdce8 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -68,7 +68,6 @@ export const spec = { interpretResponse: function (ucfunnelResponseObj, request) { const bidRequest = request.bidRequest; const ad = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; - const videoPlayerSize = parseSizes(bidRequest); let bid = { requestId: bidRequest.bidId, @@ -117,10 +116,10 @@ export const spec = { vastXml: ad.vastXml }); - if (videoPlayerSize && videoPlayerSize.length === 2) { + if (bidRequest.sizes && bidRequest.sizes.length > 0) { Object.assign(bid, { - width: videoPlayerSize[0], - height: videoPlayerSize[1] + width: bidRequest.sizes[0][0], + height: bidRequest.sizes[0][1] }); } break; @@ -128,8 +127,8 @@ export const spec = { default: var size = parseSizes(bidRequest); Object.assign(bid, { - width: ad.width || size[0], - height: ad.height || size[1], + width: ad.width || size[0][0], + height: ad.height || size[0][1], ad: ad.adm || '' }); } @@ -156,12 +155,6 @@ export const spec = { }; registerBidder(spec); -function transformSizes(requestSizes) { - if (typeof requestSizes === 'object' && requestSizes.length) { - return requestSizes[0]; - } -} - function getCookieSyncParameter(gdprApplies, apiVersion, consentString, uspConsent) { let param = '?'; if (gdprApplies == '1') { @@ -187,11 +180,10 @@ function parseSizes(bid) { params.video.playerWidth, params.video.playerHeight ]; - return size; + return [size]; } } - - return transformSizes(bid.sizes); + return bid.sizes; } function getSupplyChain(schain) { @@ -244,6 +236,20 @@ function getFloor(bid, size, mediaTypes) { return undefined; } +function addBidData(bidData, key, value) { + if (value) { + bidData[key] = value; + } +} + +function getFormat(size) { + let formatList = [] + for (var i = 0; i < size.length; i++) { + formatList.push(size[i].join(',')); + } + return (formatList.length > 0) ? formatList.join(';') : ''; +} + function getRequestData(bid, bidderRequest) { const size = parseSizes(bid); const language = navigator.language; @@ -264,14 +270,8 @@ function getRequestData(bid, bidderRequest) { schain: supplyChain }; - if (bidFloor) { - bidData.fp = bidFloor; - } - - if (gpid) { - bidData.gpid = gpid; - } - + addBidData(bidData, 'fp', bidFloor); + addBidData(bidData, 'gpid', gpid); addUserId(bidData, bid.userId); bidData.u = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; @@ -293,10 +293,11 @@ function getRequestData(bid, bidderRequest) { } } - if (size != undefined && size.length == 2) { - bidData.w = size[0]; - bidData.h = size[1]; + if (size != undefined && size.length > 0 && size[0].length == 2) { + bidData.w = size[0][0]; + bidData.h = size[0][1]; } + addBidData(bidData, 'format', getFormat(size)); if (bidderRequest && bidderRequest.uspConsent) { Object.assign(bidData, { diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index 9bec7229450..998e0db6fe8 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -30,7 +30,7 @@ const validBannerBidReq = { params: { adid: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D' }, - sizes: [[300, 250]], + sizes: [[300, 250], [336, 280]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', ortb2Imp: { @@ -180,15 +180,15 @@ describe('ucfunnel Adapter', function () { expect(data.schain).to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'); }); - it('must parse bid size from a nested array', function () { - const width = 640; - const height = 480; - const bid = deepClone(validBannerBidReq); - bid.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ bid ], bidderRequest); + it('should support multiple size', function () { + const sizes = [[300, 250], [336, 280]]; + const format = '300,250;336,280'; + validBannerBidReq.sizes = sizes; + const requests = spec.buildRequests([ validBannerBidReq ], bidderRequest); const data = requests[0].data; - expect(data.w).to.equal(width); - expect(data.h).to.equal(height); + expect(data.w).to.equal(sizes[0][0]); + expect(data.h).to.equal(sizes[0][1]); + expect(data.format).to.equal(format); }); it('should set bidfloor if configured', function() { From 3c632ca35a8060b1530ebded1a4746b828b52af1 Mon Sep 17 00:00:00 2001 From: alexalukr2b2 <136449554+alexalukr2b2@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:41:19 +0100 Subject: [PATCH 290/760] R2B2 Bid Adapter: Initial release (#10702) * R2B2 bidder adapter * R2B2 bid adapter: fix * conditional renderer --------- Co-authored-by: jenda --- modules/r2b2BidAdapter.js | 309 ++++++++++ modules/r2b2BidAdapter.md | 37 ++ test/spec/modules/r2b2BidAdapter_spec.js | 689 +++++++++++++++++++++++ 3 files changed, 1035 insertions(+) create mode 100644 modules/r2b2BidAdapter.js create mode 100644 modules/r2b2BidAdapter.md create mode 100644 test/spec/modules/r2b2BidAdapter_spec.js diff --git a/modules/r2b2BidAdapter.js b/modules/r2b2BidAdapter.js new file mode 100644 index 00000000000..15a65e3924c --- /dev/null +++ b/modules/r2b2BidAdapter.js @@ -0,0 +1,309 @@ +import {logWarn, logError, triggerPixel, deepSetValue, getParameterByName} from '../src/utils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {Renderer} from '../src/Renderer.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import {pbsExtensions} from '../libraries/pbsExtensions/pbsExtensions.js'; +import {bidderSettings} from '../src/bidderSettings.js'; + +const ADAPTER_VERSION = '1.0.0'; +const BIDDER_CODE = 'r2b2'; +const GVL_ID = 1235; + +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_TTL = 360; +const DEFAULT_NET_REVENUE = true; +const DEBUG_PARAM = 'pbjs_test_r2b2'; +const RENDERER_URL = 'https://delivery.r2b2.io/static/rendering.js'; + +const ENDPOINT = bidderSettings.get(BIDDER_CODE, 'endpoint') || 'hb.r2b2.cz'; +const SERVER_URL = 'https://' + ENDPOINT; +const URL_BID = SERVER_URL + '/openrtb2/bid'; +const URL_SYNC = SERVER_URL + '/cookieSync'; +const URL_EVENT = SERVER_URL + '/event'; + +const URL_EVENT_ON_BIDDER_ERROR = URL_EVENT + '/bidError'; +const URL_EVENT_ON_TIMEOUT = URL_EVENT + '/timeout'; + +const R2B2_TEST_UNIT = 'selfpromo'; + +export const internal = { + placementsToSync: [], + mappedParams: {} +} + +let r2b2Error = function(message, params) { + logError(message, params, BIDDER_CODE) +} + +function getIdParamsFromPID(pid) { + // selfpromo test creative + if (pid === R2B2_TEST_UNIT) { + return { d: 'test', g: 'test', p: 'selfpromo', m: 0, selfpromo: 1 } + } + if (!isNaN(pid)) { + return { pid: Number(pid) } + } + if (typeof pid === 'string') { + const params = pid.split('/'); + if (params.length === 3 || params.length === 4) { + const paramNames = ['d', 'g', 'p', 'm']; + return paramNames.reduce((p, paramName, index) => { + let param = params[index]; + if (paramName === 'm') { + param = ['desktop', 'classic', '0'].includes(param) ? 0 : Number(!!param) + } + p[paramName] = param; + return p + }, {}); + } + } +} + +function pickIdFromParams(params) { + if (!params) return null; + const { d, g, p, m, pid } = params; + return d ? { d, g, p, m } : { pid }; +} + +function getIdsFromBids(bids) { + return bids.reduce((ids, bid) => { + const params = internal.mappedParams[bid.bidId]; + const id = pickIdFromParams(params); + if (id) { + ids.push(id); + } + return ids + }, []); +} + +function triggerEvent(eventUrl, ids) { + if (ids && !ids.length) return; + const timeStamp = new Date().getTime(); + const symbol = (eventUrl.indexOf('?') === -1 ? '?' : '&'); + const url = eventUrl + symbol + `p=${btoa(JSON.stringify(ids))}&cb=${timeStamp}`; + triggerPixel(url) +} + +const converter = ortbConverter({ + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const idParams = getIdParamsFromPID(bidRequest.params.pid); + deepSetValue(imp, 'ext.r2b2', idParams); + internal.placementsToSync.push(idParams); + internal.mappedParams[imp.id] = Object.assign({}, bidRequest.params, idParams); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'ext.version', ADAPTER_VERSION); + request.cur = [DEFAULT_CURRENCY]; + const test = getParameterByName(DEBUG_PARAM) === '1' ? 1 : 0; + deepSetValue(request, 'test', test); + return request; + }, + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL + }, + processors: pbsExtensions +}); + +function setUpRenderer(adUnitCode, bid) { + // let renderer load once in main window, but pass the renderDocument + let renderDoc; + const config = { + documentResolver: (bid, sourceDocument, renderDocument) => { + renderDoc = renderDocument; + return sourceDocument; + } + } + let renderer = Renderer.install({ + url: RENDERER_URL, + config: config, + id: bid.requestId, + adUnitCode + }); + + renderer.setRender(function (bid, doc) { + doc = renderDoc || doc; + window.R2B2 = window.R2B2 || {}; + let main = window.R2B2; + main.HB = main.HB || {}; + main.HB.Render = main.HB.Render || {}; + main.HB.Render.queue = main.HB.Render.queue || []; + main.HB.Render.queue.push(() => { + const id = pickIdFromParams(internal.mappedParams[bid.requestId]) + main.HB.Renderer.render(id, bid, null, doc) + }) + }) + + return renderer +} + +function getExtMediaType(bidMediaType, responseBid) { + switch (bidMediaType) { + case BANNER: + return { + type: 'banner', + settings: { + chd: null, + width: responseBid.w, + height: responseBid.h, + ad: { + type: 'content', + data: responseBid.adm + } + } + }; + case NATIVE: + break; + case VIDEO: + break; + default: + break; + } +} + +function createPrebidResponseBid(requestImp, bidResponse, serverResponse, bids) { + const bidId = requestImp.id; + const adUnitCode = bids[0].adUnitCode; + const mediaType = bidResponse.ext.prebid.type; + let bidOut = { + requestId: bidId, + cpm: bidResponse.price, + creativeId: bidResponse.crid, + width: bidResponse.w, + height: bidResponse.h, + ttl: bidResponse.ttl ?? DEFAULT_TTL, + netRevenue: serverResponse.netRevenue ?? DEFAULT_NET_REVENUE, + currency: serverResponse.cur ?? DEFAULT_CURRENCY, + ad: bidResponse.adm, + mediaType: mediaType, + winUrl: bidResponse.nurl, + ext: { + cid: bidResponse.ext?.r2b2?.cid, + cdid: bidResponse.ext?.r2b2?.cdid, + mediaType: getExtMediaType(mediaType, bidResponse), + adUnit: adUnitCode, + dgpm: internal.mappedParams[bidId], + events: bidResponse.ext?.r2b2?.events + } + }; + if (bidResponse.ext?.r2b2?.useRenderer) { + bidOut.renderer = setUpRenderer(adUnitCode, bidOut); + } + return bidOut; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVL_ID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function(bid) { + if (!bid.params || !bid.params.pid) { + logWarn('Bad params, "pid" required.'); + return false + } + const id = getIdParamsFromPID(bid.params.pid); + if (!id || !(id.pid || (id.d && id.g && id.p))) { + logWarn('Bad params, "pid" has to be either a number or a correctly assembled string.'); + return false + } + return true + }, + buildRequests: function(validBidRequests, bidderRequest) { + const data = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest + }); + return [{ + method: 'POST', + url: URL_BID, + data, + bids: bidderRequest.bids + }] + }, + + interpretResponse: function(serverResponse, request) { + // r2b2Error('error message', {params: 1}); + let prebidResponses = []; + + const response = serverResponse.body; + if (!response || !response.seatbid || !response.seatbid[0] || !response.seatbid[0].bid) { + return prebidResponses; + } + let requestImps = request.data.imp || []; + try { + response.seatbid.forEach(seat => { + let bids = seat.bid; + + for (let responseBid of bids) { + let responseImpId = responseBid.impid; + let requestCurrentImp = requestImps.find((requestImp) => requestImp.id === responseImpId); + if (!requestCurrentImp) { + r2b2Error('Cant match bid response.', {impid: Boolean(responseBid.impid)}); + continue;// Skip this iteration if there's no match + } + prebidResponses.push(createPrebidResponseBid(requestCurrentImp, responseBid, response, request.bids)); + } + }) + } catch (e) { + r2b2Error('Error while interpreting response:', {msg: e.message}); + } + return prebidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + + if (!syncOptions.iframeEnabled) { + logWarn('Please enable iframe based user sync.'); + return syncs; + } + + let plString; + try { + plString = btoa(JSON.stringify(internal.placementsToSync || [])); + } catch (e) { + logWarn('User sync failed: ' + e.message); + return syncs + } + + let url = URL_SYNC + `?p=${plString}`; + + if (gdprConsent) { + url += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}` + } + + if (uspConsent) { + url += `&us_privacy=${uspConsent}` + } + + syncs.push({ + type: 'iframe', + url: url + }) + return syncs; + }, + onBidWon: function(bid) { + const url = bid.ext?.events?.onBidWon; + if (url) { + triggerEvent(url) + } + }, + onSetTargeting: function(bid) { + const url = bid.ext?.events?.onSetTargeting; + if (url) { + triggerEvent(url) + } + }, + onTimeout: function(bids) { + triggerEvent(URL_EVENT_ON_TIMEOUT, getIdsFromBids(bids)) + }, + onBidderError: function(params) { + let { bidderRequest } = params; + triggerEvent(URL_EVENT_ON_BIDDER_ERROR, getIdsFromBids(bidderRequest.bids)) + } +} +registerBidder(spec); diff --git a/modules/r2b2BidAdapter.md b/modules/r2b2BidAdapter.md new file mode 100644 index 00000000000..43b59133215 --- /dev/null +++ b/modules/r2b2BidAdapter.md @@ -0,0 +1,37 @@ +# Overview + +``` +Module Name: R2B2 Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@r2b2.cz +``` + +## Description + +Module that integrates R2B2 demand sources. To get your bidder configuration reach out to our account team on partner@r2b2.io + + + +## Test unit + +```javascript + var adUnits = [ + { + code: 'test-r2b2', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + } + }, + bids: [{ + bidder: 'r2b2', + params: { + pid: 'selfpromo' + } + }] + } + ]; +``` +## Rendering + +Our adapter can feature a custom renderer specifically for display ads, tailored to enhance ad presentation and functionality. This is particularly beneficial for non-standard ad formats that require more complex logic. It's important to note that our rendering process operates outside of SafeFrames. For additional information, not limited to rendering aspects, please feel free to contact us at partner@r2b2.io diff --git a/test/spec/modules/r2b2BidAdapter_spec.js b/test/spec/modules/r2b2BidAdapter_spec.js new file mode 100644 index 00000000000..b94b400a71d --- /dev/null +++ b/test/spec/modules/r2b2BidAdapter_spec.js @@ -0,0 +1,689 @@ +import {expect} from 'chai'; +import {spec, internal as r2b2, internal} from 'modules/r2b2BidAdapter.js'; +import * as utils from '../../../src/utils'; +import 'modules/schain.js'; +import 'modules/userId/index.js'; + +function encodePlacementIds (ids) { + return btoa(JSON.stringify(ids)); +} + +describe('R2B2 adapter', function () { + let serverResponse, requestForInterpretResponse; + let bidderRequest; + let bids = []; + let gdprConsent = { + gdprApplies: true, + consentString: 'consent-string', + }; + let schain = { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + }; + const usPrivacyString = '1YNN'; + const impId = 'impID'; + const price = 10.6; + const ad = 'adm'; + const creativeId = 'creativeID'; + const cid = 41849; + const cdid = 595121; + const unitCode = 'unitCode'; + const bidId1 = '1'; + const bidId2 = '2'; + const bidId3 = '3'; + const bidId4 = '4'; + const bidId5 = '5'; + const bidWonUrl = 'url1'; + const setTargetingUrl = 'url2'; + const bidder = 'r2b2'; + const foreignBidder = 'differentBidder'; + const id1 = { pid: 'd/g/p' }; + const id1Object = { d: 'd', g: 'g', p: 'p', m: 0 }; + const id2 = { pid: 'd/g/p/1' }; + const id2Object = { d: 'd', g: 'g', p: 'p', m: 1 }; + const badId = { pid: 'd/g/' }; + const bid1 = { bidId: bidId1, bidder, params: [ id1 ] }; + const bid2 = { bidId: bidId2, bidder, params: [ id2 ] }; + const bidWithBadSetup = { bidId: bidId3, bidder, params: [ badId ] }; + const bidForeign1 = { bidId: bidId4, bidder: foreignBidder, params: [ { id: 'abc' } ] }; + const bidForeign2 = { bidId: bidId5, bidder: foreignBidder, params: [ { id: 'xyz' } ] }; + const fakeTime = 1234567890; + const cacheBusterRegex = /[\?&]cb=([^&]+)/; + let bidStub, time; + + beforeEach(function () { + bids = [{ + bidder: 'r2b2', + params: { + pid: 'example.com/generic/300x250/1' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + }, + adUnitCode: unitCode, + transactionId: '29c408b9-65ce-48b1-9167-18a57791f908', + sizes: [ + [300, 250] + ], + bidId: '20917a54ee9858', + bidderRequestId: '15270d403778d', + auctionId: '36acef1b-f635-4f57-b693-5cc55ee16346', + src: 'client', + ortb2: { + regs: { + ext: { + gdpr: 1, + us_privacy: '1YYY' + } + }, + user: { + ext: { + consent: 'consent-string' + } + }, + site: {}, + device: {} + }, + schain + }, { + bidder: 'r2b2', + params: { + pid: 'example.com/generic/300x600/0' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 600] + ] + } + }, + adUnitCode: unitCode, + transactionId: '29c408b9-65ce-48b1-9167-18a57791f908', + sizes: [ + [300, 600] + ], + bidId: '3dd53d30c691fe', + bidderRequestId: '15270d403778d', + auctionId: '36acef1b-f635-4f57-b693-5cc55ee16346', + src: 'client', + ortb2: { + regs: { + ext: { + gdpr: 1, + us_privacy: '1YYY' + } + }, + user: { + ext: { + consent: 'consent-string' + } + }, + site: {}, + device: {} + }, + schain + }]; + bidderRequest = { + bidderCode: 'r2b2', + auctionId: '36acef1b-f635-4f57-b693-5cc55ee16346', + bidderRequestId: '15270d403778d', + bids: bids, + ortb2: { + regs: { + ext: { + gdpr: 1, + us_privacy: '1YYY' + } + }, + user: { + ext: { + consent: 'consent-string' + } + }, + site: {}, + device: {} + }, + gdprConsent: { + consentString: 'consent-string', + vendorData: {}, + gdprApplies: true, + apiVersion: 2 + }, + uspConsent: '1YYY', + }; + serverResponse = { + id: 'a66a6e32-2a7d-4ed3-bb13-6f3c9bdcf6a1', + seatbid: [{ + bid: [{ + id: '4756cc9e9b504fd0bd39fdd594506545', + impid: impId, + price: price, + adm: ad, + crid: creativeId, + w: 300, + h: 250, + ext: { + prebid: { + meta: { + adaptercode: 'r2b2' + }, + type: 'banner' + }, + r2b2: { + cdid: cdid, + cid: cid, + useRenderer: true + } + } + }], + seat: 'seat' + }] + }; + requestForInterpretResponse = { + data: { + imp: [ + {id: impId} + ] + }, + bids + }; + }); + + describe('isBidRequestValid', function () { + let bid = {}; + + it('should return false when missing required "pid" param', function () { + bid.params = {random: 'param'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {d: 'd', g: 'g', p: 'p', m: 1}; + expect(spec.isBidRequestValid(bid)).to.equal(false) + }); + + it('should return false when "pid" is malformed', function () { + bid.params = {pid: 'pid'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: '///'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: '/g/p/m'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: 'd//p/m'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: 'd/g//m'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: 'd/p/'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {pid: 'd/g/p/m/t'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when "pid" is a correct dgpm', function () { + bid.params = {pid: 'd/g/p/m'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when type is blank', function () { + bid.params = {pid: 'd/g/p/'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when type is missing', function () { + bid.params = {pid: 'd/g/p'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when "pid" is a number', function () { + bid.params = {pid: 12356}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true when "pid" is a numeric string', function () { + bid.params = {pid: '12356'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return true for selfpromo unit', function () { + bid.params = {pid: 'selfpromo'}; + expect(spec.isBidRequestValid(bid)).to.equal(true) + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + r2b2.placementsToSync = []; + r2b2.mappedParams = {}; + }); + + it('should set correct request method and url and pass bids', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let request = requests[0] + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://hb.r2b2.cz/openrtb2/bid'); + expect(request.data).to.be.an('object'); + expect(request.bids).to.deep.equal(bids); + }); + + it('should pass correct parameters', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + let {data} = requests[0]; + let {imp, device, site, source, ext, cur, test} = data; + expect(imp).to.be.an('array').that.has.lengthOf(1); + expect(device).to.be.an('object'); + expect(site).to.be.an('object'); + expect(source).to.be.an('object'); + expect(cur).to.deep.equal(['USD']); + expect(ext.version).to.equal('1.0.0'); + expect(test).to.equal(0); + }); + + it('should pass correct imp', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(1); + expect(imp[0]).to.be.an('object'); + let bid = imp[0]; + expect(bid.id).to.equal('20917a54ee9858'); + expect(bid.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 250}]}); + expect(bid.ext).to.be.an('object'); + expect(bid.ext.r2b2).to.deep.equal({d: 'example.com', g: 'generic', p: '300x250', m: 1}); + }); + + it('should map type correctly', function () { + let result, bid; + let requestWithId = function(id) { + let b = bids[0]; + b.params.pid = id; + let passedBids = [b]; + bidderRequest.bids = passedBids; + return spec.buildRequests(passedBids, bidderRequest); + }; + + result = requestWithId('example.com/generic/300x250/mobile'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(1); + + result = requestWithId('example.com/generic/300x250/desktop'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(0); + + result = requestWithId('example.com/generic/300x250/1'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(1); + + result = requestWithId('example.com/generic/300x250/0'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(0); + + result = requestWithId('example.com/generic/300x250/m'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(1); + + result = requestWithId('example.com/generic/300x250'); + bid = result[0].data.imp[0]; + expect(bid.ext.r2b2.m).to.be.a('number').that.is.equal(0); + }); + + it('should pass correct parameters for test ad', function () { + let testAdBid = bids[0]; + testAdBid.params = {pid: 'selfpromo'}; + let requests = spec.buildRequests([testAdBid], bidderRequest); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(1); + expect(imp[0]).to.be.an('object'); + let bid = imp[0]; + expect(bid.ext).to.be.an('object'); + expect(bid.ext.r2b2).to.deep.equal({d: 'test', g: 'test', p: 'selfpromo', m: 0, 'selfpromo': 1}); + }); + + it('should pass multiple bids', function () { + let requests = spec.buildRequests(bids, bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(bids.length); + let bid1 = imp[0]; + expect(bid1.ext.r2b2).to.deep.equal({d: 'example.com', g: 'generic', p: '300x250', m: 1}); + let bid2 = imp[1]; + expect(bid2.ext.r2b2).to.deep.equal({d: 'example.com', g: 'generic', p: '300x600', m: 0}); + }); + + it('should set up internal variables', function () { + let requests = spec.buildRequests(bids, bidderRequest); + let bid1Id = bids[0].bidId; + let bid2Id = bids[1].bidId; + expect(r2b2.placementsToSync).to.be.an('array').that.has.lengthOf(2); + expect(r2b2.mappedParams).to.have.property(bid1Id); + expect(r2b2.mappedParams[bid1Id]).to.deep.equal({d: 'example.com', g: 'generic', p: '300x250', m: 1, pid: 'example.com/generic/300x250/1'}); + expect(r2b2.mappedParams).to.have.property(bid2Id); + expect(r2b2.mappedParams[bid2Id]).to.deep.equal({d: 'example.com', g: 'generic', p: '300x600', m: 0, pid: 'example.com/generic/300x600/0'}); + }); + + it('should pass gdpr properties', function () { + let requests = spec.buildRequests(bids, bidderRequest); + let {data} = requests[0]; + let {user, regs} = data; + expect(user).to.be.an('object').that.has.property('ext'); + expect(regs).to.be.an('object').that.has.property('ext'); + expect(user.ext.consent).to.equal('consent-string'); + expect(regs.ext.gdpr).to.equal(1); + }); + + it('should pass us privacy properties', function () { + let requests = spec.buildRequests(bids, bidderRequest); + let {data} = requests[0]; + let {regs} = data; + expect(regs).to.be.an('object').that.has.property('ext'); + expect(regs.ext.us_privacy).to.equal('1YYY'); + }); + + it('should pass supply chain', function () { + let requests = spec.buildRequests(bids, bidderRequest); + let {data} = requests[0]; + let {source} = data; + expect(source).to.be.an('object').that.has.property('ext'); + expect(source.ext.schain).to.deep.equal({ + complete: 1, + nodes: [ + {asi: 'example.com', hp: 1, sid: '00001'} + ], + ver: '1.0' + }) + }); + + it('should pass extended ids', function () { + let eidsArray = [ + { + source: 'adserver.org', + uids: [ + { + atype: 1, + ext: { + rtiPartner: 'TDID', + }, + id: 'TTD_ID_FROM_USER_ID_MODULE', + }, + ], + }, + { + source: 'pubcid.org', + uids: [ + { + atype: 1, + id: 'pubCommonId_FROM_USER_ID_MODULE', + }, + ], + }, + ]; + bids[0].userIdAsEids = eidsArray; + let requests = spec.buildRequests(bids, bidderRequest); + let request = requests[0]; + let eids = request.data.user.ext.eids; + + expect(eids).to.deep.equal(eidsArray); + }); + }); + + describe('interpretResponse', function () { + it('should respond with empty response when there are no bids', function () { + let result = spec.interpretResponse({ body: {} }, {}); + expect(result).to.be.an('array').that.has.lengthOf(0); + result = spec.interpretResponse({ body: { seatbid: [] } }, {}); + expect(result).to.be.an('array').that.has.lengthOf(0); + result = spec.interpretResponse({ body: { seatbid: [ {} ] } }, {}); + expect(result).to.be.an('array').that.has.lengthOf(0); + result = spec.interpretResponse({ body: { seatbid: [ { bids: [] } ] } }, {}); + expect(result).to.be.an('array').that.has.lengthOf(0); + }); + + it('should map params correctly', function () { + let result = spec.interpretResponse({ body: serverResponse }, requestForInterpretResponse); + expect(result).to.be.an('array').that.has.lengthOf(1); + let bid = result[0]; + expect(bid.requestId).to.equal(impId); + expect(bid.cpm).to.equal(price); + expect(bid.ad).to.equal(ad); + expect(bid.currency).to.equal('USD'); + expect(bid.mediaType).to.equal('banner'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + expect(bid.creativeId).to.equal(creativeId); + }); + + it('should set up renderer on bid', function () { + let result = spec.interpretResponse({ body: serverResponse }, requestForInterpretResponse); + expect(result).to.be.an('array').that.has.lengthOf(1); + let bid = result[0]; + expect(bid.renderer).to.be.an('object'); + expect(bid.renderer).to.have.property('render').that.is.a('function'); + expect(bid.renderer).to.have.property('url').that.is.a('string'); + }); + + it('should map ext params correctly', function() { + let dgpm = {something: 'something'}; + r2b2.mappedParams = {}; + r2b2.mappedParams[impId] = dgpm; + let result = spec.interpretResponse({ body: serverResponse }, requestForInterpretResponse); + expect(result).to.be.an('array').that.has.lengthOf(1); + let bid = result[0]; + expect(bid.ext).to.be.an('object'); + let { ext } = bid; + expect(ext.dgpm).to.deep.equal(dgpm); + expect(ext.cid).to.equal(cid); + expect(ext.cdid).to.equal(cdid); + expect(ext.adUnit).to.equal(unitCode); + expect(ext.mediaType).to.deep.equal({ + type: 'banner', + settings: { + chd: null, + width: 300, + height: 250, + ad: { + type: 'content', + data: ad + } + } + }); + }); + + it('should handle multiple bids', function() { + const impId2 = '123456'; + const price2 = 12; + const ad2 = 'gaeouho'; + const w2 = 300; + const h2 = 600; + let b = serverResponse.seatbid[0].bid[0]; + let b2 = Object.assign({}, b); + b2.impid = impId2; + b2.price = price2; + b2.adm = ad2; + b2.w = w2; + b2.h = h2; + serverResponse.seatbid[0].bid.push(b2); + requestForInterpretResponse.data.imp.push({id: impId2}); + let result = spec.interpretResponse({ body: serverResponse }, requestForInterpretResponse); + expect(result).to.be.an('array').that.has.lengthOf(2); + let firstBid = result[0]; + let secondBid = result[1]; + expect(firstBid.requestId).to.equal(impId); + expect(firstBid.ad).to.equal(ad); + expect(firstBid.cpm).to.equal(price); + expect(firstBid.width).to.equal(300); + expect(firstBid.height).to.equal(250); + expect(secondBid.requestId).to.equal(impId2); + expect(secondBid.ad).to.equal(ad2); + expect(secondBid.cpm).to.equal(price2); + expect(secondBid.width).to.equal(w2); + expect(secondBid.height).to.equal(h2); + }); + }); + + describe('getUserSyncs', function() { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + + it('should return an array with a sync for all bids', function() { + r2b2.placementsToSync = [id1Object, id2Object]; + const expectedEncodedIds = encodePlacementIds(r2b2.placementsToSync); + const syncs = spec.getUserSyncs(syncOptions); + expect(syncs).to.be.an('array').that.has.lengthOf(1); + const sync = syncs[0]; + expect(sync).to.be.an('object'); + expect(sync.type).to.equal('iframe'); + expect(sync.url).to.include(`?p=${expectedEncodedIds}`); + }); + + it('should return the sync and include gdpr and usp parameters in the url', function() { + r2b2.placementsToSync = [id1Object, id2Object]; + const syncs = spec.getUserSyncs(syncOptions, {}, gdprConsent, usPrivacyString); + const sync = syncs[0]; + expect(sync).to.be.an('object'); + expect(sync.url).to.include(`&gdpr=1`); + expect(sync.url).to.include(`&gdpr_consent=${gdprConsent.consentString}`); + expect(sync.url).to.include(`&us_privacy=${usPrivacyString}`); + }); + }); + + describe('events', function() { + beforeEach(function() { + time = sinon.useFakeTimers(fakeTime); + sinon.stub(utils, 'triggerPixel'); + r2b2.mappedParams = {}; + r2b2.mappedParams[bidId1] = id1Object; + r2b2.mappedParams[bidId2] = id2Object; + bidStub = { + adserverTargeting: { hb_bidder: bidder, hb_pb: '10.00', hb_size: '300x300' }, + cpm: 10, + currency: 'USD', + ext: { + dgpm: { d: 'r2b2.cz', g: 'generic', m: 1, p: '300x300', pid: 'r2b2.cz/generic/300x300/1' } + }, + params: [ { pid: 'r2b2.cz/generic/300x300/1' } ], + }; + }); + afterEach(function() { + utils.triggerPixel.restore(); + time.restore(); + }); + + describe('onBidWon', function () { + it('exists and is a function', () => { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + it('should return nothing and trigger a pixel with passed url', function () { + bidStub.ext.events = { + onBidWon: bidWonUrl, + onSetTargeting: setTargetingUrl + }; + const response = spec.onBidWon(bidStub); + expect(response).to.be.an('undefined'); + expect(utils.triggerPixel.called).to.equal(true); + expect(utils.triggerPixel.callCount).to.equal(1); + expect(utils.triggerPixel.calledWithMatch(bidWonUrl)).to.equal(true); + }); + it('should not trigger a pixel if url is not available', function () { + bidStub.ext.events = null; + spec.onBidWon(bidStub); + expect(utils.triggerPixel.callCount).to.equal(0); + bidStub.ext.events = { + onBidWon: '', + onSetTargeting: '', + }; + spec.onBidWon(bidStub); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + }); + + describe('onSetTargeting', function () { + it('exists and is a function', () => { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); + }); + it('should return nothing and trigger a pixel with passed url', function () { + bidStub.ext.events = { + onBidWon: bidWonUrl, + onSetTargeting: setTargetingUrl + }; + const response = spec.onSetTargeting(bidStub); + expect(response).to.be.an('undefined'); + expect(utils.triggerPixel.called).to.equal(true); + expect(utils.triggerPixel.callCount).to.equal(1); + expect(utils.triggerPixel.calledWithMatch(setTargetingUrl)).to.equal(true); + }); + it('should not trigger a pixel if url is not available', function () { + bidStub.ext.events = null; + spec.onSetTargeting(bidStub); + expect(utils.triggerPixel.callCount).to.equal(0); + bidStub.ext.events = { + onBidWon: '', + onSetTargeting: '', + }; + spec.onSetTargeting(bidStub); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + }); + + describe('onTimeout', function () { + it('exists and is a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should return nothing and trigger a pixel', function () { + const bids = [bid1, bid2]; + const response = spec.onTimeout(bids); + expect(response).to.be.an('undefined'); + expect(utils.triggerPixel.callCount).to.equal(1); + }); + it('should not trigger a pixel if no bids available', function () { + const bids = []; + spec.onTimeout(bids); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + it('should trigger a pixel with correct ids and a cache buster', function () { + const bids = [bid1, bidForeign1, bidForeign2, bid2, bidWithBadSetup]; + const expectedIds = [id1Object, id2Object]; + const expectedEncodedIds = encodePlacementIds(expectedIds); + spec.onTimeout(bids); + expect(utils.triggerPixel.callCount).to.equal(1); + const triggeredUrl = utils.triggerPixel.args[0][0]; + expect(triggeredUrl).to.include(`p=${expectedEncodedIds}`); + expect(triggeredUrl.match(cacheBusterRegex)).to.exist; + }); + }); + + describe('onBidderError', function () { + it('exists and is a function', () => { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + it('should return nothing and trigger a pixel', function () { + const bidderRequest = { bids: [bid1, bid2] }; + const response = spec.onBidderError({ bidderRequest }); + expect(response).to.be.an('undefined') + expect(utils.triggerPixel.callCount).to.equal(1); + }); + it('should not trigger a pixel if no bids available', function () { + const bidderRequest = { bids: [] }; + spec.onBidderError({ bidderRequest }); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + it('should call triggerEvent with correct ids and a cache buster', function () { + const bids = [bid1, bid2, bidWithBadSetup] + const bidderRequest = { bids }; + const expectedIds = [id1Object, id2Object]; + const expectedEncodedIds = encodePlacementIds(expectedIds); + spec.onBidderError({ bidderRequest }); + expect(utils.triggerPixel.callCount).to.equal(1); + const triggeredUrl = utils.triggerPixel.args[0][0]; + expect(triggeredUrl).to.include(`p=${expectedEncodedIds}`); + expect(triggeredUrl.match(cacheBusterRegex)).to.exist; + }); + }); + }); +}); From f5e19885057300c645764eb421f0013b9b172810 Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:20:51 +0300 Subject: [PATCH 291/760] Core: fix headers in ortbConverter readme (#10874) --- libraries/ortbConverter/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/ortbConverter/README.md b/libraries/ortbConverter/README.md index 31f56b4c754..751971eebdc 100644 --- a/libraries/ortbConverter/README.md +++ b/libraries/ortbConverter/README.md @@ -80,8 +80,7 @@ However, there are two restrictions (to avoid them, use the [other customization ) ``` - -### Fine grained customization - imp, request, bidResponse, response +### Fine grained customization - imp, request, bidResponse, response When invoked, `toORTB({bidRequests, bidderRequest})` first loops through each request in `bidRequests`, converting them into ORTB `imp` objects. It then packages them into a single ORTB request, adding other parameters that are not imp-specific (such as for example `request.tmax`). @@ -91,7 +90,7 @@ a single return value. You can customize each of these steps using the `ortbConverter` arguments `imp`, `request`, `bidResponse` and `response`: -### Customizing imps: `imp(buildImp, bidRequest, context)` +### Customizing imps: `imp(buildImp, bidRequest, context)` Invoked once for each input `bidRequest`; should return the ORTB `imp` object to include in the request. The arguments are: @@ -101,7 +100,7 @@ The arguments are: - `context`: a [context object](#context) that contains at least: - `bidderRequest`: the `bidderRequest` argument passed to `toORTB`. -#### Example: attaching custom bid params +#### Example: attaching custom bid params ```javascript const converter = ortbConverter({ @@ -351,7 +350,7 @@ const converter = ortbConverter({ - the `context` argument of `ortbConverter`: e.g. `ortbConverter({context: {ttl: 30}})`. This will set `context.ttl = 30` globally for the converter. - the `context` argument of `toORTB`: e.g. `converter.toORTB({bidRequests, bidderRequest, context: {ttl: 30}})`. This will set `context.ttl = 30` only for this request. -### Special `context` properties +### Special `context` properties For ease of use, the conversion logic gives special meaning to some context properties: From c6b45c41367f58e5d26f176c509215541e44dcf1 Mon Sep 17 00:00:00 2001 From: geoffray-viously <95097046+geoffray-viously@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:22:05 +0100 Subject: [PATCH 292/760] SparteoBidAdapter: Add UserSync (#10822) --- modules/sparteoBidAdapter.js | 42 +++++++++++++++++---- test/spec/modules/sparteoBidAdapter_spec.js | 26 +++++++++++++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index 93353c5dda7..bfb527d46f2 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -8,6 +8,8 @@ const GVLID = 1028; const TTL = 60; const HTTP_METHOD = 'POST'; const REQUEST_URL = 'https://bid.sparteo.com/auction'; +const USER_SYNC_URL_IFRAME = 'https://sync.sparteo.com/sync/iframe.html?from=prebidjs'; +let isSynced = window.sparteoCrossfire?.started || false; const converter = ortbConverter({ context: { @@ -60,7 +62,7 @@ export const spec = { * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: function(bid) { + isBidRequestValid: function (bid) { let bannerParams = deepAccess(bid, 'mediaTypes.banner'); let videoParams = deepAccess(bid, 'mediaTypes.video'); @@ -106,7 +108,7 @@ export const spec = { return true; }, - buildRequests: function(bidRequests, bidderRequest) { + buildRequests: function (bidRequests, bidderRequest) { const payload = converter.toORTB({bidRequests, bidderRequest}) return { @@ -116,23 +118,49 @@ export const spec = { }; }, - interpretResponse: function(serverResponse, requests) { + interpretResponse: function (serverResponse, requests) { const bids = converter.fromORTB({response: serverResponse.body, request: requests.data}).bids; return bids; }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {}, + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + let syncurl = ''; - onTimeout: function(timeoutData) {}, + if (!isSynced && !window.sparteoCrossfire?.started) { + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + syncurl += '&gdpr=' + (gdprConsent.gdprApplies ? 1 : 0); + syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); + } + if (uspConsent && uspConsent.consentString) { + syncurl += `&usp_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + isSynced = true; + + window.sparteoCrossfire = { + started: true + }; + + return [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + syncurl + }]; + } + } + }, + + onTimeout: function (timeoutData) {}, - onBidWon: function(bid) { + onBidWon: function (bid) { if (bid && bid.nurl) { triggerPixel(bid.nurl, null); } }, - onSetTargeting: function(bid) {} + onSetTargeting: function (bid) {} }; registerBidder(spec); diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index 71191b44a29..293f7da30a1 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -6,6 +6,7 @@ const CURRENCY = 'EUR'; const TTL = 60; const HTTP_METHOD = 'POST'; const REQUEST_URL = 'https://bid.sparteo.com/auction'; +const USER_SYNC_URL_IFRAME = 'https://sync.sparteo.com/sync/iframe.html?from=prebidjs'; const VALID_BID_BANNER = { bidder: 'sparteo', @@ -438,4 +439,29 @@ describe('SparteoAdapter', function () { }); }); }); + + describe('getUserSyncs', function() { + describe('Check methods succeed', function () { + it('should return the sync url', function() { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + }; + const gdprConsent = { + gdprApplies: 1, + consentString: 'tcfv2' + }; + const uspConsent = { + consentString: '1Y---' + }; + + const syncUrls = [{ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + '&gdpr=1&gdpr_consent=tcfv2&usp_consent=1Y---' + }]; + + expect(adapter.getUserSyncs(syncOptions, null, gdprConsent, uspConsent)).to.deep.equal(syncUrls); + }); + }); + }); }); From f625c1e916986275297f53c92c234468834e69b3 Mon Sep 17 00:00:00 2001 From: Konstantin Korobkov Date: Wed, 27 Dec 2023 18:22:50 +0100 Subject: [PATCH 293/760] Yandex Bid Adapter: Add rtt (roud trip time) tracking via nurl (#10846) * Add rtt (roud trip time) tracking via nurl in yandexBidAdapter * Yandex Bid Adapter: Fix let -> const --------- Co-authored-by: Konstantin Korobkov --- modules/yandexBidAdapter.js | 22 ++++++++- test/spec/modules/yandexBidAdapter_spec.js | 55 ++++++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index 9ca989b2259..4a7f9114a17 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -147,7 +147,7 @@ export const spec = { interpretResponse: interpretResponse, onBidWon: function (bid) { - const nurl = bid['nurl']; + const nurl = addRTT(bid['nurl'], bid.timeToRespond); if (!nurl) { return; @@ -385,4 +385,24 @@ function replaceAuctionPrice(url, price, currency) { .replace(/\${AUCTION_CURRENCY}/, currency); } +function addRTT(url, rtt) { + if (!url) return; + + if (url.indexOf(`\${RTT}`) > -1) { + return url.replace(/\${RTT}/, rtt ?? -1); + } + + const urlObj = new URL(url); + + if (Number.isInteger(rtt)) { + urlObj.searchParams.set('rtt', rtt); + } else { + urlObj.searchParams.delete('rtt'); + } + + url = urlObj.toString(); + + return url; +} + registerBidder(spec); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index f14e8df6c09..d5cfc26b31a 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,6 +1,6 @@ import { assert, expect } from 'chai'; import { spec, NATIVE_ASSETS } from 'modules/yandexBidAdapter.js'; -import { parseUrl } from 'src/utils.js'; +import * as utils from 'src/utils.js'; import { BANNER, NATIVE } from '../../../src/mediaTypes'; import { config } from '../../../src/config'; @@ -71,7 +71,7 @@ describe('Yandex adapter', function () { expect(method).to.equal('POST'); - const parsedRequestUrl = parseUrl(url); + const parsedRequestUrl = utils.parseUrl(url); const { search: query } = parsedRequestUrl expect(parsedRequestUrl.hostname).to.equal('bs.yandex.ru'); @@ -100,7 +100,7 @@ describe('Yandex adapter', function () { const bannerRequest = getBidRequest(); const requests = spec.buildRequests([bannerRequest], bidderRequest); const { url } = requests[0]; - const parsedRequestUrl = parseUrl(url); + const parsedRequestUrl = utils.parseUrl(url); const { search: query } = parsedRequestUrl expect(query['ssp-cur']).to.equal('USD'); @@ -478,6 +478,55 @@ describe('Yandex adapter', function () { }); }); }); + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function() { + const result = spec.onBidWon({}); + expect(utils.triggerPixel.callCount).to.equal(0) + }) + + it('Should trigger pixel if bid has nurl', function() { + const result = spec.onBidWon({ + nurl: 'https://example.com/some-tracker', + timeToRespond: 378, + }); + expect(utils.triggerPixel.callCount).to.equal(1) + expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker?rtt=378') + }) + + it('Should trigger pixel if bid has nurl with path & params', function() { + const result = spec.onBidWon({ + nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2', + timeToRespond: 378, + }); + expect(utils.triggerPixel.callCount).to.equal(1) + expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&rtt=378') + }) + + it('Should trigger pixel if bid has nurl with path & params and rtt macros', function() { + const result = spec.onBidWon({ + nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=${RTT}', + timeToRespond: 378, + }); + expect(utils.triggerPixel.callCount).to.equal(1) + expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=378') + }) + + it('Should trigger pixel if bid has nurl and there is no timeToRespond param, but has rtt macros in nurl', function() { + const result = spec.onBidWon({ + nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=${RTT}', + }); + expect(utils.triggerPixel.callCount).to.equal(1) + expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=-1') + }) + }) }); function getBidConfig() { From ed48e349c50c7bfdeed61129ea426b87ab714aab Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Wed, 27 Dec 2023 20:23:45 +0300 Subject: [PATCH 294/760] AdMatic Bid Adapter : consent management features added (#10813) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management --- modules/admaticBidAdapter.js | 131 ++- test/spec/modules/admaticBidAdapter_spec.js | 1057 ++++++++++++------- 2 files changed, 818 insertions(+), 370 deletions(-) diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index fc5cf9c8f7b..236114f6396 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -1,4 +1,4 @@ -import {getValue, logError, isEmpty, deepAccess, isArray, getBidIdParameter} from '../src/utils.js'; +import {getValue, formatQS, logError, deepAccess, isArray, getBidIdParameter} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; @@ -56,18 +56,16 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (validBidRequests, bidderRequest) => { + const tmax = bidderRequest.timeout; const bids = validBidRequests.map(buildRequestObject); - const blacklist = bidderRequest.ortb2; + const ortb = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); const host = getValue(validBidRequests[0].params, 'host'); const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; const bidderName = validBidRequests[0].bidder; const payload = { - user: { - ua: navigator.userAgent - }, - blacklist: [], + ortb, site: { page: bidderRequest.refererInfo.page, ref: bidderRequest.refererInfo.page, @@ -80,17 +78,59 @@ export const spec = { ext: { cur: currency, bidder: bidderName - } + }, + schain: {}, + regs: { + ext: { + } + }, + user: { + ext: {} + }, + at: 1, + tmax: parseInt(tmax) }; - if (!isEmpty(blacklist.badv)) { - payload.blacklist = blacklist.badv; - }; + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + const consentStr = (bidderRequest.gdprConsent.consentString) + ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ''; + const gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + payload.regs.ext.gdpr = gdpr; + payload.regs.ext.consent = consentStr; + } + + if (bidderRequest && bidderRequest.coppa) { + payload.regs.ext.coppa = bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined); + } + + if (bidderRequest && bidderRequest.ortb2?.regs?.gpp) { + payload.regs.ext.gpp = bidderRequest.ortb2?.regs?.gpp; + } + + if (bidderRequest && bidderRequest.ortb2?.regs?.gpp_sid) { + payload.regs.ext.gpp_sid = bidderRequest.ortb2?.regs?.gpp_sid; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.regs.ext.uspIab = bidderRequest.uspConsent; + } + + if (validBidRequests[0].schain) { + const schain = mapSchain(validBidRequests[0].schain); + if (schain) { + payload.schain = schain; + } + } + + if (validBidRequests[0].userIdAsEids) { + const eids = { eids: validBidRequests[0].userIdAsEids }; + payload.user.ext = { ...payload.user.ext, ...eids }; + } if (payload) { switch (bidderName) { case 'pixad': - SYNC_URL = 'https://static.pixad.com.tr/sync.html'; + SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; break; default: SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; @@ -101,12 +141,36 @@ export const spec = { } }, - getUserSyncs: function (syncOptions, responses) { - if (syncOptions.iframeEnabled) { - return [{ + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (!hasSynced && syncOptions.iframeEnabled) { + // data is only assigned if params are available to pass to syncEndpoint + let params = {}; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; + } + } + + if (uspConsent) { + params['us_privacy'] = encodeURIComponent(uspConsent); + } + + if (gppConsent?.gppString) { + params['gpp'] = gppConsent.gppString; + params['gpp_sid'] = gppConsent.applicableSections?.toString(); + } + + params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + + hasSynced = true; + return { type: 'iframe', - url: SYNC_URL - }]; + url: SYNC_URL + params + }; } }, @@ -156,6 +220,41 @@ export const spec = { } }; +var hasSynced = false; + +export function resetUserSync() { + hasSynced = false; +} + +/** + * @param {object} schain object set by Publisher + * @returns {object} OpenRTB SupplyChain object + */ +function mapSchain(schain) { + if (!schain) { + return null; + } + if (!validateSchain(schain)) { + logError('AdMatic: required schain params missing'); + return null; + } + return schain; +} + +/** + * @param {object} schain object set by Publisher + * @returns {object} bool + */ +function validateSchain(schain) { + if (!schain.nodes) { + return false; + } + const requiredFields = ['asi', 'sid', 'hp']; + return schain.nodes.every(node => { + return requiredFields.every(field => node[field]); + }); +} + function isUrl(str) { try { URL(str); diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index bd409958b1a..b4d84634962 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -1,12 +1,553 @@ -import {expect} from 'chai'; -import {spec, storage} from 'modules/admaticBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {getStorageManager} from 'src/storageManager'; +import { expect } from 'chai'; +import { spec } from 'modules/admaticBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; const ENDPOINT = 'https://layer.serve.admatic.com.tr/pb'; describe('admaticBidAdapter', () => { const adapter = newBidder(spec); + let validRequest = [ { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'ortb2Imp': { 'ext': { 'instl': 1 } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + }, + 'native': { + }, + 'video': { + } + }, + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else if (inputParams.mediaType === VIDEO) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === NATIVE) { + return { + currency: 'USD', + floor: 1.0 + }; + } else { + return {} + } + }, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'pixad.com.tr', + 'sid': 'px-pub-3000856707', + 'hp': 1 + } + ] + }, + 'at': 1, + 'tmax': 1000, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': '0', + 'atype': 1, + 'ext': { + 'linkType': 0, + 'pba': 'wMh3sAXcnhDq7CfSa6ji1g==' + } + } + ] + }, + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '5a49273f-a424-454b-b478-169c3551aa72', + 'atype': 1 + } + ] + } + ] + } + }, + 'ortb': { + 'badv': [], + 'bcat': [], + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost' + } + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' + } + }, + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'mediatype': {}, + 'type': 'banner', + 'id': '2205da7a81846b', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'floors': { + 'video': { + '338x280': { 'currency': 'USD', 'floor': 1 } + } + }, + 'id': '45e86fc7ce7fc93' + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'title': { + 'required': true, + 'len': 120 + }, + 'image': { + 'required': true + }, + 'icon': { + 'required': false, + 'sizes': [ + 640, + 480 + ] + }, + 'sponsoredBy': { + 'required': false + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': false + }, + 'displayUrl': { + 'required': false + } + }, + 'ext': { + 'instl': 0, + 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', + 'data': { + 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' + } + }, + 'floors': { + 'native': { + '*': { 'currency': 'USD', 'floor': 1 } + } + }, + 'id': '16e0c8982318f91' + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + } ]; + let bidderRequest = { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', + }, + 'bidder': 'admatic', + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + 'ortb2Imp': { 'ext': { 'instl': 1 } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [728, 90]] + }, + 'native': { + }, + 'video': { + 'playerSize': [ + 336, + 280 + ] + } + }, + 'userId': { + 'id5id': { + 'uid': '0', + 'ext': { + 'linkType': 0, + 'pba': 'wMh3sAXcnhDq7CfSa6ji1g==' + } + }, + 'pubcid': '5a49273f-a424-454b-b478-169c3551aa72' + }, + 'userIdAsEids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': '0', + 'atype': 1, + 'ext': { + 'linkType': 0, + 'pba': 'wMh3sAXcnhDq7CfSa6ji1g==' + } + } + ] + }, + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '5a49273f-a424-454b-b478-169c3551aa72', + 'atype': 1 + } + ] + } + ], + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else if (inputParams.mediaType === VIDEO) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === NATIVE) { + return { + currency: 'USD', + floor: 1.0 + }; + } else { + return {} + } + }, + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'pixad.com.tr', + 'sid': 'px-pub-3000856707', + 'hp': 1 + } + ] + }, + 'at': 1, + 'tmax': 1000, + 'user': { + 'ext': { + 'eids': [ + { + 'source': 'id5-sync.com', + 'uids': [ + { + 'id': '0', + 'atype': 1, + 'ext': { + 'linkType': 0, + 'pba': 'wMh3sAXcnhDq7CfSa6ji1g==' + } + } + ] + }, + { + 'source': 'pubcid.org', + 'uids': [ + { + 'id': '5a49273f-a424-454b-b478-169c3551aa72', + 'atype': 1 + } + ] + } + ] + } + }, + 'ortb': { + 'source': {}, + 'site': { + 'domain': 'localhost:8888', + 'publisher': { + 'domain': 'localhost:8888' + }, + 'page': 'http://localhost:8888/', + 'name': 'http://localhost:8888' + }, + 'badv': [], + 'bcat': [], + 'device': { + 'w': 896, + 'h': 979, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '119' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '119' + ] + }, + { + 'brand': 'Not?A_Brand', + 'version': [ + '24' + ] + } + ], + 'mobile': 0 + } + } + }, + 'site': { + 'page': 'http://localhost:8888/admatic.html', + 'ref': 'http://localhost:8888', + 'publisher': { + 'name': 'localhost', + 'publisherId': 12321312 + } + }, + 'imp': [ + { + 'size': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 728, + 'h': 90 + } + ], + 'id': '2205da7a81846b', + 'mediatype': {}, + 'type': 'banner', + 'floors': { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + } + }, + { + 'size': [ + { + 'w': 338, + 'h': 280 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 338, + 280 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 1, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2 + }, + 'floors': { + 'video': { + '338x280': { 'currency': 'USD', 'floor': 1 } + } + }, + 'id': '45e86fc7ce7fc93' + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'title': { + 'required': true, + 'len': 120 + }, + 'image': { + 'required': true + }, + 'icon': { + 'required': false, + 'sizes': [ + 640, + 480 + ] + }, + 'sponsoredBy': { + 'required': false + }, + 'body': { + 'required': false + }, + 'clickUrl': { + 'required': false + }, + 'displayUrl': { + 'required': false + } + }, + 'ext': { + 'instl': 0, + 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', + 'data': { + 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' + } + }, + 'floors': { + 'native': { + '*': { 'currency': 'USD', 'floor': 1 } + } + }, + 'id': '16e0c8982318f91' + } + ], + 'ext': { + 'cur': 'USD', + 'bidder': 'admatic' + } + }; describe('inherited functions', () => { it('exists and is a function', () => { @@ -51,353 +592,147 @@ describe('admaticBidAdapter', () => { describe('buildRequests', function () { it('sends bid request to ENDPOINT via POST', function () { - let validRequest = [ { - 'refererInfo': { - 'page': 'https://www.admatic.com.tr', - 'domain': 'https://www.admatic.com.tr', - }, - 'bidder': 'admatic', - 'params': { - 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' - }, - 'ortb2Imp': { 'ext': { 'instl': 1 } }, - 'ortb2': { 'badv': ['admatic.com.tr'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [728, 90]] - } - }, - getFloor: inputParams => { - if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { - return { - currency: 'USD', - floor: 1.0 - }; - } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { - return { - currency: 'USD', - floor: 2.0 - }; - } else { - return {} - } - }, - 'user': { - 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' - }, - 'blacklist': [], - 'site': { - 'page': 'http://localhost:8888/admatic.html', - 'ref': 'http://localhost:8888', - 'publisher': { - 'name': 'localhost', - 'publisherId': 12321312 + const request = spec.buildRequests(validRequest, bidderRequest); + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should not populate GDPR if for non-EEA users', function () { + let bidRequest = Object.assign([], validRequest); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - }, - 'imp': [ - { - 'size': [ - { - 'w': 300, - 'h': 250 - }, - { - 'w': 728, - 'h': 90 - } - ], - 'mediatype': {}, - 'type': 'banner', - 'id': '2205da7a81846b', - 'floors': { - 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } - } - } - }, - { - 'size': [ - { - 'w': 338, - 'h': 280 - } - ], - 'type': 'video', - 'mediatype': { - 'context': 'instream', - 'mimes': [ - 'video/mp4' - ], - 'maxduration': 240, - 'api': [ - 1, - 2 - ], - 'playerSize': [ - [ - 338, - 280 - ] - ], - 'protocols': [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8 - ], - 'skip': 1, - 'playbackmethod': [ - 2 - ], - 'linearity': 1, - 'placement': 2 - }, - 'id': '45e86fc7ce7fc93' - }, - { - 'size': [ - { - 'w': 1, - 'h': 1 - } - ], - 'type': 'native', - 'mediatype': { - 'title': { - 'required': true, - 'len': 120 - }, - 'image': { - 'required': true - }, - 'icon': { - 'required': false, - 'sizes': [ - 640, - 480 - ] - }, - 'sponsoredBy': { - 'required': false - }, - 'body': { - 'required': false - }, - 'clickUrl': { - 'required': false - }, - 'displayUrl': { - 'required': false - } - }, - 'ext': { - 'instl': 0, - 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', - 'data': { - 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' - } - }, - 'id': '16e0c8982318f91' + }) + ); + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.regs.ext.consent).to.equal('BOJ8RZsOJ8RZsABAB8AAAAAZ-A'); + }); + + it('should populate GDPR and empty consent string if available for EEA users without consent string but with consent', function () { + let bidRequest = Object.assign([], validRequest); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + gdprConsent: { + gdprApplies: true } - ], - 'ext': { - 'cur': 'USD', - 'bidder': 'admatic' + }) + ); + expect(request.data.regs.ext.gdpr).to.equal(1); + expect(request.data.regs.ext.consent).to.equal(''); + }); + + it('should properly build a request when coppa flag is true', function () { + let bidRequest = Object.assign([], validRequest); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + coppa: true + }) + ); + expect(request.data.regs.ext.coppa).to.not.be.undefined; + expect(request.data.regs.ext.coppa).to.equal(1); + }); + + it('should properly build a request with gpp consent field', function () { + let bidRequest = Object.assign([], validRequest); + const ortb2 = { + regs: { + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2] } - } ]; - let bidderRequest = { - 'refererInfo': { - 'page': 'https://www.admatic.com.tr', - 'domain': 'https://www.admatic.com.tr', - }, - 'bidder': 'admatic', - 'params': { - 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' - }, - 'ortb2Imp': { 'ext': { 'instl': 1 } }, - 'ortb2': { 'badv': ['admatic.com.tr'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [728, 90]] - } - }, - getFloor: inputParams => { - if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { - return { - currency: 'USD', - floor: 1.0 - }; - } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { - return { - currency: 'USD', - floor: 2.0 - }; - } else { - return {} - } - }, - 'user': { - 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' - }, - 'blacklist': [], - 'site': { - 'page': 'http://localhost:8888/admatic.html', - 'ref': 'http://localhost:8888', - 'publisher': { - 'name': 'localhost', - 'publisherId': 12321312 - } - }, - 'imp': [ - { - 'size': [ - { - 'w': 300, - 'h': 250 - }, - { - 'w': 728, - 'h': 90 - } - ], - 'id': '2205da7a81846b', - 'mediatype': {}, - 'type': 'banner', - 'floors': { - 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } - } + }; + const request = spec.buildRequests(bidRequest, { ...bidderRequest, ortb2 }); + expect(request.data.regs.ext.gpp).to.equal('gpp_consent_string'); + expect(request.data.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); + }); + + it('should properly build a request with ccpa consent field', function () { + let bidRequest = Object.assign([], validRequest); + const request = spec.buildRequests( + bidRequest, + Object.assign({}, bidderRequest, { + uspConsent: '1---' + }) + ); + expect(request.data.regs.ext.uspIab).to.not.be.null; + expect(request.data.regs.ext.uspIab).to.equal('1---'); + }); + + it('should properly forward eids', function () { + const bidRequests = [ + { + bidder: 'admatic', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] } }, - { - 'size': [ - { - 'w': 338, - 'h': 280 - } - ], - 'type': 'video', - 'mediatype': { - 'context': 'instream', - 'mimes': [ - 'video/mp4' - ], - 'maxduration': 240, - 'api': [ - 1, - 2 - ], - 'playerSize': [ - [ - 338, - 280 - ] - ], - 'protocols': [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8 - ], - 'skip': 1, - 'playbackmethod': [ - 2 - ], - 'linearity': 1, - 'placement': 2 - }, - 'id': '45e86fc7ce7fc93' - }, - { - 'size': [ - { - 'w': 1, - 'h': 1 - } - ], - 'type': 'native', - 'mediatype': { - 'title': { - 'required': true, - 'len': 120 - }, - 'image': { - 'required': true - }, - 'icon': { - 'required': false, - 'sizes': [ - 640, - 480 - ] - }, - 'sponsoredBy': { - 'required': false - }, - 'body': { - 'required': false - }, - 'clickUrl': { - 'required': false - }, - 'displayUrl': { - 'required': false - } - }, - 'ext': { - 'instl': 0, - 'gpid': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a', - 'data': { - 'pbadslot': 'native-INS_b1b1269f-9570-fe3c-9bf4-f187827ec94a' - } - }, - 'id': '16e0c8982318f91' - } - ], - 'ext': { - 'cur': 'USD', - 'bidder': 'admatic' + userIdAsEids: [ + { + source: 'admatic.com.tr', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ], + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'admatic.com.tr', + uids: [{ + id: 'abc', + atype: 1 + }] } - }; - const request = spec.buildRequests(validRequest, bidderRequest); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); + ]); }); it('should properly build a banner request with floors', function () { - let bidRequests = [ + const request = spec.buildRequests(validRequest, bidderRequest); + request.data.imp[0].floors = { + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + } + }; + }); + + it('should properly build a video request with several player sizes with floors', function () { + const bidRequests = [ { 'bidder': 'admatic', - 'params': { - 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' - }, + 'adUnitCode': 'bid-123', + 'transactionId': 'transaction-123', 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [728, 90]] + 'video': { + 'playerSize': [[300, 250], [728, 90]] } }, 'ortb2Imp': { 'ext': { 'instl': 1 } }, 'ortb2': { 'badv': ['admatic.com.tr'] }, + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, getFloor: inputParams => { - if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + if (inputParams.mediaType === VIDEO && inputParams.size[0] === 300 && inputParams.size[1] === 250) { return { currency: 'USD', floor: 1.0 }; - } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 728 && inputParams.size[1] === 90) { return { currency: 'USD', floor: 2.0 @@ -408,37 +743,50 @@ describe('admaticBidAdapter', () => { } }, ]; - let bidderRequest = { + const bidderRequest = { 'refererInfo': { 'page': 'https://www.admatic.com.tr', 'domain': 'https://www.admatic.com.tr', - }, - 'bidder': 'admatic', - 'params': { - 'networkId': 10433394, - 'host': 'layer.serve.admatic.com.tr' - }, - 'ortb2Imp': { 'ext': { 'instl': 1 } }, - 'ortb2': { 'badv': ['admatic.com.tr'] }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [728, 90]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'creativeId': 'er2ee', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [728, 90]] - } } }; const request = spec.buildRequests(bidRequests, bidderRequest); - request.data.imp[0].floors = { - 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } + }); + + it('should properly build a native request with floors', function () { + const bidRequests = [ + { + 'bidder': 'admatic', + 'adUnitCode': 'bid-123', + 'transactionId': 'transaction-123', + 'mediaTypes': { + 'native': { + } + }, + 'ortb2Imp': { 'ext': { 'instl': 1 } }, + 'ortb2': { 'badv': ['admatic.com.tr'] }, + 'params': { + 'networkId': 10433394, + 'host': 'layer.serve.admatic.com.tr' + }, + getFloor: inputParams => { + if (inputParams.mediaType === NATIVE) { + return { + currency: 'USD', + floor: 1.0 + }; + } else { + return {} + } + } + }, + ]; + const bidderRequest = { + 'refererInfo': { + 'page': 'https://www.admatic.com.tr', + 'domain': 'https://www.admatic.com.tr', } }; + const request = spec.buildRequests(bidRequests, bidderRequest); }); }); @@ -495,7 +843,7 @@ describe('admaticBidAdapter', () => { 'mime_type': 'iframe', 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], - 'party_tag': '{"native":{"ver":"1.1","assets":[{"id":1,"title":{"text":"title"}},{"id":4,"data":{"value":"body"}},{"id":5,"data":{"value":"sponsored"}},{"id":2,"img":{"url":"https://www.admatic.com.tr","w":1200,"h":628}},{"id":3,"img":{"url":"https://www.admatic.com.tr","w":640,"h":480}}],"link":{"url":"https://www.admatic.com.tr"},"imptrackers":["https://www.admatic.com.tr"]}}', + 'party_tag': '{"native":{"ver":"1.1","assets":[{"id":1,"title":{"text":"title"}},{"id":4,"data":{"value":"body"}},{"id":5,"data":{"value":"sponsored"}},{"id":6,"data":{"value":"cta"}},{"id":2,"img":{"url":"https://www.admatic.com.tr","w":1200,"h":628}},{"id":3,"img":{"url":"https://www.admatic.com.tr","w":640,"h":480}}],"link":{"url":"https://www.admatic.com.tr"},"imptrackers":["https://www.admatic.com.tr"]}}', 'iurl': 'https://www.admatic.com.tr' } ], @@ -571,6 +919,7 @@ describe('admaticBidAdapter', () => { 'title': 'title', 'body': 'body', 'sponsoredBy': 'sponsored', + 'cta': 'cta', 'image': { 'url': 'https://www.admatic.com.tr', 'width': 1200, From 9ec20924a6d444e5ecea5e9f503b8cd479666422 Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Wed, 27 Dec 2023 19:24:43 +0200 Subject: [PATCH 295/760] Taboola Bid Adapter: Cookie Look Up Logic Fix (#10873) * cookie-look-up-logic-fix * cookie-look-up-logic-fix * cookie-look-up-logic-fix --- modules/taboolaBidAdapter.js | 12 +++++++++++- test/spec/modules/taboolaBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a8745449ee4..7402b725fd2 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -16,6 +16,8 @@ export const USER_SYNC_IFRAME_URL = 'https://cdn.taboola.com/scripts/prebid_ifra const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; +const TGID_COOKIE_KEY = 't_gid'; +const TBLA_ID_COOKIE_KEY = 'tbla_id'; export const EVENT_ENDPOINT = 'https://beacon.bidder.taboola.com'; /** @@ -40,10 +42,18 @@ export const userData = { const {cookiesAreEnabled, getCookie} = userData.storageManager; if (cookiesAreEnabled()) { const cookieData = getCookie(COOKIE_KEY); - const userId = userData.getCookieDataByKey(cookieData, USER_ID); + let userId = userData.getCookieDataByKey(cookieData, USER_ID); if (userId) { return userId; } + userId = getCookie(TGID_COOKIE_KEY); + if (userId) { + return userId; + } + const tblaId = getCookie(TBLA_ID_COOKIE_KEY); + if (tblaId) { + return tblaId; + } } }, getCookieDataByKey(cookieData, key) { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 402b4990166..280376e4e56 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -459,6 +459,22 @@ describe('Taboola Adapter', function () { expect(resData.user.buyeruid).to.equal('12121212'); }); + it('should get user id from tgid cookie if local storage isn`t defined', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.returns('d966c5be-c49f-4f73-8cd1-37b6b5790653-tuct9f7bf10'); + + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const resData = JSON.parse(res.data); + + expect(resData.user.buyeruid).to.equal('d966c5be-c49f-4f73-8cd1-37b6b5790653-tuct9f7bf10'); + }); + it('should get user id from TRC if local storage and cookie isn`t defined', function () { hasLocalStorage.returns(false); cookiesAreEnabled.returns(false); From 984abfa19c44c33cfdbc88fa311831d3fc26ee57 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Wed, 27 Dec 2023 18:28:08 +0100 Subject: [PATCH 296/760] ZetaGlobalSsp Bid Adapter : support topics module (#10803) * add getTopics() * provide get segments from ortb2 * rename const * additional check * fix test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 20 +++++++++++++ .../modules/zeta_global_sspBidAdapter_spec.js | 28 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index db31791d86f..0c5db541286 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -163,6 +163,7 @@ export const spec = { } provideEids(validBidRequests[0], payload); + provideSegments(bidderRequest, payload); const url = params.sid ? ENDPOINT_URL.concat('?sid=', params.sid) : ENDPOINT_URL; return { method: 'POST', @@ -335,6 +336,25 @@ function provideEids(request, payload) { } } +function provideSegments(bidderRequest, payload) { + const data = bidderRequest.ortb2?.user?.data; + if (isArray(data)) { + const segments = data.filter(d => d?.segment).map(d => d.segment).filter(s => isArray(s)).flatMap(s => s).filter(s => s?.id); + if (segments.length > 0) { + if (!payload.user) { + payload.user = {}; + } + if (!isArray(payload.user.data)) { + payload.user.data = []; + } + const payloadData = { + segment: segments + }; + payload.user.data.push(payloadData); + } + } +} + function provideMediaType(zetaBid, bid, bidRequest) { if (zetaBid.ext && zetaBid.ext.prebid && zetaBid.ext.prebid.type) { bid.mediaType = zetaBid.ext.prebid.type === VIDEO ? VIDEO : BANNER; diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index d4fe28eff90..9ef97019a98 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -124,7 +124,24 @@ describe('Zeta Ssp Bid Adapter', function () { uspConsent: 'someCCPAString', params: params, userIdAsEids: eids, - timeout: 500 + timeout: 500, + ortb2: { + user: { + data: [ + { + ext: { + segtax: 600, + segclass: 'classifier_v1' + }, + segment: [ + { id: '3' }, + { id: '44' }, + { id: '59' } + ] + } + ] + } + } }]; const bannerWithFewSizesRequest = [{ @@ -606,4 +623,13 @@ describe('Zeta Ssp Bid Adapter', function () { expect(bidResponse[0].ad).to.eql(zetaResponse.body.seatbid[0].bid[0].adm); expect(bidResponse[0].vastXml).to.be.undefined; }); + + it('Test provide segments into the request', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.user.data[0].segment.length).to.eql(3); + expect(payload.user.data[0].segment[0].id).to.eql('3'); + expect(payload.user.data[0].segment[1].id).to.eql('44'); + expect(payload.user.data[0].segment[2].id).to.eql('59'); + }); }); From 325cd3c51cb3e0708c85a132337fde2d589ed11f Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Thu, 28 Dec 2023 11:02:18 +0100 Subject: [PATCH 297/760] Adnuntius Bid Adapter: bugfix for storageFunction (#10869) * Removed linting issues * Fixed merge issues. * Bugfix on storageTool. --- modules/adnuntiusBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 8f841006524..6f3ed6eb5b6 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -30,14 +30,14 @@ const storageTool = (function () { const getMetaInternal = function () { if (!storage.localStorageIsEnabled()) { - return {}; + return []; } let parsedJson; try { parsedJson = JSON.parse(storage.getDataFromLocalStorage(META_DATA_KEY)); } catch (e) { - return {}; + return []; } let filteredEntries = parsedJson ? parsedJson.filter((datum) => { From bdbbf09a3182eb1c4198865d5542a79ed38a29c6 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 28 Dec 2023 20:52:14 +0000 Subject: [PATCH 298/760] Prebid 8.29.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 592fcb3d16d..fc1b8d76509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.29.0-pre", + "version": "8.29.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 62e99c75911..79d7aa1c74d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.29.0-pre", + "version": "8.29.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 46784ab77ea2349a5c8b4b487b28d7980d3f7f94 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 28 Dec 2023 20:52:14 +0000 Subject: [PATCH 299/760] Increment version to 8.30.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc1b8d76509..65b4011bca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.29.0", + "version": "8.30.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 79d7aa1c74d..85642969f06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.29.0", + "version": "8.30.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From cc499687325c378cf7bcf49c393b5ccfbf3ca67d Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:06:42 +0300 Subject: [PATCH 300/760] Yandex Bid Adapter: add support for topicsFpdModule (#10875) * Yandex Bid Adapter: add support for topicsFpdModule * add test --- modules/yandexBidAdapter.js | 5 +++ test/spec/modules/yandexBidAdapter_spec.js | 50 +++++++++++++--------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index 4a7f9114a17..038c1f3aa70 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -131,6 +131,11 @@ export const spec = { deepSetValue(data, 'user.ext.eids', eids); } + const userData = deepAccess(bidRequest, 'ortb2.user.data'); + if (userData && userData.length) { + deepSetValue(data, 'user.data', userData); + } + const queryParamsString = formatQS(queryParams); return { method: 'POST', diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index d5cfc26b31a..c5f088a2306 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -106,19 +106,37 @@ describe('Yandex adapter', function () { expect(query['ssp-cur']).to.equal('USD'); }); - it('should send eids if defined', function() { - const bannerRequest = getBidRequest({ + it('should send eids and ortb2 user data if defined', function() { + const bidRequestExtra = { userIdAsEids: [{ source: 'sharedid.org', - uids: [ - { - id: '01', - atype: 1 - } - ] - }] - }); + uids: [{ id: '01', atype: 1 }], + }], + ortb2: { + user: { + data: [ + { + ext: { segtax: 600, segclass: '1' }, + name: 'example.com', + segment: [{ id: '243' }], + }, + { + ext: { segtax: 600, segclass: '1' }, + name: 'ads.example.org', + segment: [{ id: '243' }], + }, + ], + }, + }, + }; + const expected = { + ext: { + eids: bidRequestExtra.userIdAsEids, + }, + data: bidRequestExtra.ortb2.user.data, + }; + const bannerRequest = getBidRequest(bidRequestExtra); const requests = spec.buildRequests([bannerRequest], bidderRequest); expect(requests).to.have.lengthOf(1); @@ -128,17 +146,7 @@ describe('Yandex adapter', function () { const { data } = request; expect(data.user).to.exist; - expect(data.user).to.deep.equal({ - ext: { - eids: [{ - source: 'sharedid.org', - uids: [{ - id: '01', - atype: 1, - }], - }], - } - }); + expect(data.user).to.deep.equal(expected); }); describe('banner', () => { From ff276735733334d1990cb3ccc0ec543229bf80e4 Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Sun, 31 Dec 2023 23:07:40 +0300 Subject: [PATCH 301/760] Yandex Bid Adapter: add jsdoc (#10884) --- modules/yandexBidAdapter.js | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index 038c1f3aa70..cc4a8c18241 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -4,10 +4,55 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js' import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { config } from '../src/config.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @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/auction.js').BidderRequest} BidderRequest + * @typedef {import('../src/mediaTypes.js').MediaType} MediaType + * @typedef {import('../src/utils.js').MediaTypes} MediaTypes + * @typedef {import('../modules/priceFloors.js').getFloor} GetFloor + */ + +/** + * @typedef {Object} CustomServerRequestFields + * @property {BidRequest} bidRequest + */ + +/** + * @typedef {ServerRequest & CustomServerRequestFields} YandexServerRequest + */ + +/** + * Yandex bidder-specific params which the publisher used in their bid request. + * + * @typedef {Object} YandexBidRequestParams + * @property {string} placementId Possible formats: `R-I-123456-2`, `R-123456-1`, `123456-789`. + * @property {number} [pageId] Deprecated. Please use `placementId` instead. + * @property {number} [impId] Deprecated. Please use `placementId` instead. + */ + +/** + * @typedef {Object} AdditionalBidRequestFields + * @property {GetFloor} [getFloor] + * @property {MediaTypes} [mediaTypes] + */ + +/** + * @typedef {BidRequest & AdditionalBidRequestFields} ExtendedBidRequest + */ + const BIDDER_CODE = 'yandex'; const BIDDER_URL = 'https://bs.yandex.ru/prebid'; const DEFAULT_TTL = 180; const DEFAULT_CURRENCY = 'EUR'; +/** + * @type {MediaType[]} + */ const SUPPORTED_MEDIA_TYPES = [ BANNER, NATIVE ]; const SSP_ID = 10500; @@ -42,11 +87,18 @@ export const NATIVE_ASSETS = { const NATIVE_ASSETS_IDS = {}; _each(NATIVE_ASSETS, (asset, key) => { NATIVE_ASSETS_IDS[asset[0]] = key }); +/** @type BidderSpec */ export const spec = { code: BIDDER_CODE, aliases: ['ya'], // short code supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid request to validate. + * @returns {boolean} True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { const { params } = bid; if (!params) { @@ -59,6 +111,13 @@ export const spec = { return true; }, + /** + * Make a server request from the list of BidRequests. + * + * @param {ExtendedBidRequest[]} validBidRequests An array of bids. + * @param {BidderRequest} bidderRequest Bidder request object. + * @returns {YandexServerRequest[]} Objects describing the requests to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); @@ -151,7 +210,11 @@ export const spec = { interpretResponse: interpretResponse, - onBidWon: function (bid) { + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction. + * @param {Bid} bid The bid that won the auction. + */ + onBidWon: function(bid) { const nurl = addRTT(bid['nurl'], bid.timeToRespond); if (!nurl) { @@ -162,6 +225,9 @@ export const spec = { } } +/** + * @param {YandexBidRequestParams} bidRequestParams + */ function extractPlacementIds(bidRequestParams) { const { placementId } = bidRequestParams; const result = { pageId: null, impId: null }; @@ -196,6 +262,9 @@ function extractPlacementIds(bidRequestParams) { return result; } +/** + * @param {ExtendedBidRequest} bidRequest + */ function getBidfloor(bidRequest) { const floors = []; @@ -215,6 +284,9 @@ function getBidfloor(bidRequest) { return floors.sort((a, b) => b.floor - a.floor)[0]; } +/** + * @param {ExtendedBidRequest} bidRequest + */ function mapBanner(bidRequest) { if (deepAccess(bidRequest, 'mediaTypes.banner')) { const sizes = bidRequest.sizes || bidRequest.mediaTypes.banner.sizes; @@ -232,6 +304,9 @@ function mapBanner(bidRequest) { } } +/** + * @param {ExtendedBidRequest} bidRequest + */ function mapNative(bidRequest) { const adUnitNativeAssets = deepAccess(bidRequest, 'mediaTypes.native'); if (adUnitNativeAssets) { @@ -304,6 +379,13 @@ function mapImageAsset(adUnitImageAssetParams, nativeAssetType) { return img; } +/** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {YandexServerRequest} yandexServerRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ function interpretResponse(serverResponse, { bidRequest }) { let response = serverResponse.body; if (!response.seatbid) { @@ -318,6 +400,7 @@ function interpretResponse(serverResponse, { bidRequest }) { return bidsReceived.map(bidReceived => { const price = bidReceived.price; + /** @type {Bid} */ let prBid = { requestId: bidRequest.bidId, cpm: price, From 233695d377cb815b2f1c8991efbdb2b8fb8496dd Mon Sep 17 00:00:00 2001 From: kmdevops <126434358+kmdevops@users.noreply.github.com> Date: Tue, 2 Jan 2024 18:35:02 +0100 Subject: [PATCH 302/760] DxKulture Bid Adapter : user syncs improvements (#10738) * Initial implementation of kulturemedia bid adapter * Changing outstream to instream * Enriching md file with test examples * Changing nId to networkId * Cleaning up md file * Submitting rebranded dxkultureBidAdapter * dxkultureBidAdapter - Improve UserSyncs * Include gdpr/usp params in iframe usersync url * Add gdpr/usp data to iframe usync urls * Cleaning up testing html file * Adding outstream support * Updating exchange endpoint * Resolve requests test * Resolving iframe/pixel priority when iframeEnabled/pixelEnabled * Improving userSync filtering condition * Prioritize iframe user syncing --------- Co-authored-by: Danijel Predarski Co-authored-by: dani-nova <73398187+dani-nova@users.noreply.github.com> Co-authored-by: Slavisa Petkovic Co-authored-by: Slavisa Petkovic <32300768+spetkovic@users.noreply.github.com> --- modules/dxkultureBidAdapter.js | 111 +++++++++++++----- test/spec/modules/dxkultureBidAdapter_spec.js | 79 ++++--------- 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/modules/dxkultureBidAdapter.js b/modules/dxkultureBidAdapter.js index 9e4768d12bb..282b54e0823 100644 --- a/modules/dxkultureBidAdapter.js +++ b/modules/dxkultureBidAdapter.js @@ -1,5 +1,6 @@ import { logInfo, + logWarn, logError, logMessage, deepAccess, @@ -8,13 +9,14 @@ import { } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' const BIDDER_CODE = 'dxkulture'; const DEFAULT_BID_TTL = 300; const DEFAULT_NET_REVENUE = true; const DEFAULT_CURRENCY = 'USD'; -const SYNC_URL = 'https://ads.kulture.media/usync'; +const DEFAULT_OUTSTREAM_RENDERER_URL = 'https://cdn.dxkulture.com/players/dxOutstreamPlayer.js'; const converter = ortbConverter({ context: { @@ -55,20 +57,14 @@ const converter = ortbConverter({ }, bidResponse(buildBidResponse, bid, context) { let resMediaType; + const {bidRequest} = context; + if (bid.adm?.trim().startsWith(' { + const value = userSync[key]; + if (value.syncs && value.syncs.length) { + syncDetails = syncDetails.concat(value.syncs); + } + }); + syncDetails.forEach(syncDetails => { + let queryParamStrings = []; + let syncUrl = syncDetails.url; + + if (syncDetails.type === 'iframe') { + if (gdprConsent) { + queryParamStrings.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParamStrings.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + if (uspConsent) { + queryParamStrings.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + syncUrl = `${syncDetails.url}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` + } + + syncs.push({ + type: syncDetails.type === 'iframe' ? 'iframe' : 'image', + url: syncUrl + }); + }); + + if (syncOptions.iframeEnabled) { + syncs = syncs.filter(s => s.type == 'iframe'); + } else if (syncOptions.pixelEnabled) { + syncs = syncs.filter(s => s.type == 'image'); + } } + }); + logInfo('dxkulture.getUserSyncs result=%o', syncs); + return syncs; + }, - return [{ - type: pixelType, - url: `${syncUrl}${queryParamStrings.length > 0 ? '?' + queryParamStrings.join('&') : ''}` - }]; - } +}; + +function outstreamRenderer(bid) { + const rendererConfig = { + width: bid.width, + height: bid.height, + vastTimeout: 5000, + maxAllowedVastTagRedirects: 3, + allowVpaid: false, + autoPlay: true, + preload: true, + mute: false } -}; + const renderer = Renderer.install({ + id: bid.adId, + url: DEFAULT_OUTSTREAM_RENDERER_URL, + config: rendererConfig, + loaded: false, + targetId: bid.adUnitCode, + adUnitCode: bid.adUnitCode + }); + + try { + renderer.setRender(function (bid) { + bid.renderer.push(() => { + const { id, config } = bid.renderer; + window.dxOutstreamPlayer(bid, id, config); + }); + }); + } catch (err) { + logWarn('dxkulture: Prebid Error calling setRender on renderer', err); + } + + return renderer; +} /* ======================================= * Util Functions diff --git a/test/spec/modules/dxkultureBidAdapter_spec.js b/test/spec/modules/dxkultureBidAdapter_spec.js index d3ae8ec5124..a752c81cb6e 100644 --- a/test/spec/modules/dxkultureBidAdapter_spec.js +++ b/test/spec/modules/dxkultureBidAdapter_spec.js @@ -281,7 +281,7 @@ describe('dxkultureBidAdapter', function() { it('should return expected request object', function() { const bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(bidRequest.url).equal('https://ads.kulture.media/pbjs?pid=publisherId&placementId=123456'); + expect(bidRequest.url).equal('https://ads.dxkulture.com/pbjs?pid=publisherId&placementId=123456'); expect(bidRequest.method).equal('POST'); }); }); @@ -606,7 +606,14 @@ describe('dxkultureBidAdapter', function() { }); }); - describe('user sync', function () { + describe('getUserSyncs', function () { + let bidRequest, bidderResponse; + beforeEach(function() { + const bidderRequest = getVideoRequest(); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + bidderResponse = getBidderResponse(); + }); + it('handles no parameters', function () { let opts = spec.getUserSyncs({}); expect(opts).to.be.an('array').that.is.empty; @@ -617,66 +624,26 @@ describe('dxkultureBidAdapter', function() { expect(opts).to.be.an('array').that.is.empty; }); - describe('when gdpr applies', function () { - let gdprConsent; - let gdprPixelUrl; - const consentString = 'gdpr-pixel-consent'; - const gdprApplies = '1'; - beforeEach(() => { - gdprConsent = { - consentString, - gdprApplies: true - }; - - gdprPixelUrl = `${SYNC_URL}&gdpr=${gdprApplies}&gdpr_consent=${consentString}`; - }); + it('iframe sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [bidderResponse]); - it('when there is a response, it should have the gdpr query params', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - gdprConsent - ); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['sovrn'].syncs[0].url); + }); - expect(url).to.have.string(`gdpr_consent=${consentString}`); - expect(url).to.have.string(`gdpr=${gdprApplies}`); - }); + it('pixel sync enabled should return results', function () { + let opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [bidderResponse]); - it('should not send signals if no consent object is available', function () { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - ); - expect(url).to.not.have.string('gdpr_consent='); - expect(url).to.not.have.string('gdpr='); - }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal(bidderResponse.body.ext.usersync['appnexus'].syncs[0].url); }); - describe('when ccpa applies', function () { - let usPrivacyConsent; - let uspPixelUrl; - const privacyString = 'TEST'; - beforeEach(() => { - usPrivacyConsent = 'TEST'; - uspPixelUrl = `${SYNC_URL}&us_privacy=${privacyString}` - }); - it('should send the us privacy string, ', () => { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - undefined, - usPrivacyConsent - ); - expect(url).to.have.string(`us_privacy=${privacyString}`); - }); + it('all sync enabled should prioritize iframe', function () { + let opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, [bidderResponse]); - it('should not send signals if no consent string is available', function () { - let [{url}] = spec.getUserSyncs( - {iframeEnabled: true, pixelEnabled: true}, - [], - ); - expect(url).to.not.have.string('us_privacy='); - }); + expect(opts.length).to.equal(1); }); }); }); From 04a7659e19767e5cbcef96e21ea7c052d80b12f5 Mon Sep 17 00:00:00 2001 From: Gena Date: Tue, 2 Jan 2024 22:44:17 +0200 Subject: [PATCH 303/760] Mygaru Id System: Initial release (#10848) --- modules/.submodules.json | 3 +- modules/mygaruIdSystem.js | 98 ++++++++++++++++++++++++ modules/mygaruIdSystem.md | 24 ++++++ modules/userId/eids.md | 7 ++ modules/userId/userId.md | 3 + test/spec/modules/mygaruIdSystem_spec.js | 62 +++++++++++++++ 6 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 modules/mygaruIdSystem.js create mode 100644 modules/mygaruIdSystem.md create mode 100644 test/spec/modules/mygaruIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 830d3c9b2f3..d2a13a57330 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -47,7 +47,8 @@ "adqueryIdSystem", "gravitoIdSystem", "freepassIdSystem", - "operaadsIdSystem" + "operaadsIdSystem", + "mygaruIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/mygaruIdSystem.js b/modules/mygaruIdSystem.js new file mode 100644 index 00000000000..4d50de16d48 --- /dev/null +++ b/modules/mygaruIdSystem.js @@ -0,0 +1,98 @@ +/** + * This module adds MyGaru Real Time User Sync to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/mygaruIdSystem + * @requires module:modules/userId + */ + +import { ajax } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +const bidderCode = 'mygaruId'; +const syncUrl = 'https://ident.mygaru.com/v2/id'; + +export function buildUrl(opts) { + const queryPairs = []; + for (let key in opts) { + if (opts[key] !== undefined) { + queryPairs.push(`${key}=${encodeURIComponent(opts[key])}`); + } + } + return `${syncUrl}?${queryPairs.join('&')}`; +} + +function requestRemoteIdAsync(url) { + return new Promise((resolve) => { + ajax( + url, + { + success: response => { + try { + const jsonResponse = JSON.parse(response); + const { iuid } = jsonResponse; + resolve(iuid); + } catch (e) { + resolve(); + } + }, + error: () => { + resolve(); + }, + }, + undefined, + { + method: 'GET', + contentType: 'application/json' + } + ); + }); +} + +/** @type {Submodule} */ +export const mygaruIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: bidderCode, + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{id: string} | null} + */ + decode(id) { + return id; + }, + /** + * get the MyGaru Id from local storages and initiate a new user sync + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @returns {{id: string | undefined}} + */ + getId(config, consentData) { + const gdprApplies = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies ? 1 : 0; + const gdprConsentString = gdprApplies ? consentData.consentString : undefined; + const url = buildUrl({ + gdprApplies, + gdprConsentString + }); + + return { + url, + callback: function (done) { + return requestRemoteIdAsync(url).then((id) => { + done({ mygaruId: id }); + }) + } + } + }, + eids: { + 'mygaruId': { + source: 'mygaru.com', + atype: 1 + }, + } +}; + +submodule('userId', mygaruIdSubmodule); diff --git a/modules/mygaruIdSystem.md b/modules/mygaruIdSystem.md new file mode 100644 index 00000000000..92724f99469 --- /dev/null +++ b/modules/mygaruIdSystem.md @@ -0,0 +1,24 @@ +## Mygaru User ID Submodule + +MyGaru provides single use tokens as a UserId for SSPs and DSP that consume telecom DMP data. + +## Building Prebid with Mygaru ID Support + +First, make sure to add submodule to your Prebid.js package with: + +``` +gulp build --modules=userId,mygaruIdSystem +``` +Params configuration is not required. +Also mygaru is async, in order to get ids for initial ad auctions you need to add auctionDelay param to userSync config. + +```javascript +pbjs.setConfig({ + userSync: { + auctionDelay: 100, + userIds: [{ + name: 'mygaruId', + }] + } +}); +``` diff --git a/modules/userId/eids.md b/modules/userId/eids.md index eb37c50f0a3..c8d27cc9d52 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -324,6 +324,13 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 3 }] + }, + { + source: 'mygaru.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] } ] ``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 01930a67dda..7a01e128814 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -158,6 +158,9 @@ pbjs.setConfig({ }, { name: "gravitompId" + }, + { + name: "mygaruId" } ], syncDelay: 5000, diff --git a/test/spec/modules/mygaruIdSystem_spec.js b/test/spec/modules/mygaruIdSystem_spec.js new file mode 100644 index 00000000000..2bfb5fdd4af --- /dev/null +++ b/test/spec/modules/mygaruIdSystem_spec.js @@ -0,0 +1,62 @@ +import { mygaruIdSubmodule } from 'modules/mygaruIdSystem.js'; +import { server } from '../../mocks/xhr'; + +describe('MygaruID module', function () { + it('should respond with async callback and get valid id', async () => { + const callBackSpy = sinon.spy(); + const expectedUrl = `https://ident.mygaru.com/v2/id?gdprApplies=0`; + const result = mygaruIdSubmodule.getId({}); + + expect(result.callback).to.be.an('function'); + const promise = result.callback(callBackSpy); + + const request = server.requests[0]; + expect(request.url).to.be.eq(expectedUrl); + + request.respond( + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify({ iuid: '123' }) + ); + await promise; + + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledWith({mygaruId: '123'})).to.be.true; + }); + it('should not fail on error', async () => { + const callBackSpy = sinon.spy(); + const expectedUrl = `https://ident.mygaru.com/v2/id?gdprApplies=0`; + const result = mygaruIdSubmodule.getId({}); + + expect(result.callback).to.be.an('function'); + const promise = result.callback(callBackSpy); + + const request = server.requests[0]; + expect(request.url).to.be.eq(expectedUrl); + + request.respond( + 500, + {}, + '' + ); + await promise; + + expect(callBackSpy.calledOnce).to.be.true; + expect(callBackSpy.calledWith({mygaruId: undefined})).to.be.true; + }); + + it('should not modify while decoding', () => { + const id = '222'; + const newId = mygaruIdSubmodule.decode(id) + + expect(id).to.eq(newId); + }) + it('should buildUrl with consent data', () => { + const result = mygaruIdSubmodule.getId({}, { + gdprApplies: true, + consentString: 'consentString' + }); + + expect(result.url).to.eq('https://ident.mygaru.com/v2/id?gdprApplies=1&gdprConsentString=consentString'); + }) +}); From ed054783d255943538ee427a3b019ab8973aa5df Mon Sep 17 00:00:00 2001 From: tkrishnaviant Date: Wed, 3 Jan 2024 06:07:13 -0800 Subject: [PATCH 304/760] Update viantOrtbBidAdapter_spec.js (#10888) Added a test case for native ads --- test/spec/modules/viantOrtbBidAdapter_spec.js | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index 6d5014bd639..73fdb7f3dc8 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -109,6 +109,49 @@ describe('viantOrtbBidAdapter', function () { }); }); }); + + describe('native', function () { + describe('and request config uses mediaTypes', () => { + function makeBid() { + return { + 'bidder': 'viant', + 'params': { + 'unit': '12345678', + 'delDomain': 'test-del-domain', + 'publisherId': '464', + 'placementId': 'some-PlacementId_2' + }, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [[640, 480]], + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'api': [1, 3], + 'skip': 1, + 'skipafter': 5, + 'minduration': 10, + 'maxduration': 30 + } + }, + 'adUnitCode': 'adunit-code', + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' + } + } + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(makeBid())).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let nativeBidWithMediaTypes = Object.assign({}, makeBid()); + nativeBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(nativeBidWithMediaTypes)).to.equal(false); + }); + }); + }); }); describe('buildRequests-banner', function () { From 665c89498e059eb550540985acc84ba1ae275e06 Mon Sep 17 00:00:00 2001 From: theo-stv <120092078+theo-stv@users.noreply.github.com> Date: Wed, 3 Jan 2024 15:24:08 +0100 Subject: [PATCH 305/760] Stv Bid Adapter: added user id support, adaptation of schain support (#10849) * initial commit * adapted buildRequests function * refinement pfilter and bcat * refinement * adapted tests for isBidRequestValid,buildRequests * adaptations for test * finished building stvBidAdapter.js * finished: ran tests, coverage 99% * update: rename w->srw, h->srh * adapt stvBidAdapter.md * remove dspx from stv adapters * some changes (missing: getUserSyncs, but is the same as in radsBidAdapter) * added checks in getUserSyncs; ran tests * added schain support (94.8% coverage) * correct schain encoding * added serializeUids and adapted serializeSChain --------- Co-authored-by: theo_ --- modules/stvBidAdapter.js | 59 +++++++++++++++++++++++-- test/spec/modules/stvBidAdapter_spec.js | 44 ++++++++++++++++-- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 8d368b996fc..0dacf389955 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -51,6 +51,7 @@ export const spec = { bid_id: bidId, pbver: '$prebid.version$', schain: '', + uids: '', }; if (!isVideoRequest(bidRequest)) { payload._f = 'html'; @@ -61,6 +62,11 @@ export const spec = { delete payload.schain; } + payload.uids = serializeUids(bidRequest); + if (payload.uids == '') { + delete payload.uids; + } + payload.pfilter = { ...params }; delete payload.pfilter.placement; if (params.bcat !== undefined) { delete payload.pfilter.bcat; } @@ -201,7 +207,7 @@ function objectToQueryString(obj, prefix) { let v = obj[p]; str.push((v !== null && typeof v === 'object') ? objectToQueryString(v, k) - : (k == 'schain' ? k + '=' + v : encodeURIComponent(k) + '=' + encodeURIComponent(v))); + : (k == 'schain' || k == 'uids' ? k + '=' + v : encodeURIComponent(k) + '=' + encodeURIComponent(v))); } } return str.join('&'); @@ -227,13 +233,60 @@ function serializeSChain(schain) { ret += encodeURIComponent(node.name ?? ''); ret += ','; ret += encodeURIComponent(node.domain ?? ''); - ret += ','; - ret += encodeURIComponent(node.ext ?? ''); + if (node.ext) { + ret += ','; + ret += encodeURIComponent(node.ext ?? ''); + } } return ret; } +function serializeUids(bidRequest) { + let uids = []; + + let id5 = deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + uids.push(encodeURIComponent('id5:' + id5)); + let id5Linktype = deepAccess(bidRequest, 'userId.id5id.ext.linkType'); + if (id5Linktype) { + uids.push(encodeURIComponent('id5_linktype:' + id5Linktype)); + } + } + let netId = deepAccess(bidRequest, 'userId.netId'); + if (netId) { + uids.push(encodeURIComponent('netid:' + netId)); + } + let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (uId2) { + uids.push(encodeURIComponent('uid2:' + uId2)); + } + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + uids.push(encodeURIComponent('sharedid:' + sharedId)); + } + let liverampId = deepAccess(bidRequest, 'userId.idl_env'); + if (liverampId) { + uids.push(encodeURIComponent('liverampid:' + liverampId)); + } + let criteoId = deepAccess(bidRequest, 'userId.criteoId'); + if (criteoId) { + uids.push(encodeURIComponent('criteoid:' + criteoId)); + } + // documentation missing... + let utiqId = deepAccess(bidRequest, 'userId.utiq.id'); + if (utiqId) { + uids.push(encodeURIComponent('utiq:' + utiqId)); + } else { + utiqId = deepAccess(bidRequest, 'userId.utiq'); + if (utiqId) { + uids.push(encodeURIComponent('utiq:' + utiqId)); + } + } + + return uids.join(','); +} + /** * Check if it's a banner bid request * diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js index 41f29cced34..3ef865ed2f1 100644 --- a/test/spec/modules/stvBidAdapter_spec.js +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -71,6 +71,24 @@ describe('stvAdapter', function() { 'hp': 1 } ] + }, + 'userId': { + 'id5id': { + 'uid': '1234', + 'ext': { + 'linkType': 'abc' + } + }, + 'netId': '2345', + 'uid2': { + 'id': '3456', + }, + 'sharedid': { + 'id': '4567', + }, + 'idl_env': '5678', + 'criteoId': '6789', + 'utiq': '7890', } }, { @@ -84,7 +102,27 @@ describe('stvAdapter', function() { ], 'bidId': '30b31c1838de1e2', 'bidderRequestId': '22edbae2733bf62', - 'auctionId': '1d1a030790a476' + 'auctionId': '1d1a030790a476', + 'userId': { // with other utiq variant + 'id5id': { + 'uid': '1234', + 'ext': { + 'linkType': 'abc' + } + }, + 'netId': '2345', + 'uid2': { + 'id': '3456', + }, + 'sharedid': { + 'id': '4567', + }, + 'idl_env': '5678', + 'criteoId': '6789', + 'utiq': { + 'id': '7890' + }, + } }, { 'bidder': 'stv', 'params': { @@ -181,7 +219,7 @@ describe('stvAdapter', function() { expect(request1.method).to.equal('GET'); expect(request1.url).to.equal(ENDPOINT_URL); let data = request1.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&schain=1.0,0!reseller.com,aaaaa,1,BidRequest4,,,&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&schain=1.0,0!reseller.com,aaaaa,1,BidRequest4,,&uids=id5%3A1234,id5_linktype%3Aabc,netid%3A2345,uid2%3A3456,sharedid%3A4567,liverampid%3A5678,criteoid%3A6789,utiq%3A7890&pfilter%5Bfloorprice%5D=1000000&pfilter%5Bgeo%5D%5Bcountry%5D=DE&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&bcat=IAB2%2CIAB4&dvt=desktop&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -189,7 +227,7 @@ describe('stvAdapter', function() { expect(request2.method).to.equal('GET'); expect(request2.url).to.equal(ENDPOINT_URL); let data = request2.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=html&alternative=prebid_js&_ps=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&prebidDevMode=1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=html&alternative=prebid_js&_ps=101&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e2&pbver=test&uids=id5%3A1234,id5_linktype%3Aabc,netid%3A2345,uid2%3A3456,sharedid%3A4567,liverampid%3A5678,criteoid%3A6789,utiq%3A7890&gdpr_consent=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&gdpr=true&prebidDevMode=1&media_types%5Bbanner%5D=300x250'); }); // Without gdprConsent From 185e97f9cd073ee8cf29ac85c2ebb10249526afa Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Wed, 3 Jan 2024 16:40:32 +0200 Subject: [PATCH 306/760] Missena Bid Adapter: allow per page capping (#10863) --- modules/missenaBidAdapter.js | 4 +- test/spec/modules/missenaBidAdapter_spec.js | 41 ++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index a06ba1ce29c..5e68f56ed1c 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -59,9 +59,11 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const capKey = `missena.missena.capper.remove-bubble.${validBidRequests[0]?.params.apiKey}`; const capping = safeJSONParse(storage.getDataFromLocalStorage(capKey)); + const referer = bidderRequest?.refererInfo?.topmostLocation; if ( typeof capping?.expiry === 'number' && - new Date().getTime() < capping?.expiry + new Date().getTime() < capping?.expiry && + (!capping?.referer || capping?.referer == referer) ) { logInfo('Missena - Capped'); return []; diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index 15f9eff89e7..a51cb8bbac9 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -2,6 +2,9 @@ import { expect } from 'chai'; import { spec, storage } from 'modules/missenaBidAdapter.js'; import { BANNER } from '../../../src/mediaTypes.js'; +const REFERRER = 'https://referer'; +const REFERRER2 = 'https://referer2'; + describe('Missena Adapter', function () { $$PREBID_GLOBAL$$.bidderSettings = { missena: { @@ -50,7 +53,7 @@ describe('Missena Adapter', function () { gdprApplies: true, }, refererInfo: { - topmostLocation: 'https://referer', + topmostLocation: REFERRER, canonicalUrl: 'https://canonical', }, }; @@ -112,7 +115,7 @@ describe('Missena Adapter', function () { }); it('should send referer information to the request', function () { - expect(payload.referer).to.equal('https://referer'); + expect(payload.referer).to.equal(REFERRER); expect(payload.referer_canonical).to.equal('https://canonical'); }); @@ -146,6 +149,40 @@ describe('Missena Adapter', function () { it('should not participate if capped', function () { expect(cappedRequests.length).to.equal(0); }); + + const localStorageDataSamePage = { + [`missena.missena.capper.remove-bubble.${bid.params.apiKey}`]: + JSON.stringify({ + expiry: new Date().getTime() + 600_000, // 10 min into the future + referer: REFERRER, + }), + }; + + getDataFromLocalStorageStub.callsFake( + (key) => localStorageDataSamePage[key], + ); + const cappedRequestsSamePage = spec.buildRequests(bids, bidderRequest); + + it('should not participate if capped on same page', function () { + expect(cappedRequestsSamePage.length).to.equal(0); + }); + + const localStorageDataOtherPage = { + [`missena.missena.capper.remove-bubble.${bid.params.apiKey}`]: + JSON.stringify({ + expiry: new Date().getTime() + 600_000, // 10 min into the future + referer: REFERRER2, + }), + }; + + getDataFromLocalStorageStub.callsFake( + (key) => localStorageDataOtherPage[key], + ); + const cappedRequestsOtherPage = spec.buildRequests(bids, bidderRequest); + + it('should participate if capped on a different page', function () { + expect(cappedRequestsOtherPage.length).to.equal(2); + }); }); describe('interpretResponse', function () { From 49904a04c1796187eae6fecb4f827a375b3cb73d Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Wed, 3 Jan 2024 21:15:45 +0100 Subject: [PATCH 307/760] LiveIntent ID Module: Update live-connect version (#10894) * update lc version * fix typo --- package-lock.json | 32 +++++++------- package.json | 2 +- .../modules/liveIntentIdMinimalSystem_spec.js | 4 +- test/spec/modules/liveIntentIdSystem_spec.js | 42 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65b4011bca8..11d2f1fd427 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.24.0-pre", + "version": "8.30.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.3.0" + "live-connect-js": "^6.3.2" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16336,19 +16336,19 @@ "dev": true }, "node_modules/live-connect-common": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.2.tgz", - "integrity": "sha512-K3LNKd9CpREDJbXGdwKqPojjQaxd4G6c7OAD6Yzp3wsCWTH2hV8xNAbUksSOpOcVyyOT9ilteEFXIJQJrbODxQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", + "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==", "engines": { "node": ">=18" } }, "node_modules/live-connect-js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.0.tgz", - "integrity": "sha512-1SnXQZq9gxHIb0scXPX1Da1rQ0oY2sloMGgeRreTAwhCtdQEuip/IYwgOh3/ZeZ6yT6iG9FLb7+AjORC4pO46g==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.3.tgz", + "integrity": "sha512-njpWQgoJuXhxm+XBJQvT672wR20/qROPUfUKwIXPwEtGXznJYlrUDPJDChEknBiKIo7P4jOeirAMwcZWPCN4ow==", "dependencies": { - "live-connect-common": "^v3.0.2", + "live-connect-common": "^v3.0.3", "tiny-hashes": "1.0.1" }, "engines": { @@ -38079,16 +38079,16 @@ "dev": true }, "live-connect-common": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.2.tgz", - "integrity": "sha512-K3LNKd9CpREDJbXGdwKqPojjQaxd4G6c7OAD6Yzp3wsCWTH2hV8xNAbUksSOpOcVyyOT9ilteEFXIJQJrbODxQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.0.3.tgz", + "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==" }, "live-connect-js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.0.tgz", - "integrity": "sha512-1SnXQZq9gxHIb0scXPX1Da1rQ0oY2sloMGgeRreTAwhCtdQEuip/IYwgOh3/ZeZ6yT6iG9FLb7+AjORC4pO46g==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.3.tgz", + "integrity": "sha512-njpWQgoJuXhxm+XBJQvT672wR20/qROPUfUKwIXPwEtGXznJYlrUDPJDChEknBiKIo7P4jOeirAMwcZWPCN4ow==", "requires": { - "live-connect-common": "^v3.0.2", + "live-connect-common": "^v3.0.3", "tiny-hashes": "1.0.1" } }, diff --git a/package.json b/package.json index 85642969f06..443cf0fc503 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.3.0" + "live-connect-js": "^6.3.2" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index 0ada7adc594..ad21d7b6763 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -73,7 +73,7 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); - it('should call the Identity Exchange endpoint with the privided distributorId', function() { + it('should call the Identity Exchange endpoint with the provided distributorId', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; @@ -87,7 +87,7 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnceWith({})).to.be.true; }); - it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + it('should call the Identity Exchange endpoint without the provided distributorId when appId is provided', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 506c5b312cc..4af88e1a894 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -186,7 +186,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?resolve=nonId'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 204, responseHeader @@ -194,13 +194,13 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith({})).to.be.true; }); - it('should call the Identity Exchange endpoint with the privided distributorId', function() { + it('should call the Identity Exchange endpoint with the provided distributorId', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&resolve=nonId'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&cd=.localhost&resolve=nonId'); request.respond( 204, responseHeader @@ -208,13 +208,13 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnceWith({})).to.be.true; }); - it('should call the Identity Exchange endpoint without the privided distributorId when appId is provided', function() { + it('should call the Identity Exchange endpoint without the provided distributorId when appId is provided', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?resolve=nonId'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?cd=.localhost&resolve=nonId'); request.respond( 204, responseHeader @@ -234,7 +234,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?resolve=nonId'); + expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?cd=.localhost&resolve=nonId'); request.respond( 200, responseHeader, @@ -249,7 +249,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 200, responseHeader, @@ -264,7 +264,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?resolve=nonId'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 503, responseHeader, @@ -281,7 +281,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&resolve=nonId`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&resolve=nonId`); request.respond( 200, responseHeader, @@ -304,7 +304,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&_thirdPC=third-pc&resolve=nonId`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, responseHeader, @@ -326,7 +326,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); + expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, responseHeader, @@ -359,7 +359,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=nonId&resolve=foo`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId&resolve=foo`); request.respond( 200, responseHeader, @@ -368,7 +368,7 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnce).to.be.true; }); - it('should decode a uid2 to a seperate object when present', function() { + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); @@ -378,37 +378,37 @@ describe('LiveIntentId', function() { expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a bidswitch id to a seperate object when present', function() { + it('should decode a bidswitch id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a medianet id to a seperate object when present', function() { + it('should decode a medianet id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a sovrn id to a seperate object when present', function() { + it('should decode a sovrn id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a magnite id to a seperate object when present', function() { + it('should decode a magnite id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an index id to a seperate object when present', function() { + it('should decode an index id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an openx id to a seperate object when present', function () { + it('should decode an openx id to a separate object when present', function () { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an pubmatic id to a seperate object when present', function() { + it('should decode an pubmatic id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); @@ -421,7 +421,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = server.requests[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?resolve=uid2`); + expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=uid2`); request.respond( 200, responseHeader, From 136f0bba595d4edda0023774ce59b65a64bb28a7 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Wed, 3 Jan 2024 12:18:53 -0800 Subject: [PATCH 308/760] Do not require API for video requests (#10895) --- modules/rubiconBidAdapter.js | 3 +-- test/spec/modules/rubiconBidAdapter_spec.js | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index bff7661455a..226950875e2 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1183,8 +1183,7 @@ export function hasValidVideoParams(bid) { var requiredParams = { mimes: arrayType, protocols: arrayType, - linearity: numberType, - api: arrayType + linearity: numberType } // loop through each param and verify it has the correct Object.keys(requiredParams).forEach(function(param) { diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 3f3cbfbb46c..f0e33ce940e 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2381,16 +2381,6 @@ describe('the rubicon adapter', function () { bidderRequest = createVideoBidderRequest(); delete bidderRequest.bids[0].mediaTypes.video.linearity; expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // change api to an string, no good - bidderRequest = createVideoBidderRequest(); - bidderRequest.bids[0].mediaTypes.video.api = 'string'; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); - - // delete api, no good - bidderRequest = createVideoBidderRequest(); - delete bidderRequest.bids[0].mediaTypes.video.api; - expect(spec.isBidRequestValid(bidderRequest.bids[0])).to.equal(false); }); it('bid request is valid when video context is outstream', function () { From 7a09d81d65d72ebdca928c4efb628f00afdd4d30 Mon Sep 17 00:00:00 2001 From: bbaresic <153560835+bbaresic@users.noreply.github.com> Date: Thu, 4 Jan 2024 09:34:08 -0600 Subject: [PATCH 309/760] Price Floors: Failure to Account for 'data.skipRate' (#10872) * Update skipRate handling in priceFloors.js and add unit tests for the changes. * Update wording on tests and remove unecessary spread. --- modules/priceFloors.js | 4 +- test/spec/modules/priceFloors_spec.js | 86 ++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 4ec8a8e4b7e..3837f56366c 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -347,7 +347,7 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { bid.auctionId = auctionId; bid.floorData = { skipped: floorData.skipped, - skipRate: floorData.skipRate, + skipRate: deepAccess(floorData, 'data.skipRate') ?? floorData.skipRate, floorMin: floorData.floorMin, modelVersion: deepAccess(floorData, 'data.modelVersion'), modelWeight: deepAccess(floorData, 'data.modelWeight'), @@ -396,7 +396,7 @@ export function createFloorsDataForAuction(adUnits, auctionId) { resolvedFloorsData.skipped = true; } else { // determine the skip rate now - const auctionSkipRate = getParameterByName('pbjs_skipRate') || resolvedFloorsData.skipRate; + const auctionSkipRate = getParameterByName('pbjs_skipRate') || (deepAccess(resolvedFloorsData, 'data.skipRate') ?? resolvedFloorsData.skipRate); const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; } diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 673a821b497..f618e57e60d 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -12,7 +12,7 @@ import { isFloorsDataValid, addBidResponseHook, fieldMatchingFunctions, - allowedFields, parseFloorData, normalizeDefault, getFloorDataFromAdUnits + allowedFields, parseFloorData, normalizeDefault, getFloorDataFromAdUnits, updateAdUnitsForAuction, createFloorsDataForAuction } from 'modules/priceFloors.js'; import * as events from 'src/events.js'; import * as mockGpt from '../integration/faker/googletag.js'; @@ -648,6 +648,90 @@ describe('the price floors module', function () { }); }); }); + + describe('updateAdUnitsForAuction', function() { + let inputFloorData; + let adUnits; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + inputFloorData = utils.deepClone(minFloorConfigLow); + inputFloorData.skipRate = 0.5; + }); + + it('should set the skipRate to the skipRate from the data property before using the skipRate from floorData directly', function() { + utils.deepSetValue(inputFloorData, 'data', { + skipRate: 0.7 + }); + updateAdUnitsForAuction(adUnits, inputFloorData, 'id'); + + const skipRate = utils.deepAccess(adUnits, '0.bids.0.floorData.skipRate'); + expect(skipRate).to.equal(0.7); + }); + + it('should set the skipRate to the skipRate from floorData directly if it does not exist in the data property of floorData', function() { + updateAdUnitsForAuction(adUnits, inputFloorData, 'id'); + + const skipRate = utils.deepAccess(adUnits, '0.bids.0.floorData.skipRate'); + expect(skipRate).to.equal(0.5); + }); + + it('should set the skipRate in the bid floorData to undefined if both skipRate and skipRate in the data property are undefined', function() { + inputFloorData.skipRate = undefined; + utils.deepSetValue(inputFloorData, 'data', { + skipRate: undefined, + }); + updateAdUnitsForAuction(adUnits, inputFloorData, 'id'); + + const skipRate = utils.deepAccess(adUnits, '0.bids.0.floorData.skipRate'); + expect(skipRate).to.equal(undefined); + }); + }); + + describe('createFloorsDataForAuction', function() { + let adUnits; + let floorConfig; + + beforeEach(function() { + adUnits = [getAdUnitMock()]; + floorConfig = utils.deepClone(basicFloorConfig); + }); + + it('should return skipRate as 0 if both skipRate and skipRate in the data property are undefined', function() { + floorConfig.skipRate = undefined; + floorConfig.data.skipRate = undefined; + handleSetFloorsConfig(floorConfig); + + const floorData = createFloorsDataForAuction(adUnits, 'id'); + + expect(floorData.skipRate).to.equal(0); + expect(floorData.skipped).to.equal(false); + }); + + it('should properly set skipRate if it is available in the data property', function() { + // this will force skipped to be true + floorConfig.skipRate = 101; + floorConfig.data.skipRate = 201; + handleSetFloorsConfig(floorConfig); + + const floorData = createFloorsDataForAuction(adUnits, 'id'); + + expect(floorData.data.skipRate).to.equal(201); + expect(floorData.skipped).to.equal(true); + }); + + it('should should use the skipRate if its not available in the data property ', function() { + // this will force skipped to be true + floorConfig.skipRate = 101; + handleSetFloorsConfig(floorConfig); + + const floorData = createFloorsDataForAuction(adUnits, 'id'); + + expect(floorData.skipRate).to.equal(101); + expect(floorData.skipped).to.equal(true); + }); + }); + describe('pre-auction tests', function () { let exposedAdUnits; const validateBidRequests = (getFloorExpected, FloorDataExpected) => { From aa21bfd0e226fe7344ffbf1cf1f383fbc6919adc Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 4 Jan 2024 18:27:53 +0000 Subject: [PATCH 310/760] Prebid 8.30.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11d2f1fd427..577b65c74f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.30.0-pre", + "version": "8.30.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 443cf0fc503..ad2801bf812 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.30.0-pre", + "version": "8.30.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 714acd35f2a92417ebcd9bd7add9fe16428d4601 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 4 Jan 2024 18:27:53 +0000 Subject: [PATCH 311/760] Increment version to 8.31.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 577b65c74f7..cfdc8669c2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.30.0", + "version": "8.31.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ad2801bf812..830f700537d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.30.0", + "version": "8.31.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 866be6e48ca21bb3fdd727a99f8ff09befb8cc43 Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Fri, 5 Jan 2024 00:29:13 +0530 Subject: [PATCH 312/760] 1.Added safechecks for s2s metadapter case 2.Skipped firing client side tracker for pubmatic 3.Skipped adding pubmatic bid in logger (#10897) --- modules/pubmaticAnalyticsAdapter.js | 38 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 346 ++++++++++++++---- 2 files changed, 308 insertions(+), 76 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 9b910caa992..ad2a06ea86d 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -9,6 +9,7 @@ import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; +const VENDOR_OPENWRAP = 'openwrap'; const SEND_TIMEOUT = 2000; const END_POINT_HOST = 'https://t.pubmatic.com/'; const END_POINT_BID_LOGGER = END_POINT_HOST + 'wl?'; @@ -258,12 +259,27 @@ function isS2SBidder(bidder) { return (s2sBidders.indexOf(bidder) > -1) ? 1 : 0 } +function isOWPubmaticBid(adapterName) { + let s2sConf = config.getConfig('s2sConfig'); + let s2sConfArray = isArray(s2sConf) ? s2sConf : [s2sConf]; + return s2sConfArray.some(conf => { + if (adapterName === ADAPTER_CODE && conf.defaultVendor === VENDOR_OPENWRAP && + conf.bidders.indexOf(ADAPTER_CODE) > -1) { + return true; + } + }) +} + function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { + let adapterName = getAdapterNameForAlias(bid.adapterCode || bid.bidder); + if (isOWPubmaticBid(adapterName) && isS2SBidder(bid.bidder)) { + return; + } partnerBids.push({ - 'pn': getAdapterNameForAlias(bid.adapterCode || bid.bidder), + 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, 'bidid': bid.bidId || bidId, 'db': bid.bidResponse ? 0 : 1, @@ -419,13 +435,20 @@ function executeBidsLoggerCall(e, highestCpmBids) { function executeBidWonLoggerCall(auctionId, adUnitId) { const winningBidId = cache.auctions[auctionId].adUnitCodes[adUnitId].bidWon; const winningBids = cache.auctions[auctionId].adUnitCodes[adUnitId].bids[winningBidId]; - let winningBid = winningBids[0]; + if (!winningBids) { + logWarn(LOG_PRE_FIX + 'Could not find winningBids for : ', auctionId); + return; + } + let winningBid = winningBids[0]; if (winningBids.length > 1) { winningBid = winningBids.filter(bid => bid.adId === cache.auctions[auctionId].adUnitCodes[adUnitId].bidWonAdId)[0]; } const adapterName = getAdapterNameForAlias(winningBid.adapterCode || winningBid.bidder); + if (isOWPubmaticBid(adapterName) && isS2SBidder(winningBid.bidder)) { + return; + } let origAdUnit = getAdUnit(cache.auctions[auctionId].origAdUnits, adUnitId) || {}; let auctionCache = cache.auctions[auctionId]; let floorData = auctionCache.floorData; @@ -479,7 +502,10 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { function auctionInitHandler(args) { s2sBidders = (function() { let s2sConf = config.getConfig('s2sConfig'); - return (s2sConf && isArray(s2sConf.bidders)) ? s2sConf.bidders : []; + let s2sBidders = []; + (s2sConf || []) && + isArray(s2sConf) ? s2sConf.map(conf => s2sBidders.push(...conf.bidders)) : s2sBidders.push(...s2sConf.bidders); + return s2sBidders || []; }()); let cacheEntry = pick(args, [ 'timestamp', @@ -513,6 +539,10 @@ function bidRequestedHandler(args) { } function bidResponseHandler(args) { + if (!args.requestId) { + logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); + return; + } let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); @@ -577,7 +607,7 @@ function auctionEndHandler(args) { let highestCpmBids = getGlobal().getHighestCpmBids() || []; setTimeout(() => { executeBidsLoggerCall.call(this, args, highestCpmBids); - }, (cache.auctions[args.auctionId].bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); + }, (cache.auctions[args.auctionId]?.bidderDonePendingCount === 0 ? 500 : SEND_TIMEOUT)); } function bidTimeoutHandler(args) { diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 6af497bd2e9..c6447905ecd 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import pubmaticAnalyticsAdapter, {getMetadata} from 'modules/pubmaticAnalyticsAdapter.js'; +import pubmaticAnalyticsAdapter, { getMetadata } from 'modules/pubmaticAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -316,6 +316,208 @@ describe('pubmatic analytics adapter', function () { expect(utils.logError.called).to.equal(true); }); + describe('OW S2S', function() { + this.beforeEach(function() { + pubmaticAnalyticsAdapter.enableAnalytics({ + options: { + publisherId: 9999, + profileId: 1111, + profileVersionId: 20 + } + }); + config.setConfig({ + s2sConfig: { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap', + timeout: 500 + } + }); + }); + + this.afterEach(function() { + pubmaticAnalyticsAdapter.disableAnalytics(); + }); + + it('Pubmatic Won: No tracker fired', function() { + this.timeout(5000) + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(1); // only logger is fired + let request = requests[0]; + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + }); + + it('Non-pubmatic won: logger, tracker fired', function() { + const APPNEXUS_BID = Object.assign({}, BID, { + 'bidder': 'appnexus', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '2ecff0db240757', + 'hb_pb': 1.20, + 'hb_size': '640x480', + 'hb_source': 'server' + } + }); + + const MOCK_AUCTION_INIT_APPNEXUS = { + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'timestamp': 1519767010567, + 'auctionStatus': 'inProgress', + 'adUnits': [ { + 'code': '/19968336/header-bid-tag-1', + 'sizes': [[640, 480]], + 'bids': [ { + 'bidder': 'appnexus', + 'params': { + 'publisherId': '1001' + } + } ], + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + } + ], + 'adUnitCodes': ['/19968336/header-bid-tag-1'], + 'bidderRequests': [ { + 'bidderCode': 'appnexus', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'appnexus', + 'params': { + 'publisherId': '1001', + 'kgpv': 'this-is-a-kgpv' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } + } + ], + 'bidsReceived': [], + 'winningBids': [], + 'timeout': 3000 + }; + + const MOCK_BID_REQUESTED_APPNEXUS = { + 'bidder': 'appnexus', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa', + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'appnexus', + 'adapterCode': 'appnexus', + 'bidderCode': 'appnexus', + 'params': { + 'publisherId': '1001', + 'video': { + 'minduration': 30, + 'skippable': true + } + }, + 'mediaType': 'video', + 'adUnitCode': '/19968336/header-bid-tag-0', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ], + 'auctionStart': 1519149536560, + 'timeout': 5000, + 'start': 1519149562216, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'gdprConsent': { + 'consentString': 'here-goes-gdpr-consent-string', + 'gdprApplies': true + } + }; + + this.timeout(5000) + + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [APPNEXUS_BID] + }); + + events.emit(AUCTION_INIT, MOCK_AUCTION_INIT_APPNEXUS); + events.emit(BID_REQUESTED, MOCK_BID_REQUESTED_APPNEXUS); + events.emit(BID_RESPONSE, APPNEXUS_BID); + events.emit(BIDDER_DONE, { + 'bidderCode': 'appnexus', + 'bids': [ + APPNEXUS_BID, + Object.assign({}, APPNEXUS_BID, { + 'serverResponseTimeMs': 42, + }) + ] + }); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, { + [APPNEXUS_BID.adUnitCode]: APPNEXUS_BID.adserverTargeting, + }); + events.emit(BID_WON, Object.assign({}, APPNEXUS_BID, { + 'status': 'rendered' + })); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(2); // logger as well as tracker is fired + let request = requests[1]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.pubid).to.equal('9999'); + expect(data.pid).to.equal('1111'); + expect(data.pdvid).to.equal('20'); + + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.pubid).to.equal('9999'); + expect(decodeURIComponent(data.purl)).to.equal('http://www.test.com/page.html'); + + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(1); + expect(data.s[0].ps[0].pn).to.equal('appnexus'); + expect(data.s[0].ps[0].bc).to.equal('appnexus'); + }) + }); + describe('when handling events', function() { beforeEach(function () { pubmaticAnalyticsAdapter.enableAnalytics({ @@ -374,14 +576,14 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); + expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); - expect(data.s[0].ps.length).to.equal(1); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -394,8 +596,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].en).to.equal(1.23); expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(1); expect(data.s[0].ps[0].t).to.equal(0); @@ -407,10 +609,10 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -429,7 +631,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -467,7 +669,7 @@ describe('pubmatic analytics adapter', function () { expect(data.af).to.equal('video'); }); - it('Logger: do not log floor fields when prebids floor shows noData in location property', function() { + it('Logger : do not log floor fields when prebids floor shows noData in location property', function() { const BID_REQUESTED_COPY = utils.deepClone(MOCK.BID_REQUESTED); BID_REQUESTED_COPY['bids'][1]['floorData']['location'] = 'noData'; @@ -591,13 +793,13 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].sid).not.to.be.undefined; - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); + expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); @@ -671,7 +873,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].sz).to.deep.equal(['640x480']); expect(data.s[0].ps).to.be.an('array'); expect(data.s[0].ps.length).to.equal(1); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); @@ -715,11 +917,11 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); @@ -803,10 +1005,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -823,8 +1025,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(0); - expect(data.s[0].ps[0].ol1).to.equal(0); + expect(data.s[0].ps[0].l1).to.equal(0); + expect(data.s[0].ps[0].ol1).to.equal(0); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(1); @@ -869,7 +1071,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[1].ps[0].bc).to.equal('pubmatic'); @@ -885,7 +1087,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -917,10 +1119,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -938,7 +1140,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -980,7 +1182,7 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -997,7 +1199,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1035,10 +1237,10 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].sid).not.to.be.undefined; - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); @@ -1056,7 +1258,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1096,7 +1298,7 @@ describe('pubmatic analytics adapter', function () { let data = getLoggerJsonFromRequest(request.requestBody); expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1113,7 +1315,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1155,11 +1357,11 @@ describe('pubmatic analytics adapter', function () { // Testing only for rejected bid as other scenarios will be covered under other TCs expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1177,7 +1379,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1234,13 +1436,13 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); - expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); @@ -1255,7 +1457,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1268,11 +1470,11 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].fskp).to.equal(0); - expect(data.s[1].ffs).to.equal(1); - expect(data.s[1].fsrc).to.equal(2); - expect(data.s[1].fp).to.equal('pubmatic'); + expect(data.s[1].ffs).to.equal(1); + expect(data.s[1].fsrc).to.equal(2); + expect(data.s[1].fp).to.equal('pubmatic'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1289,8 +1491,8 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].dc).to.equal('PMP'); expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); - expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].l1).to.equal(944); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); @@ -1364,13 +1566,13 @@ describe('pubmatic analytics adapter', function () { // slot 1 expect(data.s[0].sn).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].fskp).to.equal(0); - expect(data.s[0].ffs).to.equal(1); - expect(data.s[0].fsrc).to.equal(2); - expect(data.s[0].fp).to.equal('pubmatic'); + expect(data.s[0].ffs).to.equal(1); + expect(data.s[0].fsrc).to.equal(2); + expect(data.s[0].fp).to.equal('pubmatic'); expect(data.s[0].sz).to.deep.equal(['640x480']); - expect(data.s[0].sid).not.to.be.undefined; + expect(data.s[0].sid).not.to.be.undefined; expect(data.s[0].ps).to.be.an('array'); - expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); + expect(data.s[0].au).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps.length).to.equal(1); expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('groupm'); @@ -1385,7 +1587,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].di).to.equal('-1'); expect(data.s[0].ps[0].dc).to.equal(''); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[0].ps[0].l2).to.equal(0); expect(data.s[0].ps[0].ss).to.equal(0); expect(data.s[0].ps[0].t).to.equal(0); @@ -1398,7 +1600,7 @@ describe('pubmatic analytics adapter', function () { // slot 2 expect(data.s[1].sn).to.equal('/19968336/header-bid-tag-1'); expect(data.s[1].sz).to.deep.equal(['1000x300', '970x250', '728x90']); - expect(data.s[1].sid).not.to.be.undefined; + expect(data.s[1].sid).not.to.be.undefined; expect(data.s[1].ps).to.be.an('array'); expect(data.s[1].ps.length).to.equal(1); expect(data.s[1].ps[0].pn).to.equal('pubmatic'); @@ -1416,7 +1618,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].mi).to.equal('matched-impression'); expect(data.s[1].ps[0].adv).to.equal('example.com'); expect(data.s[0].ps[0].l1).to.equal(944); - expect(data.s[0].ps[0].ol1).to.equal(3214); + expect(data.s[0].ps[0].ol1).to.equal(3214); expect(data.s[1].ps[0].l2).to.equal(0); expect(data.s[1].ps[0].ss).to.equal(1); expect(data.s[1].ps[0].t).to.equal(0); From 60e7e96769e03aa1f900385dd757fe2822fcc360 Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Fri, 5 Jan 2024 04:22:06 +0800 Subject: [PATCH 313/760] Discovery Bid Adapter: update buyerId, add ssppid & pmguid, add getUserSyncs (#10800) --- modules/discoveryBidAdapter.js | 62 ++++++++++---- test/spec/modules/discoveryBidAdapter_spec.js | 82 ++++++++++++++++++- 2 files changed, 126 insertions(+), 18 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 816ec3cbe1f..315df48fa5d 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; const BIDDER_CODE = 'discovery'; const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; const TIME_TO_LIVE = 500; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); let globals = {}; let itemMaps = {}; const MEDIATYPE = [BANNER, NATIVE]; @@ -14,6 +14,9 @@ const MEDIATYPE = [BANNER, NATIVE]; /* ----- _ss_pp_id:start ------ */ const COOKIE_KEY_SSPPID = '_ss_pp_id'; const COOKIE_KEY_MGUID = '__mguid_'; +const COOKIE_KEY_PMGUID = '__pmguid_'; +const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year +const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; const NATIVERET = { id: 'id', @@ -55,24 +58,22 @@ const NATIVERET = { }; /** - * 获取用户id + * 获取并生成用户的id * @return {string} */ -const getUserID = () => { - let idd = storage.getCookie(COOKIE_KEY_SSPPID); - let idm = storage.getCookie(COOKIE_KEY_MGUID); - - if (idd && !idm) { - idm = idd; - } else if (idm && !idd) { - idd = idm; - } else if (!idd && !idm) { - const uuid = utils.generateUUID(); - storage.setCookie(COOKIE_KEY_MGUID, uuid); - storage.setCookie(COOKIE_KEY_SSPPID, uuid); - return uuid; +export const getPmgUID = () => { + if (!storage.cookiesAreEnabled()) return; + + let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); + if (!pmgUid) { + pmgUid = utils.generateUUID(); + const date = new Date(); + date.setTime(date.getTime() + COOKIE_RETENTION_TIME); + try { + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, date.toUTCString()); + } catch (e) {} } - return idd; + return pmgUid; }; /* ----- _ss_pp_id:end ------ */ @@ -381,9 +382,11 @@ function getParam(validBidRequests, bidderRequest) { ext: { eids, firstPartyData, + ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, + pmguid: getPmgUID(), }, user: { - buyeruid: getUserID(), + buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, id: sharedid || pubcid, }, tmax: timeout, @@ -547,6 +550,31 @@ export const spec = { return bidResponses; }, + getUserSyncs: function (syncOptions, serverResponse, gdprConsent, uspConsent, gppConsent) { + const origin = encodeURIComponent(location.origin || `https://${location.host}`); + let syncParamUrl = `dm=${origin}`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + syncParamUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + syncParamUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + syncParamUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (syncOptions.iframeEnabled) { + return [ + { + type: 'iframe', + url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` + } + ]; + } + }, + /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 8957f8bbd40..acfc519bef9 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,5 +1,6 @@ import { expect } from 'chai'; -import { spec } from 'modules/discoveryBidAdapter.js'; +import { spec, getPmgUID, storage } from 'modules/discoveryBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { let bidRequestData = { @@ -98,6 +99,47 @@ describe('discovery:BidAdapterTests', function () { expect(req_data.imp).to.have.lengthOf(1); }); + describe('discovery: buildRequests', function() { + describe('getPmgUID function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(utils, 'generateUUID').returns('new-uuid'); + sandbox.stub(storage, 'cookiesAreEnabled'); + }) + + afterEach(() => { + sandbox.restore(); + }); + + it('should generate new UUID and set cookie if not exists', () => { + storage.cookiesAreEnabled.callsFake(() => true); + storage.getCookie.callsFake(() => null); + const uid = getPmgUID(); + expect(uid).to.equal('new-uuid'); + expect(storage.setCookie.calledOnce).to.be.true; + }); + + it('should return existing UUID from cookie', () => { + storage.cookiesAreEnabled.callsFake(() => true); + storage.getCookie.callsFake(() => 'existing-uuid'); + const uid = getPmgUID(); + expect(uid).to.equal('existing-uuid'); + expect(storage.setCookie.called).to.be.false; + }); + + it('should not set new UUID when cookies are not enabled', () => { + storage.cookiesAreEnabled.callsFake(() => false); + storage.getCookie.callsFake(() => null); + getPmgUID(); + expect(storage.setCookie.calledOnce).to.be.false; + }); + }) + }); + it('discovery:validate_response_params', function () { let tempAdm = '' tempAdm += '%3Cscr'; @@ -137,4 +179,42 @@ describe('discovery:BidAdapterTests', function () { expect(bid.height).to.equal(250); expect(bid.currency).to.equal('USD'); }); + + describe('discovery: getUserSyncs', function() { + const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; + const IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }; + const IFRAME_DISABLED = { + iframeEnabled: false, + pixelEnabled: false, + }; + const GDPR_CONSENT = { + consentString: 'gdprConsentString', + gdprApplies: true + }; + const USP_CONSENT = { + consentString: 'uspConsentString' + } + + let syncParamUrl = `dm=${encodeURIComponent(location.origin || `https://${location.host}`)}`; + syncParamUrl += '&gdpr=1&gdpr_consent=gdprConsentString&ccpa_consent=uspConsentString'; + const expectedIframeSyncs = [ + { + type: 'iframe', + url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` + } + ]; + + it('should return nothing if iframe is disabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_DISABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.be.undefined; + }); + + it('should do userSyncs if iframe is enabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_ENABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.deep.equal(expectedIframeSyncs); + }); + }); }); From cdbe68c3a0d3c4c5abfca86a927bb981fd2c9797 Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:45:23 +0100 Subject: [PATCH 314/760] ID5 User Id module - pass gpp string and sid in getId request (#10899) --- modules/id5IdSystem.js | 12 +++++++++--- test/spec/modules/id5IdSystem_spec.js | 24 +++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index e12aea5f8d1..5ed12d8def2 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -19,7 +19,7 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; -import {uspDataHandler} from '../src/adapterManager.js'; +import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; const MODULE_NAME = 'id5Id'; @@ -118,7 +118,7 @@ export const id5IdSubmodule = { } const resp = function (cbFunction) { - new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData()).execute() + new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()).execute() .then(response => { cbFunction(response) }) @@ -170,11 +170,12 @@ export const id5IdSubmodule = { }; class IdFetchFlow { - constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData) { + constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData, gppData) { this.submoduleConfig = submoduleConfig this.gdprConsentData = gdprConsentData this.cacheIdObj = cacheIdObj this.usPrivacyData = usPrivacyData + this.gppData = gppData } execute() { @@ -285,6 +286,11 @@ class IdFetchFlow { if (this.usPrivacyData !== undefined && !isEmpty(this.usPrivacyData) && !isEmptyStr(this.usPrivacyData)) { data.us_privacy = this.usPrivacyData; } + if (this.gppData) { + data.gpp_string = this.gppData.gppString; + data.gpp_sid = this.gppData.applicableSections; + } + if (signature !== undefined && !isEmptyStr(signature)) { data.s = signature; } diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 56b23ba9634..d3d91db010d 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -15,7 +15,7 @@ import {config} from 'src/config.js'; import * as events from 'src/events.js'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils.js'; -import {uspDataHandler} from 'src/adapterManager.js'; +import {uspDataHandler, gppDataHandler} from 'src/adapterManager.js'; import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; @@ -259,12 +259,14 @@ describe('ID5 ID System', function () { describe('Xhr Requests from getId()', function () { const responseHeader = HEADERS_CONTENT_TYPE_JSON + let gppStub beforeEach(function () { }); afterEach(function () { uspDataHandler.reset() + gppStub?.restore() }); it('should call the ID5 server and handle a valid response', function () { @@ -730,6 +732,26 @@ describe('ID5 ID System', function () { }); }); + it('should pass gpp_string and gpp_sid to ID5 server', function () { + let xhrServerMock = new XhrServerMock(server) + gppStub = sinon.stub(gppDataHandler, 'getConsentData'); + gppStub.returns({ + ready: true, + gppString: 'GPP_STRING', + applicableSections: [2] + }); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.gpp_string).is.equal('GPP_STRING'); + expect(requestBody.gpp_sid).contains(2); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse + }); + }); + describe('when legacy cookies are set', () => { let sandbox; beforeEach(() => { From 7f2763d58229d66ec25bc347924fbf495cecf5a1 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Sat, 6 Jan 2024 03:52:51 +1100 Subject: [PATCH 315/760] Adnuntius Bid Adapter : bugfix void au ids (#10890) * Removed linting issues * Fixed merge issues. * Bugfix on storageTool. * Adnuntius Bid Adapter: bugfix handling ad response Handle no voidAuIds better. --------- Co-authored-by: Mikael Lundin Co-authored-by: Mikael Lundin --- modules/adnuntiusBidAdapter.js | 4 ++-- test/spec/modules/adnuntiusBidAdapter_spec.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 6f3ed6eb5b6..a498d056513 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -63,7 +63,7 @@ const storageTool = (function () { } const updateVoidAuIds = function (currentVoidAuIds, auIdsAsString) { - const newAuIds = auIdsAsString ? auIdsAsString.split(';') : []; + const newAuIds = isStr(auIdsAsString) ? auIdsAsString.split(';') : []; const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => { return newAuIds.indexOf(auIdObj.value) < -1; }) || []; @@ -83,7 +83,7 @@ const storageTool = (function () { } } } - const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiResponse.voidAuIds || []); + const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiResponse.voidAuIds); if (currentAuIds.length > 0) { metaAsObj.voidAuIds = { value: currentAuIds }; } diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index e4902ca11b2..e109ca1829c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -909,6 +909,7 @@ describe('adnuntiusBidAdapter', function() { ] }; serverResponse.body.adUnits[0].deals = []; + delete serverResponse.body.metaData.voidAuIds; // test response with no voidAuIds const interpretedResponse = spec.interpretResponse(serverResponse, altBidder); expect(interpretedResponse).to.have.lengthOf(0); From 3e6b546efe38ee91eca3475710a9559944d11c9d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Fri, 5 Jan 2024 21:03:56 +0100 Subject: [PATCH 316/760] GreenbidsAnalyticsAdapter and GreenbidsRtdProvider: Rework greenbids sampling and improve transparency (#10792) * track billing events and modify sampling pattern * review updates --- modules/greenbidsAnalyticsAdapter.js | 110 +++++++--- modules/greenbidsAnalyticsAdapter.md | 37 ++-- modules/greenbidsRtdProvider.js | 72 +++++-- modules/greenbidsRtdProvider.md | 2 +- .../modules/greenbidsAnalyticsAdapter_spec.js | 137 ++++++------- .../spec/modules/greenbidsRtdProvider_spec.js | 188 ++++++++++++++++-- 6 files changed, 401 insertions(+), 145 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 2189172e16f..5d1f35f24ff 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -2,18 +2,20 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager.js'; -import {deepClone, logError, logInfo} from '../src/utils.js'; +import {deepClone, generateUUID, logError, logInfo, logWarn} from '../src/utils.js'; const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '1.0.0'; +export const ANALYTICS_VERSION = '2.0.0'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; const { EVENTS: { + AUCTION_INIT, AUCTION_END, BID_TIMEOUT, + BILLABLE_EVENT, } } = CONSTANTS; @@ -25,28 +27,53 @@ export const BIDDER_STATUS = { const analyticsOptions = {}; -export const parseBidderCode = function (bid) { - let bidderCode = bid.bidderCode || bid.bidder; - return bidderCode.toLowerCase(); -}; +export const isSampled = function(greenbidsId, samplingRate) { + if (samplingRate < 0 || samplingRate > 1) { + logWarn('Sampling rate must be between 0 and 1'); + return true; + } + const hashInt = parseInt(greenbidsId.slice(-4), 16); + + return hashInt < samplingRate * (0xFFFF + 1); +} export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), { cachedAuctions: {}, initConfig(config) { + analyticsOptions.options = deepClone(config.options); /** * Required option: pbuid * @type {boolean} */ - analyticsOptions.options = deepClone(config.options); - if (typeof config.options.pbuid !== 'string' || config.options.pbuid.length < 1) { + if (typeof analyticsOptions.options.pbuid !== 'string' || analyticsOptions.options.pbuid.length < 1) { logError('"options.pbuid" is required.'); return false; } + /** + * Deprecate use of integerated 'sampling' config + * replace by greenbidsSampling + */ + if (typeof analyticsOptions.options.sampling === 'number') { + logWarn('"options.sampling" is deprecated, please use "greenbidsSampling" instead.'); + analyticsOptions.options.greenbidsSampling = analyticsOptions.options.sampling; + // Set sampling to null to prevent prebid analytics integrated sampling to happen + analyticsOptions.options.sampling = null; + } + + /** + * Discourage unsampled analytics + */ + if (typeof analyticsOptions.options.greenbidsSampling !== 'number' || analyticsOptions.options.greenbidsSampling >= 1) { + logWarn('"options.greenbidsSampling" is not set or >=1, using this analytics module unsampled is discouraged.'); + analyticsOptions.options.greenbidsSampling = 1; + } + analyticsOptions.pbuid = config.options.pbuid analyticsOptions.server = ANALYTICS_SERVER; + return true; }, sendEventMessage(endPoint, data) { @@ -57,13 +84,16 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER }); }, createCommonMessage(auctionId) { + const cachedAuction = this.getCachedAuction(auctionId); return { version: ANALYTICS_VERSION, auctionId: auctionId, referrer: window.location.href, - sampling: analyticsOptions.options.sampling, + sampling: analyticsOptions.options.greenbidsSampling, prebid: '$prebid.version$', + greenbidsId: cachedAuction.greenbidsId, pbuid: analyticsOptions.pbuid, + billingId: cachedAuction.billingId, adUnits: [], }; }, @@ -96,21 +126,22 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER } } }, - createBidMessage(auctionEndArgs, timeoutBids) { - logInfo(auctionEndArgs) + createBidMessage(auctionEndArgs) { const {auctionId, timestamp, auctionEnd, adUnits, bidsReceived, noBids} = auctionEndArgs; + const cachedAuction = this.getCachedAuction(auctionId); const message = this.createCommonMessage(auctionId); + const timeoutBids = cachedAuction.timeoutBids || []; message.auctionElapsed = (auctionEnd - timestamp); adUnits.forEach((adUnit) => { - const adUnitCode = adUnit.code.toLowerCase(); + const adUnitCode = adUnit.code?.toLowerCase() || 'unknown_adunit_code'; message.adUnits.push({ code: adUnitCode, mediaTypes: { - ...(adUnit.mediaTypes.banner !== undefined) && {banner: adUnit.mediaTypes.banner}, - ...(adUnit.mediaTypes.video !== undefined) && {video: adUnit.mediaTypes.video}, - ...(adUnit.mediaTypes.native !== undefined) && {native: adUnit.mediaTypes.native} + ...(adUnit.mediaTypes?.banner !== undefined) && {banner: adUnit.mediaTypes.banner}, + ...(adUnit.mediaTypes?.video !== undefined) && {video: adUnit.mediaTypes.video}, + ...(adUnit.mediaTypes?.native !== undefined) && {native: adUnit.mediaTypes.native} }, ortb2Imp: adUnit.ortb2Imp || {}, bidders: [], @@ -130,13 +161,26 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER getCachedAuction(auctionId) { this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { timeoutBids: [], + greenbidsId: null, + billingId: null, + isSampled: true, }; return this.cachedAuctions[auctionId]; }, + handleAuctionInit(auctionInitArgs) { + const cachedAuction = this.getCachedAuction(auctionInitArgs.auctionId); + try { + cachedAuction.greenbidsId = auctionInitArgs.adUnits[0].ortb2Imp.ext.greenbids.greenbidsId; + } catch (e) { + logInfo("Couldn't find Greenbids RTD info, assuming analytics only"); + cachedAuction.greenbidsId = generateUUID(); + } + cachedAuction.isSampled = isSampled(cachedAuction.greenbidsId, analyticsOptions.options.greenbidsSampling); + }, handleAuctionEnd(auctionEndArgs) { const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); this.sendEventMessage('/', - this.createBidMessage(auctionEndArgs, cachedAuction.timeoutBids) + this.createBidMessage(auctionEndArgs, cachedAuction) ); }, handleBidTimeout(timeoutBids) { @@ -145,14 +189,34 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER cachedAuction.timeoutBids.push(bid); }); }, + handleBillable(billableArgs) { + const cachedAuction = this.getCachedAuction(billableArgs.auctionId); + /* Filter Greenbids Billable Events only */ + if (billableArgs.vendor === 'greenbidsRtdProvider') { + cachedAuction.billingId = billableArgs.billingId || 'unknown_billing_id'; + } + }, track({eventType, args}) { - switch (eventType) { - case BID_TIMEOUT: - this.handleBidTimeout(args); - break; - case AUCTION_END: - this.handleAuctionEnd(args); - break; + try { + if (eventType === AUCTION_INIT) { + this.handleAuctionInit(args); + } + + if (this.getCachedAuction(args?.auctionId)?.isSampled ?? true) { + switch (eventType) { + case BID_TIMEOUT: + this.handleBidTimeout(args); + break; + case AUCTION_END: + this.handleAuctionEnd(args); + break; + case BILLABLE_EVENT: + this.handleBillable(args); + break; + } + } + } catch (e) { + logWarn('There was an error handling event ' + eventType); } }, getAnalyticsOptions() { diff --git a/modules/greenbidsAnalyticsAdapter.md b/modules/greenbidsAnalyticsAdapter.md index 46e3af2c5e2..1be2c1741ed 100644 --- a/modules/greenbidsAnalyticsAdapter.md +++ b/modules/greenbidsAnalyticsAdapter.md @@ -1,23 +1,24 @@ -# Overview +#### Registration -``` -Module Name: Greenbids Analytics Adapter -Module Type: Analytics Adapter -Maintainer: jb@greenbids.ai -``` +The Greenbids Analytics adapter requires setup and approval from the +Greenbids team. Please reach out to our team for more information [greenbids.ai](https://greenbids.ai). -# Description +#### Analytics Options -Analytics adapter for Greenbids +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +|-------------|---------|--------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|------------------| +| pbuid | required | The Greenbids Publisher ID | greenbids-publisher-1 | string | +| greenbidsSampling | optional | sampling factor [0-1] (a value of 0.1 will filter 90% of the traffic) | 1.0 | float | -# Test Parameters +### Example Configuration -``` -{ - provider: 'greenbids', - options: { - pbuid: "PBUID_FROM_GREENBIDS" - sampling: 1.0 - } -} -``` +```javascript + pbjs.enableAnalytics({ + provider: 'greenbids', + options: { + pbuid: "greenbids-publisher-1" // please contact Greenbids to get a pbuid for yourself + greenbidsSampling: 1.0 + } + }); +``` \ No newline at end of file diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js index b3d79f05996..7fcd163a7c2 100644 --- a/modules/greenbidsRtdProvider.js +++ b/modules/greenbidsRtdProvider.js @@ -1,12 +1,13 @@ -import { logError } from '../src/utils.js'; +import { logError, deepClone, generateUUID, deepSetValue, deepAccess } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; const MODULE_NAME = 'greenbidsRtdProvider'; -const MODULE_VERSION = '1.0.0'; +const MODULE_VERSION = '2.0.0'; const ENDPOINT = 'https://t.greenbids.ai'; -const auctionInfo = {}; const rtdOptions = {}; function init(moduleConfig) { @@ -16,22 +17,33 @@ function init(moduleConfig) { return false; } else { rtdOptions.pbuid = params?.pbuid; - rtdOptions.targetTPR = params?.targetTPR || 0.99; rtdOptions.timeout = params?.timeout || 200; return true; } } function onAuctionInitEvent(auctionDetails) { - auctionInfo.auctionId = auctionDetails.auctionId; + /* Emitting one billing event per auction */ + let defaultId = 'default_id'; + let greenbidsId = deepAccess(auctionDetails.adUnits[0], 'ortb2Imp.ext.greenbids.greenbidsId', defaultId); + /* greenbids was successfully called so we emit the event */ + if (greenbidsId !== defaultId) { + events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + type: 'auction', + billingId: generateUUID(), + auctionId: auctionDetails.auctionId, + vendor: MODULE_NAME + }); + } } function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { - let promise = createPromise(reqBidsConfigObj); + let greenbidsId = generateUUID(); + let promise = createPromise(reqBidsConfigObj, greenbidsId); promise.then(callback); } -function createPromise(reqBidsConfigObj) { +function createPromise(reqBidsConfigObj, greenbidsId) { return new Promise((resolve) => { const timeoutId = setTimeout(() => { resolve(reqBidsConfigObj); @@ -40,7 +52,7 @@ function createPromise(reqBidsConfigObj) { ENDPOINT, { success: (response) => { - processSuccessResponse(response, timeoutId, reqBidsConfigObj); + processSuccessResponse(response, timeoutId, reqBidsConfigObj, greenbidsId); resolve(reqBidsConfigObj); }, error: () => { @@ -48,24 +60,35 @@ function createPromise(reqBidsConfigObj) { resolve(reqBidsConfigObj); }, }, - createPayload(reqBidsConfigObj), - { contentType: 'application/json' } + createPayload(reqBidsConfigObj, greenbidsId), + { + contentType: 'application/json', + customHeaders: { + 'Greenbids-Pbuid': rtdOptions.pbuid + } + } ); }); } -function processSuccessResponse(response, timeoutId, reqBidsConfigObj) { +function processSuccessResponse(response, timeoutId, reqBidsConfigObj, greenbidsId) { clearTimeout(timeoutId); const responseAdUnits = JSON.parse(response); - - updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits); + updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits, greenbidsId); } -function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits) { +function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { adUnits.forEach((adUnit) => { const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); if (matchingAdUnit) { - removeFalseBidders(adUnit, matchingAdUnit); + deepSetValue(adUnit, 'ortb2Imp.ext.greenbids', { + greenbidsId: greenbidsId, + keptInAuction: matchingAdUnit.bidders, + isExploration: matchingAdUnit.isExploration + }); + if (!matchingAdUnit.isExploration) { + removeFalseBidders(adUnit, matchingAdUnit); + } } }); } @@ -85,14 +108,24 @@ function getFalseBidders(bidders) { .map(([bidder]) => bidder); } -function createPayload(reqBidsConfigObj) { +function stripAdUnits(adUnits) { + const stripedAdUnits = deepClone(adUnits); + return stripedAdUnits.map(adUnit => { + adUnit.bids = adUnit.bids.map(bid => { + return { bidder: bid.bidder }; + }); + return adUnit; + }); +} + +function createPayload(reqBidsConfigObj, greenbidsId) { return JSON.stringify({ - auctionId: auctionInfo.auctionId, version: MODULE_VERSION, + ...rtdOptions, referrer: window.location.href, prebid: '$prebid.version$', - rtdOptions: rtdOptions, - adUnits: reqBidsConfigObj.adUnits, + greenbidsId: greenbidsId, + adUnits: stripAdUnits(reqBidsConfigObj.adUnits), }); } @@ -105,6 +138,7 @@ export const greenbidsSubmodule = { findMatchingAdUnit: findMatchingAdUnit, removeFalseBidders: removeFalseBidders, getFalseBidders: getFalseBidders, + stripAdUnits: stripAdUnits, }; submodule('realTimeData', greenbidsSubmodule); diff --git a/modules/greenbidsRtdProvider.md b/modules/greenbidsRtdProvider.md index 85b8f5a7859..ab8105a4537 100644 --- a/modules/greenbidsRtdProvider.md +++ b/modules/greenbidsRtdProvider.md @@ -2,6 +2,7 @@ ``` Module Name: Greenbids RTD Provider +Module Version: 2.0.0 Module Type: RTD Provider Maintainer: jb@greenbids.ai ``` @@ -21,7 +22,6 @@ This module is configured as part of the `realTimeData.dataProviders` object. | `waitForIt ` | required (mandatory true value) | Tells prebid auction to wait for the result of this module | `'true'` | `boolean` | | `params` | required | | | `Object` | | `params.pbuid` | required | The client site id provided by Greenbids. | `'TEST_FROM_GREENBIDS'` | `string` | -| `params.targetTPR` | optional (default 0.95) | Target True positive rate for the throttling model | `0.99` | `[0-1]` | | `params.timeout` | optional (default 200) | Maximum amount of milliseconds allowed for module to finish working (has to be <= to the realTimeData.auctionDelay property) | `200` | `number` | #### Example diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 3cfdc9b9749..30361ca5661 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -1,8 +1,11 @@ import { - greenbidsAnalyticsAdapter, parseBidderCode, + greenbidsAnalyticsAdapter, + isSampled, ANALYTICS_VERSION, BIDDER_STATUS } from 'modules/greenbidsAnalyticsAdapter.js'; - +import { + generateUUID, +} from '../../../src/utils.js'; import {expect} from 'chai'; import sinon from 'sinon'; @@ -13,11 +16,42 @@ const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; describe('Greenbids Prebid AnalyticsAdapter Testing', function () { + describe('enableAnalytics and config parser', function () { + const configOptions = { + pbuid: pbuid, + greenbidsSampling: 1, + }; + beforeEach(function () { + greenbidsAnalyticsAdapter.enableAnalytics({ + provider: 'greenbidsAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + greenbidsAnalyticsAdapter.disableAnalytics(); + }); + + it('should parse config correctly with optional values', function () { + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); + expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().pbuid).to.equal(configOptions.pbuid); + }); + + it('should not enable Analytics when pbuid is missing', function () { + const configOptions = { + options: { + } + }; + const validConfig = greenbidsAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + }); + describe('event tracking and message cache manager', function () { beforeEach(function () { const configOptions = { pbuid: pbuid, - sampling: 0, + greenbidsSampling: 1, }; greenbidsAnalyticsAdapter.enableAnalytics({ @@ -30,43 +64,6 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { greenbidsAnalyticsAdapter.disableAnalytics(); }); - describe('#parseBidderCode()', function() { - it('should get lower case bidder code from bidderCode field value', function() { - const receivedBids = [ - { - auctionId: auctionId, - adUnitCode: 'adunit_1', - bidder: 'greenbids', - bidderCode: 'GREENBIDS', - requestId: 'a1b2c3d4', - timeToRespond: 72, - cpm: 0.1, - currency: 'USD', - ad: 'fake ad1' - }, - ]; - const result = parseBidderCode(receivedBids[0]); - expect(result).to.equal('greenbids'); - }); - it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { - const receivedBids = [ - { - auctionId: auctionId, - adUnitCode: 'adunit_1', - bidder: 'greenbids', - bidderCode: '', - requestId: 'a1b2c3d4', - timeToRespond: 72, - cpm: 0.1, - currency: 'USD', - ad: 'fake ad1' - }, - ]; - const result = parseBidderCode(receivedBids[0]); - expect(result).to.equal('greenbids'); - }); - }); - describe('#getCachedAuction()', function() { const existing = {timeoutBids: [{}]}; greenbidsAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; @@ -146,7 +143,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { auctionId: auctionId, pbuid: pbuid, referrer: window.location.href, - sampling: 0, + sampling: 1, prebid: '$prebid.version$', }); } @@ -260,7 +257,9 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { noBids: noBids }; + sinon.stub(greenbidsAnalyticsAdapter, 'getCachedAuction').returns({timeoutBids: timeoutBids}); const result = greenbidsAnalyticsAdapter.createBidMessage(args, timeoutBids); + greenbidsAnalyticsAdapter.getCachedAuction.restore(); assertHavingRequiredMessageFields(result); expect(result).to.deep.include({ @@ -330,7 +329,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { timeout: 3000, auctionEnd: 1234567990, bidsReceived: receivedBids, - noBids: noBids + noBids: noBids, }]; greenbidsAnalyticsAdapter.handleBidTimeout(args); @@ -353,7 +352,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { describe('greenbids Analytics Adapter track handler ', function () { const configOptions = { pbuid: pbuid, - sampling: 1, + greenbidsSampling: 1, }; beforeEach(function () { @@ -369,50 +368,52 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { events.getEvents.restore(); }); + it('should call handleAuctionInit as AUCTION_INIT trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionInit'); + events.emit(constants.EVENTS.AUCTION_INIT, {auctionId: 'auctionId'}); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionInit, 1); + greenbidsAnalyticsAdapter.handleAuctionInit.restore(); + }); + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { sinon.spy(greenbidsAnalyticsAdapter, 'handleBidTimeout'); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); + events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: 'auctionId'}); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBidTimeout, 1); greenbidsAnalyticsAdapter.handleBidTimeout.restore(); }); it('should call handleAuctionEnd as AUCTION_END trigger event', function() { sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionEnd'); - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(constants.EVENTS.AUCTION_END, {auctionId: 'auctionId'}); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionEnd, 1); greenbidsAnalyticsAdapter.handleAuctionEnd.restore(); }); - }); - - describe('enableAnalytics and config parser', function () { - const configOptions = { - pbuid: pbuid, - sampling: 0, - }; - beforeEach(function () { - greenbidsAnalyticsAdapter.enableAnalytics({ - provider: 'greenbidsAnalytics', - options: configOptions + it('should call handleBillable as BILLABLE_EVENT trigger event', function() { + sinon.spy(greenbidsAnalyticsAdapter, 'handleBillable'); + events.emit(constants.EVENTS.BILLABLE_EVENT, { + type: 'auction', + billingId: generateUUID(), + auctionId: 'auctionId', + vendor: 'greenbidsRtdProvider' }); + sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBillable, 1); + greenbidsAnalyticsAdapter.handleBillable.restore(); }); + }); - afterEach(function () { - greenbidsAnalyticsAdapter.disableAnalytics(); + describe('isSampled', function() { + it('should return true for invalid sampling rates', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', -1)).to.be.true; + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 1.2)).to.be.true; }); - it('should parse config correctly with optional values', function () { - expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); - expect(greenbidsAnalyticsAdapter.getAnalyticsOptions().pbuid).to.equal(configOptions.pbuid); + it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001)).to.be.false; }); - it('should not enable Analytics when pbuid is missing', function () { - const configOptions = { - options: { - } - }; - const validConfig = greenbidsAnalyticsAdapter.initConfig(configOptions); - expect(validConfig).to.equal(false); + it('should return determinist true value for valid sampling rate given the predifined id and rate', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999)).to.be.true; }); }); }); diff --git a/test/spec/modules/greenbidsRtdProvider_spec.js b/test/spec/modules/greenbidsRtdProvider_spec.js index cd93e9013c0..d0083d4dc7a 100644 --- a/test/spec/modules/greenbidsRtdProvider_spec.js +++ b/test/spec/modules/greenbidsRtdProvider_spec.js @@ -6,7 +6,9 @@ import { import { greenbidsSubmodule } from 'modules/greenbidsRtdProvider.js'; -import {server} from '../../mocks/xhr.js'; +import { server } from '../../mocks/xhr.js'; +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; describe('greenbidsRtdProvider', () => { const endPoint = 't.greenbids.ai'; @@ -39,14 +41,36 @@ describe('greenbidsRtdProvider', () => { }] }; - const SAMPLE_RESPONSE_ADUNITS = [ + const SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED = [ { code: 'adUnit1', bidders: { 'appnexus': true, 'rubicon': false, 'ix': true - } + }, + isExploration: false + }, + { + code: 'adUnit2', + bidders: { + 'appnexus': false, + 'rubicon': true, + 'openx': true + }, + isExploration: false + + }]; + + const SAMPLE_RESPONSE_ADUNITS_EXPLORED = [ + { + code: 'adUnit1', + bidders: { + 'appnexus': true, + 'rubicon': false, + 'ix': true + }, + isExploration: true }, { code: 'adUnit2', @@ -54,7 +78,9 @@ describe('greenbidsRtdProvider', () => { 'appnexus': false, 'rubicon': true, 'openx': true - } + }, + isExploration: true + }]; describe('init', () => { @@ -70,22 +96,37 @@ describe('greenbidsRtdProvider', () => { }); describe('updateAdUnitsBasedOnResponse', () => { - it('should update ad units based on response', () => { + it('should update ad units based on response if not exploring', () => { const adUnits = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits)); - greenbidsSubmodule.updateAdUnitsBasedOnResponse(adUnits, SAMPLE_RESPONSE_ADUNITS); + greenbidsSubmodule.updateAdUnitsBasedOnResponse(adUnits, SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED); expect(adUnits[0].bids).to.have.length(2); expect(adUnits[1].bids).to.have.length(2); }); + + it('should not update ad units based on response if exploring', () => { + const adUnits = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits)); + greenbidsSubmodule.updateAdUnitsBasedOnResponse(adUnits, SAMPLE_RESPONSE_ADUNITS_EXPLORED); + + expect(adUnits[0].bids).to.have.length(3); + expect(adUnits[1].bids).to.have.length(3); + expect(adUnits[0].ortb2Imp.ext.greenbids.greenbidsId).to.be.a.string; + expect(adUnits[1].ortb2Imp.ext.greenbids.greenbidsId).to.be.a.string; + expect(adUnits[0].ortb2Imp.ext.greenbids.greenbidsId).to.equal(adUnits[0].ortb2Imp.ext.greenbids.greenbidsId); + expect(adUnits[0].ortb2Imp.ext.greenbids.keptInAuction).to.deep.equal(SAMPLE_RESPONSE_ADUNITS_EXPLORED[0].bidders); + expect(adUnits[1].ortb2Imp.ext.greenbids.keptInAuction).to.deep.equal(SAMPLE_RESPONSE_ADUNITS_EXPLORED[1].bidders); + expect(adUnits[0].ortb2Imp.ext.greenbids.isExploration).to.equal(SAMPLE_RESPONSE_ADUNITS_EXPLORED[0].isExploration); + expect(adUnits[1].ortb2Imp.ext.greenbids.isExploration).to.equal(SAMPLE_RESPONSE_ADUNITS_EXPLORED[1].isExploration); + }); }); describe('findMatchingAdUnit', () => { it('should find matching ad unit by code', () => { - const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'adUnit1'); - expect(matchingAdUnit).to.deep.equal(SAMPLE_RESPONSE_ADUNITS[0]); + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED, 'adUnit1'); + expect(matchingAdUnit).to.deep.equal(SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED[0]); }); it('should return undefined if no matching ad unit is found', () => { - const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS, 'nonexistent'); + const matchingAdUnit = greenbidsSubmodule.findMatchingAdUnit(SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED, 'nonexistent'); expect(matchingAdUnit).to.be.undefined; }); }); @@ -93,7 +134,7 @@ describe('greenbidsRtdProvider', () => { describe('removeFalseBidders', () => { it('should remove bidders with false value', () => { const adUnit = JSON.parse(JSON.stringify(SAMPLE_REQUEST_BIDS_CONFIG_OBJ.adUnits[0])); - const matchingAdUnit = SAMPLE_RESPONSE_ADUNITS[0]; + const matchingAdUnit = SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED[0]; greenbidsSubmodule.removeFalseBidders(adUnit, matchingAdUnit); expect(adUnit.bids).to.have.length(2); expect(adUnit.bids.map((bid) => bid.bidder)).to.not.include('rubicon'); @@ -125,14 +166,15 @@ describe('greenbidsRtdProvider', () => { setTimeout(() => { server.requests[0].respond( 200, - {'Content-Type': 'application/json'}, - JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + { 'Content-Type': 'application/json' }, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED) ); }, 50); setTimeout(() => { const requestUrl = new URL(server.requests[0].url); expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.greenbidsId).to.be.a.string; expect(requestBids.adUnits[0].bids).to.have.length(2); expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.not.include('rubicon'); expect(requestBids.adUnits[0].bids.map((bid) => bid.bidder)).to.include('ix'); @@ -157,8 +199,8 @@ describe('greenbidsRtdProvider', () => { setTimeout(() => { server.requests[0].respond( 200, - {'Content-Type': 'application/json'}, - JSON.stringify(SAMPLE_RESPONSE_ADUNITS) + { 'Content-Type': 'application/json' }, + JSON.stringify(SAMPLE_RESPONSE_ADUNITS_NOT_EXPLORED) ); done(); }, 300); @@ -166,6 +208,7 @@ describe('greenbidsRtdProvider', () => { setTimeout(() => { const requestUrl = new URL(server.requests[0].url); expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.greenbidsId).to.be.a.string; expect(requestBids.adUnits[0].bids).to.have.length(3); expect(requestBids.adUnits[1].bids).to.have.length(3); expect(callback.calledOnce).to.be.true; @@ -183,14 +226,15 @@ describe('greenbidsRtdProvider', () => { setTimeout(() => { server.requests[0].respond( 500, - {'Content-Type': 'application/json'}, - JSON.stringify({'failure': 'fail'}) + { 'Content-Type': 'application/json' }, + JSON.stringify({ 'failure': 'fail' }) ); }, 50); setTimeout(() => { const requestUrl = new URL(server.requests[0].url); expect(requestUrl.host).to.be.eq(endPoint); + expect(requestBids.greenbidsId).to.be.a.string; expect(requestBids.adUnits[0].bids).to.have.length(3); expect(requestBids.adUnits[1].bids).to.have.length(3); expect(callback.calledOnce).to.be.true; @@ -198,4 +242,116 @@ describe('greenbidsRtdProvider', () => { }, 60); }); }); + + describe('stripAdUnits', function () { + it('should strip all properties except bidder from each bid in adUnits', function () { + const adUnits = + [ + { + bids: [ + { bidder: 'bidder1', otherProp: 'value1' }, + { bidder: 'bidder2', otherProp: 'value2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } } + } + ]; + const expectedOutput = [ + { + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } } + } + ]; + + // Perform the test + const output = greenbidsSubmodule.stripAdUnits(adUnits); + expect(output).to.deep.equal(expectedOutput); + }); + + it('should strip all properties except bidder from each bid in adUnits but keep ortb2Imp', function () { + const adUnits = + [ + { + bids: [ + { bidder: 'bidder1', otherProp: 'value1' }, + { bidder: 'bidder2', otherProp: 'value2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } }, + ortb2Imp: { + ext: { + greenbids: { + greenbidsId: 'test' + } + } + } + } + ]; + const expectedOutput = [ + { + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } }, + ortb2Imp: { + ext: { + greenbids: { + greenbidsId: 'test' + } + } + } + } + ]; + + // Perform the test + const output = greenbidsSubmodule.stripAdUnits(adUnits); + expect(output).to.deep.equal(expectedOutput); + }); + }); + + describe('onAuctionInitEvent', function () { + it('should not emit billable event if greenbids hasn\'t set the adunit.ext value', function () { + sinon.spy(events, 'emit'); + greenbidsSubmodule.onAuctionInitEvent({ + auctionId: 'test', + adUnits: [ + { + bids: [ + { bidder: 'bidder1', otherProp: 'value1' }, + { bidder: 'bidder2', otherProp: 'value2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } }, + } + ] + }); + sinon.assert.callCount(events.emit, 0); + events.emit.restore(); + }); + + it('should emit billable event if greenbids has set the adunit.ext value', function (done) { + let counter = 0; + events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { + if (event.vendor === 'greenbidsRtdProvider' && event.type === 'auction') { + counter += 1; + } + expect(counter).to.equal(1); + done(); + }); + greenbidsSubmodule.onAuctionInitEvent({ + auctionId: 'test', + adUnits: [ + { + bids: [ + { bidder: 'bidder1', otherProp: 'value1' }, + { bidder: 'bidder2', otherProp: 'value2' } + ], + mediaTypes: { 'banner': { prop: 'value3' } }, + ortb2Imp: { ext: { greenbids: { greenbidsId: 'b0b39610-b941-4659-a87c-de9f62d3e13e' } } } + } + ] + }); + }); + }); }); From d390f6c3d84e65f0d4a5d60020acc7b9201be9b1 Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Sat, 6 Jan 2024 20:18:51 +0300 Subject: [PATCH 317/760] Core: add eslint-plugin-jsdoc (#10893) --- .eslintrc.js | 35 +++- modules/adagioAnalyticsAdapter.js | 24 +-- modules/adagioBidAdapter.js | 10 +- modules/admaticBidAdapter.js | 3 +- modules/adpod.js | 2 +- modules/adqueryBidAdapter.js | 3 +- modules/beopBidAdapter.js | 22 +-- modules/bliinkBidAdapter.js | 4 +- modules/blueconicRtdProvider.js | 6 +- modules/brandmetricsRtdProvider.js | 12 +- modules/bucksenseBidAdapter.js | 12 +- modules/codefuelBidAdapter.js | 42 ++--- modules/criteoBidAdapter.js | 3 +- modules/deltaprojectsBidAdapter.js | 16 +- modules/fledgeForGpt.js | 4 +- modules/freewheel-sspBidAdapter.js | 36 ++--- modules/geoedgeRtdProvider.js | 8 +- modules/getintentBidAdapter.js | 8 +- modules/gjirafaBidAdapter.js | 10 +- modules/goldfishAdsRtdProvider.js | 12 +- modules/gravitoIdSystem.js | 24 +-- modules/idWardRtdProvider.js | 28 ++-- modules/imRtdProvider.js | 4 +- modules/impactifyBidAdapter.js | 4 +- modules/incrxBidAdapter.js | 32 ++-- modules/integr8BidAdapter.js | 10 +- modules/ixBidAdapter.js | 14 +- modules/jwplayerRtdProvider.js | 16 +- modules/lemmaDigitalBidAdapter.js | 76 ++++----- modules/malltvBidAdapter.js | 10 +- modules/mediafilterRtdProvider.js | 4 +- modules/mediasquareBidAdapter.js | 48 +++--- modules/merkleIdSystem.js | 28 ++-- modules/multibid/index.js | 58 +++---- modules/mwOpenLinkIdSystem.js | 26 +-- modules/naveggIdSystem.js | 26 +-- modules/nobidBidAdapter.js | 48 +++--- modules/novatiqIdSystem.js | 24 +-- modules/oneKeyIdSystem.js | 30 ++-- modules/operaadsIdSystem.js | 30 ++-- modules/optidigitalBidAdapter.js | 42 ++--- modules/optimeraRtdProvider.js | 3 +- modules/optimonAnalyticsAdapter.js | 16 +- modules/pairIdSystem.js | 30 ++-- modules/pilotxBidAdapter.js | 30 ++-- modules/prebidServerBidAdapter/index.js | 2 +- modules/priceFloors.js | 4 +- modules/prismaBidAdapter.js | 48 +++--- modules/pubmaticBidAdapter.js | 16 +- modules/relevantdigitalBidAdapter.js | 2 +- modules/sizeMappingV2.js | 2 +- modules/slimcutBidAdapter.js | 30 ++-- modules/smilewantedBidAdapter.js | 6 +- modules/sovrnBidAdapter.js | 2 +- modules/sspBCBidAdapter.js | 2 +- modules/tappxBidAdapter.js | 14 +- modules/teadsIdSystem.js | 28 ++-- modules/temedyaBidAdapter.js | 30 ++-- modules/topicsFpdModule.js | 8 +- modules/ucfunnelBidAdapter.js | 2 +- modules/userId/index.js | 14 +- modules/weboramaRtdProvider.js | 81 ++++++---- modules/xeBidAdapter.js | 22 +-- modules/yandexBidAdapter.js | 2 +- modules/zetaBidAdapter.js | 34 ++-- package-lock.json | 153 ++++++++++++++++++ package.json | 1 + src/adapters/bidderFactory.js | 11 +- src/auction.js | 42 ++--- src/events.js | 5 +- src/targeting.js | 40 ++--- src/userSync.js | 2 +- .../e2e/modules/e2e_consent_mgt_gdpr.spec.js | 4 +- 73 files changed, 881 insertions(+), 659 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fc3ad3afe66..511e78048e4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,12 +11,25 @@ module.exports = { node: { moduleDirectory: ['node_modules', './'] } + }, + 'jsdoc': { + mode: 'typescript', + tagNamePreference: { + 'tag constructor': 'constructor', + extends: 'extends', + method: 'method', + return: 'return', + } } }, - extends: 'standard', + extends: [ + 'standard', + 'plugin:jsdoc/recommended' + ], plugins: [ 'prebid', - 'import' + 'import', + 'jsdoc' ], globals: { 'BROWSERSTACK_USERNAME': false, @@ -46,6 +59,24 @@ module.exports = { 'no-undef': 2, 'no-useless-escape': 'off', 'no-console': 'error', + 'jsdoc/check-types': 'off', + 'jsdoc/newline-after-description': 'off', + 'jsdoc/require-jsdoc': 'off', + 'jsdoc/require-param': 'off', + 'jsdoc/require-param-description': 'off', + 'jsdoc/require-param-name': 'off', + 'jsdoc/require-param-type': 'off', + 'jsdoc/require-property': 'off', + 'jsdoc/require-property-description': 'off', + 'jsdoc/require-property-name': 'off', + 'jsdoc/require-property-type': 'off', + 'jsdoc/require-returns': 'off', + 'jsdoc/require-returns-check': 'off', + 'jsdoc/require-returns-description': 'off', + 'jsdoc/require-returns-type': 'off', + 'jsdoc/require-yields': 'off', + 'jsdoc/require-yields-check': 'off', + 'jsdoc/tag-lines': 'off' }, overrides: Object.keys(allowedModules).map((key) => ({ files: key + '/**/*.js', diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 807937ba3dd..de0aa1cb5d7 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -48,7 +48,7 @@ const enc = window.encodeURIComponent; /** /* BEGIN ADAGIO.JS CODE -*/ + */ function canAccessTopWindow() { try { @@ -71,12 +71,12 @@ const adagioEnqueue = function adagioEnqueue(action, data) { }; /** -* END ADAGIO.JS CODE -*/ + * END ADAGIO.JS CODE + */ /** -* UTILS FUNCTIONS -*/ + * UTILS FUNCTIONS + */ const guard = { adagio: (value) => isAdagio(value), @@ -130,9 +130,9 @@ function addKeyPrefix(obj, prefix) { } /** -* sendRequest to Adagio. It filter null values and encode each query param. -* @param {Object} qp -*/ + * sendRequest to Adagio. It filter null values and encode each query param. + * @param {Object} qp + */ function sendRequest(qp) { // Removing null values qp = Object.keys(qp).reduce((acc, key) => { @@ -164,7 +164,7 @@ function getTargetedAuctionId(bid) { /** * END UTILS FUNCTIONS -*/ + */ /** * HANDLERS @@ -175,7 +175,7 @@ function getTargetedAuctionId(bid) { * - handlerAdRender * * Each handler is called when the event is fired. -*/ + */ function handlerAuctionInit(event) { const w = getCurrentWindow(); @@ -271,7 +271,7 @@ function handlerAuctionInit(event) { * handlerBidResponse allow to track the adagio bid response * and to update the auction cache with the seat ID. * No beacon is sent here. -*/ + */ function handlerBidResponse(event) { if (!guard.adagio(event.bidder)) { return; @@ -372,7 +372,7 @@ function handlerAdRender(event, isSuccess) { /** * END HANDLERS -*/ + */ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { track: function(event) { diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 59d1fa72cbb..112722383a9 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -700,9 +700,9 @@ function getPageDimensions() { } /** -* @todo Move to prebid Core as Utils. -* @returns -*/ + * @todo Move to prebid Core as Utils. + * @returns + */ function getViewPortDimensions() { if (!isSafeFrameWindow() && !canAccessTopWindow()) { return ''; @@ -832,8 +832,8 @@ function getPrintNumber(adUnitCode, bidderRequest) { } /** - * domLoading feature is computed on window.top if reachable. - */ + * domLoading feature is computed on window.top if reachable. + */ function getDomLoadingDuration() { let domLoadingDuration = -1; let performance; diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 236114f6396..5afe7781d7d 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -33,7 +33,8 @@ export const spec = { {code: 'pixad'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], - /** f + /** + * f * @param {object} bid * @return {boolean} */ diff --git a/modules/adpod.js b/modules/adpod.js index d2fd817ee62..f6d8309cd9f 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -319,7 +319,7 @@ export function checkAdUnitSetupHook(fn, adUnits) { * @param {Object} videoMediaType 'mediaTypes.video' associated to bidResponse * @param {Object} bidResponse incoming bidResponse being evaluated by bidderFactory * @returns {boolean} return false if bid duration is deemed invalid as per adUnit configuration; return true if fine -*/ + */ function checkBidDuration(videoMediaType, bidResponse) { const buffer = 2; let bidDuration = deepAccess(bidResponse, 'video.durationSeconds'); diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index 0f445fdfd78..6b0d642468b 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -17,7 +17,8 @@ export const spec = { gvlid: ADQUERY_GVLID, supportedMediaTypes: [BANNER], - /** f + /** + * f * @param {object} bid * @return {boolean} */ diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index b6b6107ddd0..ad52af824ee 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -23,11 +23,11 @@ export const spec = { gvlid: TCF_VENDOR_ID, aliases: ['bp'], /** - * Test if the bid request is valid. - * - * @param {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. - */ + * Test if the bid request is valid. + * + * @param {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) { const id = bid.params.accountId || bid.params.networkId; if (id === null || typeof id === 'undefined') { @@ -39,12 +39,12 @@ export const spec = { return bid.mediaTypes.banner !== null && typeof bid.mediaTypes.banner !== 'undefined'; }, /** - * Create a BeOp server request from a list of BidRequest - * - * @param {validBidRequests[], ...} : The array of validated bidRequests - * @param {... , bidderRequest} : Common params for each bidRequests - * @return ServerRequest Info describing the request to the BeOp's server - */ + * Create a BeOp server request from a list of BidRequest + * + * @param {validBidRequests[], ...} : The array of validated bidRequests + * @param {... , bidderRequest} : Common params for each bidRequests + * @return ServerRequest Info describing the request to the BeOp's server + */ buildRequests: function(validBidRequests, bidderRequest) { const slots = validBidRequests.map(beOpRequestSlotsMaker); const firstPartyData = bidderRequest.ortb2 || {}; diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index e674c6987c3..debe956d7eb 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -134,8 +134,8 @@ function canAccessTopWindow() { } /** - * domLoading feature is computed on window.top if reachable. - */ + * domLoading feature is computed on window.top if reachable. + */ export function getDomLoadingDuration() { let domLoadingDuration = -1; let performance; diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js index b6eb9374671..bf0c457bebd 100644 --- a/modules/blueconicRtdProvider.js +++ b/modules/blueconicRtdProvider.js @@ -19,9 +19,9 @@ export const RTD_LOCAL_NAME = 'bcPrebidData'; export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** -* Try parsing stringified array of data. -* @param {String} data -*/ + * Try parsing stringified array of data. + * @param {String} data + */ function parseJson(data) { try { return JSON.parse(data); diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index bd7a33ff037..20b1125cee9 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -72,10 +72,10 @@ function checkConsent (userConsent) { } /** -* Add event- listeners to hook in to brandmetrics events -* @param {Object} reqBidsConfigObj -* @param {function} callback -*/ + * Add event- listeners to hook in to brandmetrics events + * @param {Object} reqBidsConfigObj + * @param {function} callback + */ function processBrandmetricsEvents (reqBidsConfigObj, moduleConfig, callback) { const callBidTargeting = (event) => { if (event.available && event.conf) { @@ -139,8 +139,8 @@ function initializeBrandmetrics(scriptId) { } /** -* Hook in to brandmetrics creative_in_view- event and emit billable- event for creatives measured by brandmetrics. -*/ + * Hook in to brandmetrics creative_in_view- event and emit billable- event for creatives measured by brandmetrics. + */ function initializeBillableEvents() { if (!billableEventsInitialized) { window._brandmetrics.push({ diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index 42edb783d00..ecf27de997d 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -16,7 +16,7 @@ export const spec = { * * @param {object} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. - */ + */ isBidRequestValid: function (bid) { logInfo(WHO + ' isBidRequestValid() - INPUT bid:', bid); if (bid.bidder !== BIDDER_CODE || typeof bid.params === 'undefined') { @@ -29,10 +29,10 @@ export const spec = { }, /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. - * @return ServerRequest Info describing the request to the server. + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { logInfo(WHO + ' buildRequests() - INPUT validBidRequests:', validBidRequests, 'INPUT bidderRequest:', bidderRequest); @@ -74,7 +74,7 @@ export const spec = { * * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. - */ + */ interpretResponse: function (serverResponse, request) { logInfo(WHO + ' interpretResponse() - INPUT serverResponse:', serverResponse, 'INPUT request:', request); diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index 2548b20189b..4fef3fb2494 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -10,11 +10,11 @@ export const spec = { supportedMediaTypes: [ BANNER ], aliases: ['ex'], // short code /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { if (bid.nativeParams) { return false; @@ -22,11 +22,11 @@ export const spec = { return !!(bid.params.placementId || (bid.params.member && bid.params.invCode)); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { const page = bidderRequest.refererInfo.page; const domain = bidderRequest.refererInfo.domain; @@ -78,11 +78,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: (serverResponse, { bids }) => { if (!serverResponse.body) { return []; @@ -116,12 +116,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { return []; } diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 4fe9ca7d0f4..03aaeae434c 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -125,7 +125,8 @@ export const spec = { return []; }, - /** f + /** + * f * @param {object} bid * @return {boolean} */ diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index c66e381b8f1..870378a13dd 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -16,7 +16,7 @@ export const BIDDER_CODE = 'deltaprojects'; export const BIDDER_ENDPOINT_URL = 'https://d5p.de17a.com/dogfight/prebid'; export const USERSYNC_URL = 'https://userservice.de17a.com/getuid/prebid'; -/** -- isBidRequestValid --**/ +/** -- isBidRequestValid -- */ function isBidRequestValid(bid) { if (!bid) return false; @@ -32,9 +32,9 @@ function isBidRequestValid(bid) { return true; } -/** -- Build requests --**/ +/** -- Build requests -- */ function buildRequests(validBidRequests, bidderRequest) { - /** == shared ==**/ + /** == shared == */ // -- build id const id = bidderRequest.bidderRequestId; @@ -146,7 +146,7 @@ function buildImpressionBanner(bid, bannerMediaType) { }; } -/** -- Interpret response --**/ +/** -- Interpret response -- */ function interpretResponse(serverResponse) { if (!serverResponse.body) { logWarn('Response body is invalid, return !!'); @@ -189,7 +189,7 @@ function interpretResponse(serverResponse) { return bidResponses; } -/** -- On Bid Won -- **/ +/** -- On Bid Won -- */ function onBidWon(bid) { let cpm = bid.cpm; if (bid.currency && bid.currency !== bid.originalCurrency && typeof bid.getCpmInNewCurrency === 'function') { @@ -200,7 +200,7 @@ function onBidWon(bid) { bid.ad = bid.ad.replace(wonPriceMacroPatten, wonPrice); } -/** -- Get user syncs --**/ +/** -- Get user syncs -- */ function getUserSyncs(syncOptions, serverResponses, gdprConsent) { const syncs = [] @@ -223,7 +223,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent) { return syncs; } -/** -- Get bid floor --**/ +/** -- Get bid floor -- */ export function getBidFloor(bid, mediaType, size, currency) { if (isFn(bid.getFloor)) { const bidFloorCurrency = currency || 'USD'; @@ -234,7 +234,7 @@ export function getBidFloor(bid, mediaType, size, currency) { } } -/** -- Helper methods --**/ +/** -- Helper methods -- */ function setOnAny(collection, key) { for (let i = 0, result; i < collection.length; i++) { result = deepAccess(collection[i], key); diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 566dd5f40e2..7162f4e9230 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -20,8 +20,8 @@ export let isEnabled = false; config.getConfig('fledgeForGpt', config => init(config.fledgeForGpt)); /** - * Module init. - */ + * Module init. + */ export function init(cfg) { if (cfg && cfg.enabled === true) { if (!isEnabled) { diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index ba819abd687..73c56160b00 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -183,8 +183,8 @@ function getCampaignId(xmlNode) { } /** -* returns the top most accessible window -*/ + * returns the top most accessible window + */ function getTopMostWindow() { var res = window; @@ -319,21 +319,21 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], aliases: ['stickyadstv', 'freewheelssp'], // aliases for freewheel-ssp /** - * Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!(bid.params.zoneId); }, /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(bidRequests, bidderRequest) { // var currency = config.getConfig(currency); @@ -474,12 +474,12 @@ 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. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @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) { var bidrequest = request.bidRequest; var playerSize = []; diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index afaaef93169..9bdf74c6e3c 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -71,7 +71,7 @@ export function setWrapper(responseText) { /** * preloads the client - * @param {string} key + * @param {string} key */ export function preloadClient(key) { let link = document.createElement('link'); @@ -252,9 +252,9 @@ function init(config, userConsent) { /** @type {RtdSubmodule} */ export const geoedgeSubmodule = { /** - * used to link submodule with realTimeData - * @type {string} - */ + * used to link submodule with realTimeData + * @type {string} + */ name: SUBMODULE_NAME, init, onBidResponseEvent: conditionallyWrap diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 2b6ea1c2c2a..8cffccb9f30 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -38,7 +38,7 @@ export const spec = { * * @param {BidRequest} bid The bid to validate. * @return {boolean} True if this is a valid bid, and false otherwise. - * */ + */ isBidRequestValid: function(bid) { return !!(bid && bid.params && bid.params.pid && bid.params.tid); }, @@ -108,7 +108,7 @@ function buildUrl(bid) { * * @param {BidRequest} bidRequest. * @return {object} GI bid request. - * */ + */ function buildGiBidRequest(bidRequest) { let giBidRequest = { bid_id: bidRequest.bidId, @@ -191,7 +191,7 @@ function addOptional(params, request, props) { /** * @param {String} s The string representing a size (e.g. "300x250"). * @return {Number[]} An array with two elements: [width, height] (e.g.: [300, 250]). - * */ + */ function parseSize(s) { return s.split('x').map(Number); } @@ -200,7 +200,7 @@ function parseSize(s) { * @param {Array} sizes An array of sizes/numbers to be joined into single string. * May be an array (e.g. [300, 250]) or array of arrays (e.g. [[300, 250], [640, 480]]. * @return {String} The string with sizes, e.g. array of sizes [[50, 50], [80, 80]] becomes "50x50,80x80" string. - * */ + */ function produceSize (sizes) { function sizeToStr(s) { if (Array.isArray(s) && s.length === 2 && isInteger(s[0]) && isInteger(s[1])) { diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index 91ed5c9b3fb..86cbb1d89de 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -116,11 +116,11 @@ export const spec = { }; /** -* Generate size param for bid request using sizes array -* -* @param {Array} sizes Possible sizes for the ad unit. -* @return {string} Processed sizes param to be used for the bid request. -*/ + * Generate size param for bid request using sizes array + * + * @param {Array} sizes Possible sizes for the ad unit. + * @return {string} Processed sizes param to be used for the bid request. + */ function generateSizeParam(sizes) { return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); } diff --git a/modules/goldfishAdsRtdProvider.js b/modules/goldfishAdsRtdProvider.js index c466ac5ec25..c90d3104b56 100755 --- a/modules/goldfishAdsRtdProvider.js +++ b/modules/goldfishAdsRtdProvider.js @@ -74,9 +74,9 @@ const getTargetingDataFromApi = (key) => { /** * @returns {{ * name: 'golfishads.com', - * ext: { segtax: 4}, - * segment: string[] - * } | null } + * ext: { segtax: 4}, + * segment: string[] + * } | null } */ export const getStorageData = () => { const now = new Date(); @@ -140,9 +140,9 @@ const init = (config, userConsent) => { * * @param {{ * name: string, -* ext: { segtax: 4}, -* segment: {id: string}[] -* } | null } userData + * ext: { segtax: 4}, + * segment: {id: string}[] + * } | null } userData * @param {*} reqBidsConfigObj * @returns */ diff --git a/modules/gravitoIdSystem.js b/modules/gravitoIdSystem.js index 70031ebd06e..cc02c6a103e 100644 --- a/modules/gravitoIdSystem.js +++ b/modules/gravitoIdSystem.js @@ -16,16 +16,16 @@ export const cookieKey = 'gravitompId'; export const gravitoIdSystemSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** - * performs action to obtain id - * @function - * @returns { {id: {gravitompId: string}} | undefined } - */ + * performs action to obtain id + * @function + * @returns { {id: {gravitompId: string}} | undefined } + */ getId: function() { const newId = storage.getCookie(cookieKey); if (!newId) { @@ -38,11 +38,11 @@ export const gravitoIdSystemSubmodule = { }, /** - * decode the stored id value for passing to bid requests - * @function - * @param { {gravitompId: string} } value - * @returns { {gravitompId: {string} } | undefined } - */ + * decode the stored id value for passing to bid requests + * @function + * @param { {gravitompId: string} } value + * @returns { {gravitompId: {string} } | undefined } + */ decode: function(value) { if (value && typeof value === 'object') { var result = {}; diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index 29dda216fdc..4e51f87cc81 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -28,9 +28,9 @@ function addRealTimeData(ortb2, rtd) { } /** - * Try parsing stringified array of segment IDs. - * @param {String} data - */ + * Try parsing stringified array of segment IDs. + * @param {String} data + */ function tryParse(data) { try { return JSON.parse(data); @@ -41,12 +41,12 @@ function tryParse(data) { } /** - * Real-time data retrieval from ID Ward - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - */ + * Real-time data retrieval from ID Ward + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { const jsonData = storage.getDataFromLocalStorage(rtdConfig.params.cohortStorageKey) @@ -85,11 +85,11 @@ export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent } /** - * Module init - * @param {Object} provider - * @param {Object} userConsent - * @return {boolean} - */ + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ function init(provider, userConsent) { return true; } diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 26d49c49f8c..5aa1d4e4dbe 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -50,8 +50,8 @@ function getSegments(segments, moduleConfig) { } /** -* @param {string} bidderName -*/ + * @param {string} bidderName + */ export function getBidderFunction(bidderName) { const biddersFunction = { pubmatic: function (bid, data, moduleConfig) { diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 04a45e64b19..4c0b9801a36 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -360,7 +360,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 - */ + */ onBidWon: function (bid) { ajax(`${LOGGER_URI}/prebid/won`, null, JSON.stringify(bid), { method: 'POST', @@ -373,7 +373,7 @@ export const spec = { /** * Register bidder specific code, which will execute if bidder timed out after an auction * @param {data} Containing timeout specific data - */ + */ onTimeout: function (data) { ajax(`${LOGGER_URI}/prebid/timeout`, null, JSON.stringify(data[0]), { method: 'POST', diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index d054309ee40..46be7fb75d6 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -12,22 +12,22 @@ export const spec = { supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function (bid) { return !!(bid.params.placementId); }, /** - * Make a server request from the list of BidRequests. - * - * @param validBidRequests - * @param bidderRequest - * @return Array Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param validBidRequests + * @param bidderRequest + * @return Array Info describing the request to the server. + */ buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); @@ -53,11 +53,11 @@ export const spec = { }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function (serverResponse) { const response = serverResponse.body; const bids = []; diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index cc19daf31a3..79c6533d122 100644 --- a/modules/integr8BidAdapter.js +++ b/modules/integr8BidAdapter.js @@ -128,11 +128,11 @@ export const spec = { }; /** -* Generate size param for bid request using sizes array -* -* @param {Array} sizes Possible sizes for the ad unit. -* @return {string} Processed sizes param to be used for the bid request. -*/ + * Generate size param for bid request using sizes array + * + * @param {Array} sizes Possible sizes for the ad unit. + * @return {string} Processed sizes param to be used for the bid request. + */ function generateSizeParam(sizes) { return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); } diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 41e0d8e97c5..06bad99bfe8 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1113,7 +1113,7 @@ This function retrieves the page URL and appends first party data query paramete to it without adding duplicate query parameters. Returns original referer URL if no IX FPD exists. @param {Object} bidderRequest - The bidder request object containing information about the bid and the page. @returns {string} - The modified page URL with first party data query parameters appended. -*/ + */ function getIxFirstPartyDataPageUrl (bidderRequest) { // Parse additional runtime configs. const bidderCode = (bidderRequest && bidderRequest.bidderCode) || 'ix'; @@ -1143,7 +1143,7 @@ This function appends the provided query parameters to the given URL without add @param {string} url - The base URL to which query parameters will be appended. @param {Object} params - An object containing key-value pairs of query parameters to append. @returns {string} - The modified URL with the provided query parameters appended. -*/ + */ function appendIXQueryParams(bidderRequest, url, params) { let urlObj; try { @@ -2008,11 +2008,11 @@ export const spec = { }; /** - * Build img user sync url - * @param {int} syncsPerBidder number of syncs Per Bidder - * @param {int} index index to pass - * @returns {string} img user sync url - */ + * Build img user sync url + * @param {int} syncsPerBidder number of syncs Per Bidder + * @param {int} index index to pass + * @returns {string} img user sync url + */ function buildImgSyncUrl(syncsPerBidder, index) { let consentString = ''; let gdprApplies = '0'; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index b79843dccfd..18f89c33ad2 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -26,16 +26,16 @@ let resumeBidRequest; /** @type {RtdSubmodule} */ export const jwplayerSubmodule = { /** - * used to link submodule with realTimeData - * @type {string} - */ + * used to link submodule with realTimeData + * @type {string} + */ name: SUBMODULE_NAME, /** - * add targeting data to bids and signal completion to realTimeData module - * @function - * @param {Obj} bidReqConfig - * @param {function} onDone - */ + * add targeting data to bids and signal completion to realTimeData module + * @function + * @param {Obj} bidReqConfig + * @param {function} onDone + */ getBidRequestData: enrichBidRequest, init }; diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index 9fa3081a47e..8a3b05b7ed3 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -26,7 +26,7 @@ export var spec = { * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. - **/ + */ isBidRequestValid: (bid) => { if (!bid || !bid.params) { utils.logError(LOG_WARN_PREFIX, 'nil/empty bid object'); @@ -51,11 +51,11 @@ export var spec = { }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - **/ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: (validBidRequests, bidderRequest) => { if (validBidRequests.length === 0) { return; @@ -79,11 +79,11 @@ export var spec = { }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} response A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - **/ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} response A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: (response, request) => { return spec._parseRTBResponse(request, response.body); }, @@ -93,7 +93,7 @@ export var spec = { * @param {SyncOptions} syncOptions Which user syncs are allowed? * @param {ServerResponse[]} serverResponses List of server's responses. * @return {UserSync[]} The user syncs which should be dropped. - **/ + */ getUserSyncs: (syncOptions, serverResponses) => { let syncurl = USER_SYNC + 'pid=' + pubId; if (syncOptions.iframeEnabled) { @@ -115,7 +115,7 @@ export var spec = { /** * parse object - **/ + */ _parseJSON: function (rawPayload) { try { if (rawPayload) { @@ -155,7 +155,7 @@ export var spec = { /** * create IAB standard OpenRTB bid request - **/ + */ _createoRTBRequest: (bidRequests, conf) => { var oRTBObject = {}; try { @@ -202,7 +202,7 @@ export var spec = { /** * create impression array objects - **/ + */ _getImpressionArray: (request) => { var impArray = []; var map = request.map(bid => spec._getImpressionObject(bid)); @@ -218,7 +218,7 @@ export var spec = { /** * create impression (single) object - **/ + */ _getImpressionObject: (bid) => { var impression = {}; var bObj; @@ -277,8 +277,8 @@ export var spec = { }, /** - * set bid floor - **/ + * set bid floor + */ _setFloor: (impObj, bid) => { let bidFloor = -1; // get lowest floor from floorModule @@ -304,8 +304,8 @@ export var spec = { }, /** - * parse Open RTB response - **/ + * parse Open RTB response + */ _parseRTBResponse: (request, response) => { var bidResponses = []; try { @@ -358,8 +358,8 @@ export var spec = { }, /** - * get bid request api end point url - **/ + * get bid request api end point url + */ _endPointURL: (request) => { var params = request && request[0].params ? request[0].params : null; if (params) { @@ -371,8 +371,8 @@ export var spec = { }, /** - * get domain name from url - **/ + * get domain name from url + */ _getDomain: (url) => { var a = document.createElement('a'); a.setAttribute('href', url); @@ -380,8 +380,8 @@ export var spec = { }, /** - * create the site object - **/ + * create the site object + */ _getSiteObject: (request, conf) => { var params = request && request.params ? request.params : null; if (params) { @@ -406,8 +406,8 @@ export var spec = { }, /** - * create the app object - **/ + * create the app object + */ _getAppObject: (request) => { var params = request && request.params ? request.params : null; if (params) { @@ -432,8 +432,8 @@ export var spec = { }, /** - * create the device object - **/ + * create the device object + */ _getDeviceObject: (request) => { var params = request && request.params ? request.params : null; if (params) { @@ -481,8 +481,8 @@ export var spec = { }, /** - * get request ad sizes - **/ + * get request ad sizes + */ _getSizes: (request) => { if (request && request.sizes && utils.isArray(request.sizes[0]) && request.sizes[0].length > 0) { return request.sizes[0]; @@ -491,8 +491,8 @@ export var spec = { }, /** - * create the banner object - **/ + * create the banner object + */ _getBannerRequest: (bid) => { var bObj; var adFormat = []; @@ -531,8 +531,8 @@ export var spec = { }, /** - * create the video object - **/ + * create the video object + */ _getVideoRequest: (bid) => { var vObj; if (utils.deepAccess(bid, 'mediaTypes.video')) { @@ -554,8 +554,8 @@ export var spec = { }, /** - * check media type - **/ + * check media type + */ _checkMediaType: (adm, newBid) => { // Create a regex here to check the strings var videoRegex = new RegExp(/VAST.*version/); diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 5ac50936ed6..1d50ad4e4c1 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -124,11 +124,11 @@ export const spec = { }; /** -* Generate size param for bid request using sizes array -* -* @param {Array} sizes Possible sizes for the ad unit. -* @return {string} Processed sizes param to be used for the bid request. -*/ + * Generate size param for bid request using sizes array + * + * @param {Array} sizes Possible sizes for the ad unit. + * @return {string} Processed sizes param to be used for the bid request. + */ function generateSizeParam(sizes) { return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR); } diff --git a/modules/mediafilterRtdProvider.js b/modules/mediafilterRtdProvider.js index 277b19c10b2..8a082ad4d59 100644 --- a/modules/mediafilterRtdProvider.js +++ b/modules/mediafilterRtdProvider.js @@ -16,9 +16,9 @@ import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; -/** @const {string} MEDIAFILTER_EVENT_TYPE - The event type for Media Filter. */ +/** The event type for Media Filter. */ export const MEDIAFILTER_EVENT_TYPE = 'com.mediatrust.pbjs.'; -/** @const {string} MEDIAFILTER_BASE_URL - The base URL for Media Filter scripts. */ +/** The base URL for Media Filter scripts. */ export const MEDIAFILTER_BASE_URL = 'https://scripts.webcontentassessor.com/scripts/'; export const MediaFilter = { diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 1db4dad84cc..fb580d81b94 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -20,20 +20,20 @@ export const spec = { aliases: ['msq'], // short code supportedMediaTypes: [BANNER, NATIVE, VIDEO], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!(bid.params.owner && bid.params.code); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); @@ -96,11 +96,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { const serverBody = serverResponse.body; // const headerValue = serverResponse.headers.get('some-response-header'); @@ -148,12 +148,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { @@ -164,9 +164,9 @@ export const spec = { }, /** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid if (bid.hasOwnProperty('mediaType') && bid.mediaType == 'video') { diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index fc77c7cc97d..20c6f817dca 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -87,17 +87,17 @@ function generateId(configParams, configStorage) { /** @type {Submodule} */ export const merkleIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{eids:arrayofields}} - */ + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{eids:arrayofields}} + */ decode(value) { // Legacy support for a single id const id = (value && value.pam_id && typeof value.pam_id.id === 'string') ? value.pam_id : undefined; @@ -115,12 +115,12 @@ export const merkleIdSubmodule = { }, /** - * 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} - */ + * 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, consentData) { logInfo('User ID - merkleId generating id'); diff --git a/modules/multibid/index.js b/modules/multibid/index.js index df77a157bee..27b88d47cf7 100644 --- a/modules/multibid/index.js +++ b/modules/multibid/index.js @@ -45,10 +45,10 @@ config.getConfig(MODULE_NAME, conf => { }); /** - * @summary validates multibid configuration entries - * @param {Object[]} multibid - example [{bidder: 'bidderA', maxbids: 2, prefix: 'bidA'}, {bidder: 'bidderB', maxbids: 2}] - * @return {Boolean} -*/ + * @summary validates multibid configuration entries + * @param {Object[]} multibid - example [{bidder: 'bidderA', maxbids: 2, prefix: 'bidA'}, {bidder: 'bidderB', maxbids: 2}] + * @return {Boolean} + */ export function validateMultibid(conf) { let check = true; let duplicate = conf.filter(entry => { @@ -77,10 +77,10 @@ export function validateMultibid(conf) { } /** - * @summary addBidderRequests before hook - * @param {Function} fn reference to original function (used by hook logic) - * @param {Object[]} array containing copy of each bidderRequest object -*/ + * @summary addBidderRequests before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {Object[]} array containing copy of each bidderRequest object + */ export function adjustBidderRequestsHook(fn, bidderRequests) { bidderRequests.map(bidRequest => { // Loop through bidderRequests and check if bidderCode exists in multiconfig @@ -95,11 +95,11 @@ export function adjustBidderRequestsHook(fn, bidderRequests) { } /** - * @summary addBidResponse before hook - * @param {Function} fn reference to original function (used by hook logic) - * @param {String} ad unit code for bid - * @param {Object} bid object -*/ + * @summary addBidResponse before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {String} ad unit code for bid + * @param {Object} bid object + */ export const addBidResponseHook = timedBidResponseHook('multibid', function addBidResponseHook(fn, adUnitCode, bid, reject) { let floor = deepAccess(bid, 'floorData.floorValue'); @@ -146,9 +146,9 @@ export const addBidResponseHook = timedBidResponseHook('multibid', function addB }); /** -* A descending sort function that will sort the list of objects based on the following: -* - bids without dynamic aliases are sorted before bids with dynamic aliases -*/ + * A descending sort function that will sort the list of objects based on the following: + * - bids without dynamic aliases are sorted before bids with dynamic aliases + */ export function sortByMultibid(a, b) { if (a.bidder !== a.bidderCode && b.bidder === b.bidderCode) { return 1; @@ -162,13 +162,13 @@ export function sortByMultibid(a, b) { } /** - * @summary getHighestCpmBidsFromBidPool before hook - * @param {Function} fn reference to original function (used by hook logic) - * @param {Object[]} array of objects containing all bids from bid pool - * @param {Function} function to reduce to only highest cpm value for each bidderCode - * @param {Number} adUnit bidder targeting limit, default set to 0 - * @param {Boolean} default set to false, this hook modifies targeting and sets to true -*/ + * @summary getHighestCpmBidsFromBidPool before hook + * @param {Function} fn reference to original function (used by hook logic) + * @param {Object[]} array of objects containing all bids from bid pool + * @param {Function} function to reduce to only highest cpm value for each bidderCode + * @param {Number} adUnit bidder targeting limit, default set to 0 + * @param {Boolean} default set to false, this hook modifies targeting and sets to true + */ export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { if (!config.getConfig('multibid')) resetMultiConfig(); if (hasMultibid) { @@ -216,18 +216,18 @@ export function targetBidPoolHook(fn, bidsReceived, highestCpmCallback, adUnitBi } /** -* Resets globally stored multibid configuration -*/ + * Resets globally stored multibid configuration + */ export const resetMultiConfig = () => { hasMultibid = false; multiConfig = {}; }; /** -* Resets globally stored multibid ad unit bids -*/ + * Resets globally stored multibid ad unit bids + */ export const resetMultibidUnits = () => multibidUnits = {}; /** -* Set up hooks on init -*/ + * Set up hooks on init + */ function init() { // TODO: does this reset logic make sense - what about simultaneous auctions? events.on(CONSTANTS.EVENTS.AUCTION_INIT, resetMultibidUnits); diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js index ff23547224b..8cc5cd8bb4a 100644 --- a/modules/mwOpenLinkIdSystem.js +++ b/modules/mwOpenLinkIdSystem.js @@ -112,27 +112,27 @@ export { writeCookie }; /** @type {Submodule} */ export const mwOpenLinkIdSubModule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: 'mwOpenLinkId', /** - * decode the stored id value for passing to bid requests - * @function - * @param {MwOlId} mwOlId - * @return {(Object|undefined} - */ + * decode the stored id value for passing to bid requests + * @function + * @param {MwOlId} mwOlId + * @return {(Object|undefined} + */ decode(mwOlId) { const id = mwOlId && isPlainObject(mwOlId) ? mwOlId.eid : undefined; return id ? { 'mwOpenLinkId': id } : undefined; }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleParams} [submoduleParams] - * @returns {id:MwOlId | undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [submoduleParams] + * @returns {id:MwOlId | undefined} + */ getId(submoduleConfig) { const submoduleConfigParams = (submoduleConfig && submoduleConfig.params) || {}; if (!isValidConfig(submoduleConfigParams)) return undefined; diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 8a472259873..f82bf4002ca 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -74,16 +74,16 @@ function readnavIDFromCookie() { /** @type {Submodule} */ export const naveggIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** - * decode the stored id value for passing to bid requests - * @function - * @param { Object | string | undefined } value - * @return { Object | string | undefined } - */ + * decode the stored id value for passing to bid requests + * @function + * @param { Object | string | undefined } value + * @return { Object | string | undefined } + */ decode(value) { const naveggIdVal = value ? isStr(value) ? value : isPlainObject(value) ? value.id : undefined : undefined; return naveggIdVal ? { @@ -91,11 +91,11 @@ export const naveggIdSubmodule = { } : undefined; }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} config - * @return {{id: string | undefined } | undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @return {{id: string | undefined } | undefined} + */ getId() { const naveggIdString = readnaveggIdFromLocalStorage() || readnaveggIDFromCookie() || getNaveggIdFromApi() || readoldnaveggIDFromCookie() || readnvgIDFromCookie() || readnavIDFromCookie(); diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 68010b32b37..77de3c6d97b 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -367,21 +367,21 @@ export const spec = { ], supportedMediaTypes: [BANNER, VIDEO], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { log('isBidRequestValid', bid); return !!bid.params.siteId; }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { function resolveEndpoint() { var ret = 'https://ads.servenobid.com/'; @@ -421,11 +421,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { log('interpretResponse -> serverResponse', serverResponse); log('interpretResponse -> bidRequest', bidRequest); @@ -433,12 +433,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, usPrivacy, gppConsent) { if (syncOptions.iframeEnabled) { let params = ''; @@ -483,9 +483,9 @@ export const spec = { }, /** - * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data - */ + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ onTimeout: function(data) { window.nobid.timeoutTotal++; log('Timeout total: ' + window.nobid.timeoutTotal, data); diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index 7eced81d35e..d0cc94b513b 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -17,9 +17,9 @@ const MODULE_NAME = 'novatiq'; export const novatiqIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** * used to specify vendor id @@ -28,10 +28,10 @@ export const novatiqIdSubmodule = { gvlid: 1119, /** - * decode the stored id value for passing to bid requests - * @function - * @returns {novatiq: {snowflake: string}} - */ + * decode the stored id value for passing to bid requests + * @function + * @returns {novatiq: {snowflake: string}} + */ decode(novatiqId, config) { let responseObj = { novatiq: { @@ -52,11 +52,11 @@ export const novatiqIdSubmodule = { }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} config - * @returns {id: string} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} config + * @returns {id: string} + */ getId(config) { const configParams = config.params || {}; const urlParams = this.getUrlParams(configParams); diff --git a/modules/oneKeyIdSystem.js b/modules/oneKeyIdSystem.js index 699a7a6ab95..7aa9e7b48ac 100644 --- a/modules/oneKeyIdSystem.js +++ b/modules/oneKeyIdSystem.js @@ -47,27 +47,27 @@ const getIdsAndPreferences = (callback) => { /** @type {Submodule} */ export const oneKeyIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: 'oneKeyData', /** - * decode the stored data value for passing to bid requests - * @function decode - * @param {(Object|string)} value - * @returns {(Object|undefined)} - */ + * decode the stored data value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ decode(data) { return { oneKeyData: data }; }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] - * @param {(Object|undefined)} cacheIdObj - * @returns {IdResponse|undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData} [consentData] + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ getId(config) { return { callback: getIdsAndPreferences diff --git a/modules/operaadsIdSystem.js b/modules/operaadsIdSystem.js index 09dd8512a2b..c16d5a9c009 100644 --- a/modules/operaadsIdSystem.js +++ b/modules/operaadsIdSystem.js @@ -50,33 +50,33 @@ function asyncRequest(url, cb) { export const operaIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** - * @type {string} - */ + * @type {string} + */ version, /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} id - * @returns {{'operaId': string}} - */ + * decode the stored id value for passing to bid requests + * @function + * @param {string} id + * @returns {{'operaId': string}} + */ decode: (id) => id != null && id.length > 0 ? { [ID_KEY]: id } : undefined, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @returns {IdResponse|undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [config] + * @returns {IdResponse|undefined} + */ getId(config, consentData) { logMessage(`${MODULE_NAME}: start synchronizing opera uid`); const params = (config && config.params) || {}; diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 9f27ae49d1e..152876b8d5d 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -15,11 +15,11 @@ export const spec = { gvlid: GVL_ID, supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { let isValid = false; if (typeof bid.params !== 'undefined' && bid.params.placementId && bid.params.publisherId) { @@ -29,11 +29,11 @@ export const spec = { return isValid; }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { if (!validBidRequests || validBidRequests.length === 0 || !bidderRequest || !bidderRequest.bids) { return []; @@ -105,11 +105,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { const bidResponses = []; serverResponse = serverResponse.body; @@ -138,12 +138,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { let syncurl = ''; if (!isSynced) { diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index 04d9b9d1b9f..074d8d39bbf 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -30,7 +30,8 @@ let _moduleParams = {}; * Default Optimera Key Name * This can default to hb_deal_optimera for publishers * who used the previous Optimera Bidder Adapter. - * @type {string} */ + * @type {string} + */ export let optimeraKeyName = 'hb_deal_optimera'; /** diff --git a/modules/optimonAnalyticsAdapter.js b/modules/optimonAnalyticsAdapter.js index 82bc18f605d..68baf007563 100644 --- a/modules/optimonAnalyticsAdapter.js +++ b/modules/optimonAnalyticsAdapter.js @@ -1,12 +1,12 @@ /** -* -********************************************************* -* -* Optimon.io Prebid Analytics Adapter -* -********************************************************* -* -*/ + * + ********************************************************* + * + * Optimon.io Prebid Analytics Adapter + * + ********************************************************* + * + */ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js index 489b97d02e3..1009846e06a 100644 --- a/modules/pairIdSystem.js +++ b/modules/pairIdSystem.js @@ -27,29 +27,29 @@ function pairIdFromCookie(key) { /** @type {Submodule} */ export const pairIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** - * used to specify vendor id - * @type {number} - */ + * used to specify vendor id + * @type {number} + */ gvlid: 755, /** - * decode the stored id value for passing to bid requests - * @function - * @param { string | undefined } value - * @returns {{pairId:string} | undefined } - */ + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @returns {{pairId:string} | undefined } + */ decode(value) { return value && Array.isArray(value) ? {'pairId': value} : undefined }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @returns {id: string | undefined } - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {id: string | undefined } + */ getId(config) { const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) let ids = [] diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js index 335c461e3d9..bd3612d6429 100644 --- a/modules/pilotxBidAdapter.js +++ b/modules/pilotxBidAdapter.js @@ -6,11 +6,11 @@ export const spec = { supportedMediaTypes: ['banner', 'video'], aliases: ['pilotx'], // short code /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function (bid) { let sizesCheck = !!bid.sizes let paramSizesCheck = !!bid.params.sizes @@ -35,11 +35,11 @@ export const spec = { return !!(bid.params.placementId); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function (validBidRequests, bidderRequest) { let payloadItems = {}; validBidRequests.forEach(bidRequest => { @@ -84,11 +84,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function (serverResponse, bidRequest) { const serverBody = serverResponse.body; const bidResponses = []; diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index a530c415839..bbc1ea21822 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -207,7 +207,7 @@ getConfig('s2sConfig', ({s2sConfig}) => setS2sConfig(s2sConfig)); /** * resets the _synced variable back to false, primiarily used for testing purposes -*/ + */ export function resetSyncedStatus() { _syncCount = 0; } diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 3837f56366c..f114f142bac 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -51,12 +51,12 @@ export let allowedFields = [SYN_FIELD, 'gptSlot', 'adUnitCode', 'size', 'domain' /** * @summary This is a flag to indicate if a AJAX call is processing for a floors request -*/ + */ let fetching = false; /** * @summary so we only register for our hooks once -*/ + */ let addedFloorsHook = false; /** diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index c13e6e1c330..931c5e7d7e6 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -44,20 +44,20 @@ export const spec = { aliases: ['prismadirect'], // short code supportedMediaTypes: [BANNER, VIDEO], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { return !!(bid.params.account && bid.params.tagId); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { const adUnits = []; const test = config.getConfig('debug') ? 1 : 0; @@ -109,11 +109,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { const serverBody = serverResponse.body; const bidResponses = []; @@ -163,12 +163,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { if (typeof serverResponses === 'object' && serverResponses != null && serverResponses.length > 0 && serverResponses[0].hasOwnProperty('body') && serverResponses[0].body.hasOwnProperty('cookies') && typeof serverResponses[0].body.cookies === 'object') { @@ -179,9 +179,9 @@ export const spec = { }, /** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ onBidWon: function(bid) { // fires a pixel to confirm a winning bid const params = { type: 'prebid', mediatype: 'banner' }; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index a6c7dda510c..3371573ecc2 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -734,9 +734,9 @@ function _addImpressionFPD(imp, bid) { const ortb2 = {...deepAccess(bid, 'ortb2Imp.ext.data')}; Object.keys(ortb2).forEach(prop => { /** - * Prebid AdSlot - * @type {(string|undefined)} - */ + * Prebid AdSlot + * @type {(string|undefined)} + */ if (prop === 'pbadslot') { if (typeof ortb2[prop] === 'string' && ortb2[prop]) deepSetValue(imp, 'ext.data.pbadslot', ortb2[prop]); } else if (prop === 'adserver') { @@ -1011,11 +1011,11 @@ export const spec = { gvlid: 76, supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** - * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. Valid bid request must have placementId and hbid + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: bid => { if (bid && bid.params) { if (!isStr(bid.params.publisherId)) { diff --git a/modules/relevantdigitalBidAdapter.js b/modules/relevantdigitalBidAdapter.js index ef4e1c8e33d..8d1265075f9 100644 --- a/modules/relevantdigitalBidAdapter.js +++ b/modules/relevantdigitalBidAdapter.js @@ -94,7 +94,7 @@ export const spec = { gvlid: 1100, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - /** We need both params.placementId + a complete configuration (pbsHost + accountId) to continue **/ + /** We need both params.placementId + a complete configuration (pbsHost + accountId) to continue */ isBidRequestValid: (bid) => bid.params?.placementId && getBidderConfig([bid]).complete, /** Trigger impression-pixel */ diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index d212d98f50b..5ddb2e410cb 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -63,7 +63,7 @@ export function isUsingNewSizeMapping(adUnits) { does not recognize. @params {Array} adUnits @returns {Array} validateAdUnits - Unrecognized properties are deleted. -*/ + */ export function checkAdUnitSetupHook(adUnits) { const validateSizeConfig = function (mediaType, sizeConfig, adUnitCode) { let isValid = true; diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index c3f06556652..9cdb85d4f25 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -13,11 +13,11 @@ export const spec = { aliases: [{ code: 'scm', gvlid: 102 }], supportedMediaTypes: ['video', 'banner'], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { let isValid = false; if (typeof bid.params !== 'undefined' && !isNaN(parseInt(getValue(bid.params, 'placementId'))) && parseInt(getValue(bid.params, 'placementId')) > 0) { @@ -26,11 +26,11 @@ export const spec = { return isValid; }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { const bids = validBidRequests.map(buildRequestObject); const payload = { @@ -55,11 +55,11 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, request) { const bidResponses = []; serverResponse = serverResponse.body; diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 2fbfcaa79af..09c39e52825 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -40,11 +40,13 @@ export const spec = { transactionId: bid.ortb2Imp?.ext?.tid, timeout: bidderRequest?.timeout, bidId: bid.bidId, - /** positionType is undocumented + /** + positionType is undocumented It is unclear what this parameter means. If it means the same as pos in openRTB, It should read from openRTB object - or from mediaTypes.banner.pos */ + or from mediaTypes.banner.pos + */ positionType: bid.params.positionType || '', prebidVersion: '$prebid.version$' }; diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index a7f4166579b..0ff5d842135 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -214,7 +214,7 @@ export const spec = { * Format Sovrn responses as Prebid bid responses * @param {id, seatbid} sovrnResponse A successful response from Sovrn. * @return {Bid[]} An array of formatted bids. - */ + */ interpretResponse: function({ body: {id, seatbid} }) { if (!id || !seatbid || !Array.isArray(seatbid)) return [] diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 93aa0973cc7..f24af853a54 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -164,7 +164,7 @@ const applyClientHints = ortbRequest => { Check / generate page view id Should be generated dureing first call to applyClientHints(), and re-generated if pathname has changed - */ + */ if (!pageView.id || location.pathname !== pageView.path) { pageView.path = location.pathname; pageView.id = Math.floor(1E20 * Math.random()).toString(); diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 90438f92e1b..898f6a4185d 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -53,7 +53,7 @@ export const spec = { * * @param {BidRequest} bid The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. - */ + */ isBidRequestValid: function(bid) { // bid.params.host if ((new RegExp(`^(vz.*|zz.*)\\.*$`, 'i')).test(bid.params.host)) { // New endpoint @@ -234,12 +234,12 @@ function interpretBid(serverBid, request) { } /** -* Build and makes the request -* -* @param {*} validBidRequests -* @param {*} bidderRequest -* @return response ad -*/ + * Build and makes the request + * + * @param {*} validBidRequests + * @param {*} bidderRequest + * @return response ad + */ function buildOneRequest(validBidRequests, bidderRequest) { let hostInfo = _getHostInfo(validBidRequests); const ENDPOINT = hostInfo.endpoint; diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js index b4067bf75c3..4744fd5d37b 100644 --- a/modules/teadsIdSystem.js +++ b/modules/teadsIdSystem.js @@ -34,9 +34,9 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam /** @type {Submodule} */ export const teadsIdSubmodule = { /** - * used to link submodule with config - * @type {string} - */ + * used to link submodule with config + * @type {string} + */ name: MODULE_NAME, /** * Vendor id of Teads @@ -44,21 +44,21 @@ export const teadsIdSubmodule = { */ gvlid: GVL_ID, /** - * decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{teadsId:string}} - */ + * decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{teadsId:string}} + */ decode(value) { return {teadsId: value} }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [submoduleConfig] - * @param {ConsentData} [consentData] - * @returns {IdResponse|undefined} - */ + * performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleConfig} [submoduleConfig] + * @param {ConsentData} [consentData] + * @returns {IdResponse|undefined} + */ getId(submoduleConfig, consentData) { const resp = function (callback) { const url = buildAnalyticsTagUrl(submoduleConfig, consentData); diff --git a/modules/temedyaBidAdapter.js b/modules/temedyaBidAdapter.js index 4eac0bc641f..cb9fe46d21a 100644 --- a/modules/temedyaBidAdapter.js +++ b/modules/temedyaBidAdapter.js @@ -12,20 +12,20 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, NATIVE], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function (bid) { return !!(bid.params.widgetId); }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function (validBidRequests, bidderRequest) { // convert Native ORTB definition to old-style prebid native definition validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); @@ -54,11 +54,11 @@ export const spec = { }); }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function (serverResponse, bidRequest) { try { const bidResponse = serverResponse.body; diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index dffb8bc4860..617ce0ad6f4 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -178,8 +178,8 @@ export function receiveMessage(evt) { /** Function to store Topics data received from iframe in storage(name: "prebid:topics") -* @param {Topics} topics -*/ + * @param {Topics} topics + */ export function storeInLocalStorage(bidder, topics) { const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); const topicsObj = { @@ -202,8 +202,8 @@ function isCachedDataExpired(storedTime, cacheTime) { } /** -* Function to get random bidders based on count passed with array of bidders -**/ + * Function to get random bidders based on count passed with array of bidders + */ function getRandomBidders(arr, count) { return ([...arr].sort(() => 0.5 - Math.random())).slice(0, count) } diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 67f3cebdce8..1ef72f293fc 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -64,7 +64,7 @@ export const spec = { * Format ucfunnel responses as Prebid bid responses * @param {ucfunnelResponseObj} ucfunnelResponse A successful response from ucfunnel. * @return {Bid[]} An array of formatted bids. - */ + */ interpretResponse: function (ucfunnelResponseObj, request) { const bidRequest = request.bidRequest; const ad = ucfunnelResponseObj ? ucfunnelResponseObj.body : {}; diff --git a/modules/userId/index.js b/modules/userId/index.js index b20b38f0e40..5a088b27319 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -668,8 +668,8 @@ function encryptSignals(signals, version = 1) { } /** -* This function will be exposed in the global-name-space so that publisher can register the signals-ESP. -*/ + * This function will be exposed in the global-name-space so that publisher can register the signals-ESP. + */ function registerSignalSources() { if (!isGptPubadsDefined()) { return; @@ -886,14 +886,12 @@ function updateInitializedSubmodules(dest, submodule) { /** * list of submodule configurations with valid 'storage' or 'value' obj definitions - * * storage config: contains values for storing/retrieving User ID data in browser storage - * * value config: object properties that are copied to bids (without saving to storage) + * storage config: contains values for storing/retrieving User ID data in browser storage + * value config: object properties that are copied to bids (without saving to storage) * @param {SubmoduleConfig[]} configRegistry - * @param {Submodule[]} submoduleRegistry - * @param {string[]} activeStorageTypes * @returns {SubmoduleConfig[]} */ -function getValidSubmoduleConfigs(configRegistry, submoduleRegistry) { +function getValidSubmoduleConfigs(configRegistry) { if (!Array.isArray(configRegistry)) { return []; } @@ -958,7 +956,7 @@ function updateEIDConfig(submodules) { */ function updateSubmodules() { updateEIDConfig(submoduleRegistry); - const configs = getValidSubmoduleConfigs(configRegistry, submoduleRegistry); + const configs = getValidSubmoduleConfigs(configRegistry); if (!configs.length) { return; } diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 14abba95323..a0963b844e1 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -7,25 +7,29 @@ * @requires module:modules/realTimeData */ -/** profile metadata +/** + * profile metadata * @typedef dataCallbackMetadata * @property {boolean} user if true it is user-centric data * @property {string} source describe the source of data, if 'contextual' or 'wam' * @property {boolean} isDefault if true it the default profile defined in the configuration */ -/** profile from contextual, wam or sfbx +/** + * profile from contextual, wam or sfbx * @typedef {Object.} Profile */ -/** onData callback type +/** + * onData callback type * @callback dataCallback * @param {Profile} data profile data * @param {dataCallbackMetadata} meta metadata * @returns {void} */ -/** setPrebidTargeting callback type +/** + * setPrebidTargeting callback type * @callback setPrebidTargetingCallback * @param {string} adUnitCode * @param {Profile} data @@ -33,7 +37,8 @@ * @returns {boolean} */ -/** sendToBidders callback type +/** + * sendToBidders callback type * @callback sendToBiddersCallback * @param {Object} bid * @param {string} adUnitCode @@ -90,7 +95,8 @@ * @property {?boolean} enabled if false, will ignore this configuration */ -/** common configuration between contextual, wam and sfbx +/** + * common configuration between contextual, wam and sfbx * @typedef {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} CommonConf */ @@ -188,7 +194,8 @@ class WeboramaRtdProvider { constructor(components) { this.#components = components; } - /** Initialize module + /** + * Initialize module * @method * @param {Object} moduleConfig * @param {?ModuleParams} moduleConfig.params @@ -219,7 +226,8 @@ class WeboramaRtdProvider { return Object.values(this.#components).some((c) => c.initialized); } - /** function that will allow RTD sub-modules to modify the AdUnit object for each auction + /** + * function that will allow RTD sub-modules to modify the AdUnit object for each auction * @method * @param {Object} reqBidsConfigObj * @param {doneCallback} onDone @@ -253,7 +261,8 @@ class WeboramaRtdProvider { }); } - /** function that provides ad server targeting data to RTD-core + /** + * function that provides ad server targeting data to RTD-core * @method * @param {string[]} adUnitsCodes * @param {Object} moduleConfig @@ -294,7 +303,8 @@ class WeboramaRtdProvider { } } - /** Initialize subsection module + /** + * Initialize subsection module * @method * @private * @param {ModuleParams} moduleParams @@ -330,7 +340,8 @@ class WeboramaRtdProvider { return true; } - /** normalize submodule configuration + /** + * normalize submodule configuration * @method * @private * @param {ModuleParams} moduleParams @@ -363,7 +374,8 @@ class WeboramaRtdProvider { } } - /** coerce setPrebidTargeting to a callback + /** + * coerce setPrebidTargeting to a callback * @method * @private * @param {CommonConf} submoduleParams @@ -379,7 +391,8 @@ class WeboramaRtdProvider { } } - /** coerce sendToBidders to a callback + /** + * coerce sendToBidders to a callback * @method * @private * @param {CommonConf} submoduleParams @@ -426,7 +439,8 @@ class WeboramaRtdProvider { * @typedef {Object} AdUnit * @property {Object[]} bids */ - /** function that handles bid request data + /** + * function that handles bid request data * @method * @private * @param {Object} reqBidsConfigObj @@ -476,18 +490,21 @@ class WeboramaRtdProvider { }); } - /** onSuccess callback type + /** + * onSuccess callback type * @callback successCallback * @param {?Object} data * @returns {void} */ - /** onDone callback type + /** + * onDone callback type * @callback doneCallback * @returns {void} */ - /** Fetch Bigsea Contextual Profile + /** + * Fetch Bigsea Contextual Profile * @method * @private * @param {WeboCtxConf} weboCtxConf @@ -566,7 +583,8 @@ class WeboramaRtdProvider { ajax(urlProfileAPI, callback, null, options); } - /** set bigsea contextual profile on module state + /** + * set bigsea contextual profile on module state * @method * @private * @param {?Object} data @@ -579,7 +597,8 @@ class WeboramaRtdProvider { } } - /** function that provides data handlers based on the configuration + /** + * function that provides data handlers based on the configuration * @method * @private * @param {ModuleParams} moduleParams @@ -667,7 +686,8 @@ class WeboramaRtdProvider { onData: dataConf.onData, }; } - /** handle individual bid + /** + * handle individual bid * @method * @private * @param {Object} reqBidsConfigObj @@ -694,7 +714,8 @@ class WeboramaRtdProvider { } } - /** function that handles bid request data + /** + * function that handles bid request data * @method * @private * @param {ProfileHandler} ph profile handler @@ -705,7 +726,8 @@ class WeboramaRtdProvider { return [deepClone(ph.data), deepClone(ph.metadata)]; } - /** handle appnexus/xandr bid + /** + * handle appnexus/xandr bid * @method * @private * @param {Object} reqBidsConfigObj @@ -723,7 +745,8 @@ class WeboramaRtdProvider { // this.#setBidderOrtb2(reqBidsConfigObj.ortb2Fragments?.bidder, bid.bidder, base, profile); } - /** handle generic bid via ortb2 arbitrary data + /** + * handle generic bid via ortb2 arbitrary data * @method * @private * @param {Object} reqBidsConfigObj @@ -844,7 +867,8 @@ export function isValidProfile(profile) { * @returns {buildProfileHandlerCallback} */ function getContextualProfile(component /* equivalent to this */) { - /** return contextual profile + /** + * return contextual profile * @param {WeboCtxConf} weboCtxConf * @returns {[Profile,boolean]} contextual profile + isDefault boolean flag */ @@ -865,7 +889,8 @@ function getContextualProfile(component /* equivalent to this */) { * @returns {buildProfileHandlerCallback} */ function getWeboUserDataProfile(component /* equivalent to this */) { - /** return weboUserData profile + /** + * return weboUserData profile * @param {WeboUserDataConf} weboUserDataConf * @returns {[Profile,boolean]} weboUserData profile + isDefault boolean flag */ @@ -885,7 +910,8 @@ function getWeboUserDataProfile(component /* equivalent to this */) { * @returns {buildProfileHandlerCallback} */ function getSfbxLiteDataProfile(component /* equivalent to this */) { - /** return weboUserData profile + /** + * return weboUserData profile * @param {SfbxLiteDataConf} sfbxLiteDataConf * @returns {[Profile,boolean]} sfbxLiteData profile + isDefault boolean flag */ @@ -909,7 +935,8 @@ function getSfbxLiteDataProfile(component /* equivalent to this */) { * @returns {void} */ -/** return generic webo data profile +/** + * return generic webo data profile * @param {WeboUserDataConf|SfbxLiteDataConf} weboDataConf * @param {cacheGetCallback} cacheGet * @param {cacheSetCallback} cacheSet diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js index 6f527d905d6..bff76aae172 100644 --- a/modules/xeBidAdapter.js +++ b/modules/xeBidAdapter.js @@ -142,12 +142,12 @@ function interpretResponse(serverResponse, { bidderRequest }) { } /** -* Register the user sync pixels which should be dropped after the auction. -* -* @param {SyncOptions} syncOptions Which user syncs are allowed? -* @param {ServerResponse[]} serverResponses List of server's responses. -* @return {UserSync[]} The user syncs which should be dropped. -*/ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { const syncs = []; const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); @@ -172,11 +172,11 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent } /** -* Get valid floor value from getFloor fuction. -* -* @param {Object} bid Current bid request. -* @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. -*/ + * Get valid floor value from getFloor fuction. + * + * @param {Object} bid Current bid request. + * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. + */ export function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return null; diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index cc4a8c18241..cb1cbd89c84 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -87,7 +87,7 @@ export const NATIVE_ASSETS = { const NATIVE_ASSETS_IDS = {}; _each(NATIVE_ASSETS, (asset, key) => { NATIVE_ASSETS_IDS[asset[0]] = key }); -/** @type BidderSpec */ +/** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, aliases: ['ya'], // short code diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index 527030efc9a..9a58e391a17 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -15,11 +15,11 @@ export const spec = { supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} bid The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function(bid) { // check for all required bid fields if (!(bid && @@ -50,12 +50,12 @@ export const spec = { }, /** - * Make a server request from the list of BidRequests. - * - * @param {Bids[]} validBidRequests - an array of bidRequest objects - * @param {BidderRequest} bidderRequest - master bidRequest object - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function(validBidRequests, bidderRequest) { const secure = 1; // treat all requests as secure const request = validBidRequests[0]; @@ -117,12 +117,12 @@ export const spec = { }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @param bidRequest The payload from the server's response. - * @return {Bid[]} An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ interpretResponse: function(serverResponse, bidRequest) { let bidResponse = []; if (Object.keys(serverResponse.body).length !== 0) { diff --git a/package-lock.json b/package-lock.json index cfdc8669c2f..3020105714f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jsdoc": "^38.1.6", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", "eslint-plugin-promise": "^5.1.0", @@ -1669,6 +1670,20 @@ "node": ">=0.1.90" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", + "integrity": "sha512-pM6WQKcuAtdYoqCsXSvVSu3Ij8K0HY50L8tIheOKHDl0wH1uA4zbP88etY8SIeP16NVCMCTFU+Q2DahSKheGGQ==", + "dev": true, + "dependencies": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~2.2.5" + }, + "engines": { + "node": "^12 || ^14 || ^16 || ^17" + } + }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -7489,6 +7504,15 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -9562,6 +9586,55 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/eslint-plugin-jsdoc": { + "version": "38.1.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.1.6.tgz", + "integrity": "sha512-n4s95oYlg0L43Bs8C0dkzIldxYf8pLCutC/tCbjIdF7VDiobuzPI+HZn9Q0BvgOvgPNgh5n7CSStql25HUG4Tw==", + "dev": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.22.1", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "regextras": "^0.8.0", + "semver": "^7.3.5", + "spdx-expression-parse": "^3.0.1" + }, + "engines": { + "node": "^12 || ^14 || ^16 || ^17" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -15610,6 +15683,15 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", + "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -20567,6 +20649,15 @@ "node": ">=4" } }, + "node_modules/regextras": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", + "dev": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/regjsgen": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", @@ -26452,6 +26543,17 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@es-joy/jsdoccomment": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", + "integrity": "sha512-pM6WQKcuAtdYoqCsXSvVSu3Ij8K0HY50L8tIheOKHDl0wH1uA4zbP88etY8SIeP16NVCMCTFU+Q2DahSKheGGQ==", + "dev": true, + "requires": { + "comment-parser": "1.3.1", + "esquery": "^1.4.0", + "jsdoc-type-pratt-parser": "~2.2.5" + } + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -31123,6 +31225,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "comment-parser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", + "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "dev": true + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -32862,6 +32970,39 @@ } } }, + "eslint-plugin-jsdoc": { + "version": "38.1.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.1.6.tgz", + "integrity": "sha512-n4s95oYlg0L43Bs8C0dkzIldxYf8pLCutC/tCbjIdF7VDiobuzPI+HZn9Q0BvgOvgPNgh5n7CSStql25HUG4Tw==", + "dev": true, + "requires": { + "@es-joy/jsdoccomment": "~0.22.1", + "comment-parser": "1.3.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.4.0", + "regextras": "^0.8.0", + "semver": "^7.3.5", + "spdx-expression-parse": "^3.0.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "eslint-plugin-node": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", @@ -37487,6 +37628,12 @@ "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, + "jsdoc-type-pratt-parser": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", + "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", + "dev": true + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -41242,6 +41389,12 @@ "unicode-match-property-value-ecmascript": "^2.0.0" } }, + "regextras": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", + "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", + "dev": true + }, "regjsgen": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", diff --git a/package.json b/package.json index 830f700537d..dda2e4e3b38 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.20.2", + "eslint-plugin-jsdoc": "^38.1.6", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", "eslint-plugin-promise": "^5.1.0", diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 44775b4e241..df7b2fb0eea 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -27,6 +27,11 @@ import {activityParams} from '../activities/activityParams.js'; import {MODULE_TYPE_BIDDER} from '../activities/modules.js'; import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activities.js'; +/** + * @typedef {import('../mediaTypes.js').MediaType} MediaType + * @typedef {import('../Renderer.js').Renderer} Renderer + */ + /** * This file aims to support Adapters during the Prebid 0.x -> 1.x transition. * @@ -57,7 +62,7 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ * @property {string} code A code which will be used to uniquely identify this bidder. This should be the same * one as is used in the call to registerBidAdapter * @property {string[]} [aliases] A list of aliases which should also resolve to this bidder. - * @property {MediaType[]} [supportedMediaTypes]: A list of Media Types which the adapter supports. + * @property {MediaType[]} [supportedMediaTypes] A list of Media Types which the adapter supports. * @property {function(object): boolean} isBidRequestValid Determines whether or not the given bid has all the params * needed to make a valid request. * @property {function(BidRequest[], bidderRequest): ServerRequest|ServerRequest[]} buildRequests Build the request to the Server @@ -105,7 +110,7 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ * * @property {*} body The response body. If this is legal JSON, then it will be parsed. Otherwise it'll be a * string with the body's content. - * @property {{get: function(string): string} headers The response headers. + * @property {{get: function(string): string}} headers The response headers. * Call this like `ServerResponse.headers.get("Content-Type")` */ @@ -126,7 +131,7 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ * @property {object} [video] Object for storing video response data * @property {object} [meta] Object for storing bid meta data * @property {string} [meta.primaryCatId] The IAB primary category ID - * @property [Renderer] renderer A Renderer which can be used as a default for this bid, + * @property {Renderer} renderer A Renderer which can be used as a default for this bid, * if the publisher doesn't override it. This is only relevant for Outstream Video bids. */ diff --git a/src/auction.js b/src/auction.js index 10b28ad44c9..0d677c540b9 100644 --- a/src/auction.js +++ b/src/auction.js @@ -9,14 +9,14 @@ */ /** - * @typedef {Object} AdUnit An object containing the adUnit configuration. - * - * @property {string} code A code which will be used to uniquely identify this bidder. This should be the same - * one as is used in the call to registerBidAdapter - * @property {Array.} sizes A list of size for adUnit. - * @property {object} params Any bidder-specific params which the publisher used in their bid request. - * This is guaranteed to have passed the spec.areParamsValid() test. - */ + * @typedef {Object} AdUnit An object containing the adUnit configuration. + * + * @property {string} code A code which will be used to uniquely identify this bidder. This should be the same + * one as is used in the call to registerBidAdapter + * @property {Array.} sizes A list of size for adUnit. + * @property {object} params Any bidder-specific params which the publisher used in their bid request. + * This is guaranteed to have passed the spec.areParamsValid() test. + */ /** * @typedef {Array.} size @@ -119,19 +119,19 @@ export function resetAuctionState() { } /** - * Creates new auction instance - * - * @param {Object} requestConfig - * @param {AdUnit} requestConfig.adUnits - * @param {AdUnitCode} requestConfig.adUnitCodes - * @param {function():void} requestConfig.callback - * @param {number} requestConfig.cbTimeout - * @param {Array.} requestConfig.labels - * @param {string} requestConfig.auctionId - * @param {{global: {}, bidder: {}}} ortb2Fragments first party data, separated into global - * (from getConfig('ortb2') + requestBids({ortb2})) and bidder (a map from bidderCode to ortb2) - * @returns {Auction} auction instance - */ + * Creates new auction instance + * + * @param {Object} requestConfig + * @param {AdUnit} requestConfig.adUnits + * @param {AdUnitCode} requestConfig.adUnitCodes + * @param {function():void} requestConfig.callback + * @param {number} requestConfig.cbTimeout + * @param {Array.} requestConfig.labels + * @param {string} requestConfig.auctionId + * @param {{global: {}, bidder: {}}} ortb2Fragments first party data, separated into global + * (from getConfig('ortb2') + requestBids({ortb2})) and bidder (a map from bidderCode to ortb2) + * @returns {Auction} auction instance + */ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId, ortb2Fragments, metrics}) { metrics = useMetrics(metrics); const _adUnits = adUnits; diff --git a/src/events.js b/src/events.js index 750ecdd8eee..d98991180bf 100644 --- a/src/events.js +++ b/src/events.js @@ -61,12 +61,13 @@ const _public = (function () { elapsedTime: utils.getPerformanceNow(), }); - /** Push each specific callback to the `callbacks` array. + /** + * Push each specific callback to the `callbacks` array. * If the `event` map has a key that matches the value of the * event payload id path, e.g. `eventPayload[idPath]`, then apply * each function in the `que` array as an argument to push to the * `callbacks` array - * */ + */ if (key && eventKeys.includes(key)) { push.apply(callbacks, event[key].que); } diff --git a/src/targeting.js b/src/targeting.js index e46d1ae47a4..2c2ddefa6c2 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -85,26 +85,26 @@ export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, }); /** -* A descending sort function that will sort the list of objects based on the following two dimensions: -* - bids with a deal are sorted before bids w/o a deal -* - then sort bids in each grouping based on the hb_pb value -* eg: the following list of bids would be sorted like: -* [{ -* "hb_adid": "vwx", -* "hb_pb": "28", -* "hb_deal": "7747" -* }, { -* "hb_adid": "jkl", -* "hb_pb": "10", -* "hb_deal": "9234" -* }, { -* "hb_adid": "stu", -* "hb_pb": "50" -* }, { -* "hb_adid": "def", -* "hb_pb": "2" -* }] -*/ + * A descending sort function that will sort the list of objects based on the following two dimensions: + * - bids with a deal are sorted before bids w/o a deal + * - then sort bids in each grouping based on the hb_pb value + * eg: the following list of bids would be sorted like: + * [{ + * "hb_adid": "vwx", + * "hb_pb": "28", + * "hb_deal": "7747" + * }, { + * "hb_adid": "jkl", + * "hb_pb": "10", + * "hb_deal": "9234" + * }, { + * "hb_adid": "stu", + * "hb_pb": "50" + * }, { + * "hb_adid": "def", + * "hb_pb": "2" + * }] + */ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { return function(a, b) { if (a.adserverTargeting.hb_deal !== undefined && b.adserverTargeting.hb_deal === undefined) { diff --git a/src/userSync.js b/src/userSync.js index 936836eb12e..7e23598a318 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -244,7 +244,7 @@ export function newUserSync(deps) { * @param {string} type The type of the sync; either image or iframe * @param {string} bidder The name of the adapter. e.g. "rubicon" * @returns {boolean} true => bidder is not allowed to register; false => bidder can register - */ + */ function shouldBidderBeBlocked(type, bidder) { let filterConfig = usConfig.filterSettings; diff --git a/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js b/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js index 5b5ea2ef2cd..d1803c9784a 100644 --- a/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js +++ b/test/spec/e2e/modules/e2e_consent_mgt_gdpr.spec.js @@ -1,4 +1,4 @@ -/** +/* TODO: old CMP no longer works; see if we can fix this with https://github.com/prebid/Prebid.js/issues/6377 const expect = require('chai').expect; const { testPageURL, switchFrame, waitForElement } = require('../../../helpers/testing-utils'); @@ -59,4 +59,4 @@ describe('Prebid.js GDPR Ad Unit Test', function () { expect(ele.isExisting()).to.be.true; }); }); -**/ + */ From 759c52df5e0ae3531cde62810fc701f9d8615761 Mon Sep 17 00:00:00 2001 From: pangle-fe <149553960+pangle-fe@users.noreply.github.com> Date: Tue, 9 Jan 2024 01:15:20 +0800 Subject: [PATCH 318/760] Pangle Bid Adapter : add multi format support (#10909) * feat: pangle multi format * feat: multi format --- modules/pangleBidAdapter.js | 23 +++- test/spec/modules/pangleBidAdapter_spec.js | 134 +++++++++++++++++---- 2 files changed, 131 insertions(+), 26 deletions(-) diff --git a/modules/pangleBidAdapter.js b/modules/pangleBidAdapter.js index e75a96048b4..f4a52168743 100644 --- a/modules/pangleBidAdapter.js +++ b/modules/pangleBidAdapter.js @@ -18,6 +18,11 @@ const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; const PANGLE_COOKIE = '_pangle_id'; const COOKIE_EXP = 86400 * 1000 * 365 * 1; // 1 year +const MEDIA_TYPES = { + Banner: 1, + Video: 2 +}; + export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: BIDDER_CODE }) export function isValidUuid(uuid) { @@ -104,12 +109,20 @@ const converter = ortbConverter({ currency: DEFAULT_CURRENCY, }, bidResponse(buildBidResponse, bid, context) { - const bidResponse = buildBidResponse(bid, context); const { bidRequest } = context; - if (bidRequest.mediaTypes.video?.context === 'outstream') { - const renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode}); - renderer.setRender(renderOutstream); - bidResponse.renderer = renderer; + let bidResponse; + if (bid.mtype === MEDIA_TYPES.Video) { + context.mediaType = VIDEO; + bidResponse = buildBidResponse(bid, context); + if (bidRequest.mediaTypes.video?.context === 'outstream') { + const renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode}); + renderer.setRender(renderOutstream); + bidResponse.renderer = renderer; + } + } + if (bid.mtype === MEDIA_TYPES.Banner) { + context.mediaType = BANNER; + bidResponse = buildBidResponse(bid, context); } return bidResponse; }, diff --git a/test/spec/modules/pangleBidAdapter_spec.js b/test/spec/modules/pangleBidAdapter_spec.js index 94f5a6e4b1d..6d8f9a66bcf 100644 --- a/test/spec/modules/pangleBidAdapter_spec.js +++ b/test/spec/modules/pangleBidAdapter_spec.js @@ -1,6 +1,5 @@ import { expect } from 'chai'; import { spec } from 'modules/pangleBidAdapter.js'; -import { logInfo } from '../../../src/utils'; const REQUEST = [{ adUnitCode: 'adUnitCode1', @@ -84,6 +83,7 @@ const RESPONSE = { 'cat': [], 'w': 300, 'h': 250, + 'mtype': 1, 'ext': { 'prebid': { 'type': 'banner' @@ -186,26 +186,6 @@ describe('pangle bid adapter', function () { expect(deviceType).to.equal(2); }); }); - - // describe('video', function () { - // it('video config', function() { - // logInfo(spec.buildRequests(VIDEO_REQUEST, DEFAULT_OPTIONS)[0].data, 'spec.buildRequests(videoConfig, DEFAULT_OPTIONS)[0].data.imp[0]'); - // const request = spec.buildRequests(VIDEO_REQUEST, DEFAULT_OPTIONS)[0]; - - // expect(request).to.exist.and.to.be.a('object'); - // const payload = request.data; - // expect(payload).to.exist.and.to.be.a('object'); - // const video = payload.imp[0].video; - // expect(video).to.exist.and.to.be.a('object'); - // // console.log(video, 'video???') - // // expect(url).to.equal('https://pangle.pangleglobal.com/api/ad/union/web_js/common/get_ads'); - // // assert.deepEqual(video, { - // // h: 380, - // // mimes: ['video/mp4'], - // // w: 240 - // // }) - // }) - // }) }); describe('Pangle Adapter with video', function() { @@ -247,6 +227,7 @@ describe('Pangle Adapter with video', function() { ], 'w': 640, 'h': 640, + 'mtype': 1, 'ext': { 'pangle': { 'adtype': 8 @@ -293,3 +274,114 @@ describe('Pangle Adapter with video', function() { }); }); }); + +describe('pangle multi-format ads', function () { + const bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + const multiRequest = [ + { + bidId: '2820132fe18114', + mediaTypes: { banner: { sizes: [[300, 250]] }, video: { context: 'outstream', playerSize: [[300, 250]] } }, + params: { token: 'test-token' } + } + ]; + const videoResponse = { + 'headers': null, + 'body': { + 'id': '233f1693-68d1-470a-ad85-c156c3faaf6f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2820132fe18114', + 'impid': '2820132fe18114', + 'price': 0.03294, + 'nurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/win/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&win_price=${AUCTION_PRICE}&auction_mwb=${AUCTION_BID_TO_WIN}&use_pb=1', + 'lurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/loss/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&reason=${AUCTION_LOSS}&ad_slot_type=8&auction_mwb=${AUCTION_PRICE}&use_pb=1', + 'adm': '', + 'adid': '1780626232977441', + 'adomain': [ + 'swi.esxcmnb.com' + ], + 'iurl': 'https://p16-ttam-va.ibyteimg.com/origin/ad-site-i18n-sg/202310245d0d598b3ff5993c4f129a8b', + 'cid': '1780626232977441', + 'crid': '1780626232977441', + 'attr': [ + 4 + ], + 'w': 640, + 'h': 640, + 'mtype': 2, + 'ext': { + 'pangle': { + 'adtype': 8 + }, + 'event_notification_token': { + 'payload': '980589944:8:1450:7492' + } + } + } + ], + 'seat': 'pangle' + } + ] + } + }; + const bannerResponse = { + 'headers': null, + 'body': { + 'id': '233f1693-68d1-470a-ad85-c156c3faaf6f', + 'seatbid': [ + { + 'bid': [ + { + 'id': '2820132fe18114', + 'impid': '2820132fe18114', + 'price': 0.03294, + 'nurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/win/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&win_price=${AUCTION_PRICE}&auction_mwb=${AUCTION_BID_TO_WIN}&use_pb=1', + 'lurl': 'https://api16-event-sg2.pangle.io/api/ad/union/openrtb/loss/?req_id=233f1693-68d1-470a-ad85-c156c3faaf6fu1450&ttdsp_adx_index=256&rit=980589944&extra=oqveoB%2Bg4%2ByNz9L8wwu%2Fy%2FwKxQsGaKsJHuB4NMK77uqZ9%2FJKpnsVZculJX8%2FxrRBAtaktU1DRN%2Fy6TKAqibCbj%2FM3%2BZ6biAKQG%2BCyt4eIV0KVvri9jCCnaajbkN7YNJWJJw2lW6cJ6Va3SuJG9H7a%2FAJd2PMbhK7fXWhoW72TwgOcKHKBgjM6sNDISBKbWlZyY3L1PhKSX%2FM8LOvL6qahsb%2FDpEObIx24vhQLNWp28XY1L4UqeibuRjam3eCvN7nXoQq74KkJ45QQsTgvV4j6I6EbLOdjOi%2FURhWMDjUD1VCMpqUT%2B6L8ZROgrX9Tp53eJ3bFOczmSTOmDSazKMHa%2B3uZZ7JHcSx32eoY4hfYc99NOJmYBKXNKCmoXyJvS3PCM3PlAz97hKrDMGnVv1wAQ7QGDCbittF0vZwtsRAvvx2mWINNIB3%2FUB2PjhxFsoDA%2BWE2urVZwEdyu%2FJrCznJsMwenXjcbMD5jmUF5vDkkLS%2B7TMDIEawJPJKZ62pK35enrwGxCs6ePXi21rJJkA0bF8tgAdl4mU1illBIVO4kCL%2ByRASskHPjgg%2FcdFe9HP%2Fi8byjAprH%2BhRerN%2FRKFxC3xv8b75x2pb1g7dY%2FTj9IjT0evsBSPVwFNqtKmPId35IcY%2FSXiqPHh%2FrAHZzr5BPsTT19P49SlNMR9UZYTzViX1iJpcCL1UFjuDdrdff%2BhHCviXxo%2FkRmufEF3umHZwxbdDOPAghuZ0DtRCY6S1rnb%2FK9BbpsVKSndOtgfCwMHFwiPmdw1XjEXGc1eOWXY6qfSp90PIfL6WS7Neh3ba2qMv6WxG3HSOBYvrcCqVTsNxk4UdVm3qb1J0CMVByweTMo45usSkCTdvX3JuEB7tVA6%2BrEk57b3XJd5Phf2AN8hon%2F7lmcXE41kwMQuXq89ViwQmW0G247UFWOQx4t1cmBqFiP6qNA%2F%2BunkZDno1pmAsGnTv7Mz9xtpOaIqKl8BKrVQSTopZ9WcUVzdBUutF19mn1f43BvyA9gIEhcDJHOj&reason=${AUCTION_LOSS}&ad_slot_type=8&auction_mwb=${AUCTION_PRICE}&use_pb=1', + 'adm': '', + 'adid': '1780626232977441', + 'adomain': [ + 'swi.esxcmnb.com' + ], + 'iurl': 'https://p16-ttam-va.ibyteimg.com/origin/ad-site-i18n-sg/202310245d0d598b3ff5993c4f129a8b', + 'cid': '1780626232977441', + 'crid': '1780626232977441', + 'attr': [ + 4 + ], + 'w': 640, + 'h': 640, + 'mtype': 1, + 'ext': { + 'pangle': { + 'adtype': 8 + }, + 'event_notification_token': { + 'payload': '980589944:8:1450:7492' + } + } + } + ], + 'seat': 'pangle' + } + ] + } + }; + it('should set mediaType to banner', function() { + const request = spec.buildRequests(multiRequest, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(bannerResponse, request); + const bid = interpretedResponse[0]; + expect(bid.mediaType).to.equal('banner'); + }) + it('should set mediaType to video', function() { + const request = spec.buildRequests(multiRequest, bidderRequest)[0]; + const interpretedResponse = spec.interpretResponse(videoResponse, request); + const bid = interpretedResponse[0]; + expect(bid.mediaType).to.equal('video'); + }) +}); From 66893733caf9404a582f6c7db20f9308e35448fe Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Mon, 8 Jan 2024 20:43:08 +0100 Subject: [PATCH 319/760] Criteo bid adapter: remove checks on video context and placement (#10912) Those checks are no longer useful so we decided to remove them. --- modules/criteoBidAdapter.js | 12 +-- test/spec/modules/criteoBidAdapter_spec.js | 87 ---------------------- 2 files changed, 1 insertion(+), 98 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 03aaeae434c..dfc88ba653f 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -683,17 +683,7 @@ function hasValidVideoMediaType(bidRequest) { } }); - if (isValid) { - const videoPlacement = bidRequest.mediaTypes.video.placement || bidRequest.params.video.placement; - // We do not support long form for now, also we have to check that context & placement are consistent - if (bidRequest.mediaTypes.video.context == 'instream' && videoPlacement === 1) { - return true; - } else if (bidRequest.mediaTypes.video.context == 'outstream' && videoPlacement !== 1) { - return true; - } - } - - return false; + return isValid; } /** diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 9987df34f2b..89d1d02b9d2 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -364,93 +364,6 @@ describe('The Criteo bidding adapter', function () { }); it('should return false when given an invalid video bid request', function () { - expect(spec.isBidRequestValid({ - bidder: 'criteo', - mediaTypes: { - video: { - mimes: ['video/mpeg'], - playerSize: [640, 480], - protocols: [5, 6], - maxduration: 30, - api: [1, 2] - } - }, - params: { - networkId: 456, - video: { - skip: 1, - placement: 1, - playbackmethod: 1 - } - }, - })).to.equal(false); - - expect(spec.isBidRequestValid({ - bidder: 'criteo', - mediaTypes: { - video: { - context: 'instream', - mimes: ['video/mpeg'], - playerSize: [640, 480], - protocols: [5, 6], - maxduration: 30, - api: [1, 2] - } - }, - params: { - networkId: 456, - video: { - skip: 1, - placement: 2, - playbackmethod: 1 - } - }, - })).to.equal(false); - - expect(spec.isBidRequestValid({ - bidder: 'criteo', - mediaTypes: { - video: { - context: 'outstream', - mimes: ['video/mpeg'], - playerSize: [640, 480], - protocols: [5, 6], - maxduration: 30, - api: [1, 2] - } - }, - params: { - networkId: 456, - video: { - skip: 1, - placement: 1, - playbackmethod: 1 - } - }, - })).to.equal(false); - - expect(spec.isBidRequestValid({ - bidder: 'criteo', - mediaTypes: { - video: { - context: 'adpod', - mimes: ['video/mpeg'], - playerSize: [640, 480], - protocols: [5, 6], - maxduration: 30, - api: [1, 2] - } - }, - params: { - networkId: 456, - video: { - skip: 1, - placement: 1, - playbackmethod: 1 - } - }, - })).to.equal(false); - expect(spec.isBidRequestValid({ bidder: 'criteo', mediaTypes: { From b6d7b0ad6f02f3e25b2d789509d52a61e3e7e15b Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Wed, 10 Jan 2024 01:19:05 +0800 Subject: [PATCH 320/760] Discovery Bid Adapter : add title, description, keywords (#10900) * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test resolve conflict * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 74 ++++++ test/spec/modules/discoveryBidAdapter_spec.js | 229 +++++++++++++++++- 2 files changed, 302 insertions(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 315df48fa5d..551c78c1e24 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -58,6 +58,65 @@ const NATIVERET = { }; /** + * get page title + * @returns {string} + */ + +export function getPageTitle(win = window) { + try { + const ogTitle = win.top.document.querySelector('meta[property="og:title"]') + return win.top.document.title || (ogTitle && ogTitle.content) || ''; + } catch (e) { + const ogTitle = document.querySelector('meta[property="og:title"]') + return document.title || (ogTitle && ogTitle.content) || ''; + } +} + +/** + * get page description + * @returns {string} + */ +export function getPageDescription(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="description"]') || + win.top.document.querySelector('meta[property="og:description"]') + } catch (e) { + element = document.querySelector('meta[name="description"]') || + document.querySelector('meta[property="og:description"]') + } + + return (element && element.content) || ''; +} + +/** + * get page keywords + * @returns {string} + */ +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return (element && element.content) || ''; +} + +/** + * get connection downlink + * @returns {number} + */ +export function getConnectionDownLink(win = window) { + const nav = win.navigator || {}; + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; +} + +/** + * get pmg uid * 获取并生成用户的id * @return {string} */ @@ -362,6 +421,10 @@ function getParam(validBidRequests, bidderRequest) { const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const firstPartyData = bidderRequest.ortb2; + const topWindow = window.top; + const title = getPageTitle(); + const desc = getPageDescription(); + const keywords = getPageKeywords(); if (items && items.length) { let c = { @@ -384,6 +447,17 @@ function getParam(validBidRequests, bidderRequest) { firstPartyData, ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, pmguid: getPmgUID(), + page: { + title: title ? title.slice(0, 100) : undefined, + desc: desc ? desc.slice(0, 300) : undefined, + keywords: keywords ? keywords.slice(0, 100) : undefined, + hLen: topWindow.history?.length || undefined, + }, + device: { + nbw: getConnectionDownLink(), + hc: topWindow.navigator?.hardwareConcurrency || undefined, + dm: topWindow.navigator?.deviceMemory || undefined, + } }, user: { buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index acfc519bef9..05216ff126c 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, getPmgUID, storage } from 'modules/discoveryBidAdapter.js'; +import { spec, getPmgUID, storage, getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { @@ -218,3 +218,230 @@ describe('discovery:BidAdapterTests', function () { }); }); }); + +describe('discovery Bid Adapter Tests', function () { + describe('buildRequests', () => { + describe('getPageTitle function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document title if available', function() { + const fakeTopDocument = { + title: 'Top Document Title', + querySelector: () => ({ content: 'Top Document Title test' }) + }; + const fakeTopWindow = { + document: fakeTopDocument + }; + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal('Top Document Title'); + }); + + it('should return the content of top og:title meta tag if title is empty', function() { + const ogTitleContent = 'Top OG Title Content'; + const fakeTopWindow = { + document: { + title: '', + querySelector: sandbox.stub().withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }) + } + }; + + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return the document title if no og:title meta tag is present', function() { + document.title = 'Test Page Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + + const result = getPageTitle({ top: undefined }); + expect(result).to.equal('Test Page Title'); + }); + + it('should return the content of og:title meta tag if present', function() { + document.title = ''; + const ogTitleContent = 'Top OG Title Content'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return an empty string if no title or og:title meta tag is found', function() { + document.title = ''; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(''); + }); + + it('should handle exceptions when accessing top.document and fallback to current document', function() { + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const ogTitleContent = 'Current OG Title Content'; + document.title = 'Current Document Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle(fakeWindow); + expect(result).to.equal('Current Document Title'); + }); + }); + + describe('getPageDescription function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document description if available', function() { + const descriptionContent = 'Top Document Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[name="description"]').returns({ content: descriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(descriptionContent); + }); + + it('should return the top document og:description if description is not present', function() { + const ogDescriptionContent = 'Top OG Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(ogDescriptionContent); + }); + + it('should return the current document description if top document is not accessible', function() { + const descriptionContent = 'Current Document Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="description"]').returns({ content: descriptionContent }) + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(descriptionContent); + }); + + it('should return the current document og:description if description is not present and top document is not accessible', function() { + const ogDescriptionContent = 'Current OG Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }); + + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(ogDescriptionContent); + }); + }); + + describe('getPageKeywords function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document keywords if available', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + const fakeTopDocument = { + querySelector: sandbox.stub() + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + + const result = getPageKeywords({ top: fakeTopWindow }); + expect(result).to.equal(keywordsContent); + }); + + it('should return the current document keywords if top document is not accessible', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }); + + // 模拟顶层窗口访问异常 + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + + const result = getPageKeywords(fakeWindow); + expect(result).to.equal(keywordsContent); + }); + + it('should return an empty string if no keywords meta tag is found', function() { + sandbox.stub(document, 'querySelector').withArgs('meta[name="keywords"]').returns(null); + + const result = getPageKeywords(); + expect(result).to.equal(''); + }); + }); + describe('getConnectionDownLink function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the downlink value as a string if available', function() { + const downlinkValue = 2.5; + const fakeNavigator = { + connection: { + downlink: downlinkValue + } + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.equal(downlinkValue.toString()); + }); + + it('should return undefined if downlink is not available', function() { + const fakeNavigator = { + connection: {} + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should return undefined if connection is not available', function() { + const fakeNavigator = {}; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should handle cases where navigator is not defined', function() { + const result = getConnectionDownLink({}); + expect(result).to.be.undefined; + }); + }); + }); +}); From cf1f150f74c2dfdba97800d45cabcf74a5c9c16c Mon Sep 17 00:00:00 2001 From: vicenteampliffy Date: Tue, 9 Jan 2024 18:35:02 +0100 Subject: [PATCH 321/760] Ampliffy Bid Adapter: initial commit (#10883) * ampliffyBidAdapter initial commit * Add tests --- modules/ampliffyBidAdapter.js | 419 +++++++++++++++++ modules/ampliffyBidAdapter.md | 39 ++ test/spec/modules/ampliffyBidAdapter_spec.js | 453 +++++++++++++++++++ 3 files changed, 911 insertions(+) create mode 100644 modules/ampliffyBidAdapter.js create mode 100644 modules/ampliffyBidAdapter.md create mode 100644 test/spec/modules/ampliffyBidAdapter_spec.js diff --git a/modules/ampliffyBidAdapter.js b/modules/ampliffyBidAdapter.js new file mode 100644 index 00000000000..bcd28e5bcf1 --- /dev/null +++ b/modules/ampliffyBidAdapter.js @@ -0,0 +1,419 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {logError, logInfo, triggerPixel} from '../src/utils.js'; + +const BIDDER_CODE = 'ampliffy'; +const GVLID = 1258; +const DEFAULT_ENDPOINT = 'bidder.ampliffy.com'; +const TTL = 600; // Time-to-Live - how long (in seconds) Prebid can use this bid. +const LOG_PREFIX = 'AmpliffyBidder: '; + +function isBidRequestValid(bid) { + logInfo(LOG_PREFIX + 'isBidRequestValid: Code: ' + bid.adUnitCode + ': Param' + JSON.stringify(bid.params), bid.adUnitCode); + if (bid.params) { + if (!bid.params.placementId || !bid.params.format) return false; + + if (bid.params.format.toLowerCase() !== 'video' && bid.params.format.toLowerCase() !== 'display' && bid.params.format.toLowerCase() !== 'all') return false; + if (bid.params.format.toLowerCase() === 'video' && !bid.mediaTypes['video']) return false; + if (bid.params.format.toLowerCase() === 'display' && !bid.mediaTypes['banner']) return false; + + if (!bid.params.server || bid.params.server === '') { + const server = bid.params.type + bid.params.region + bid.params.adnetwork; + if (server && server !== '') bid.params.server = server; + else bid.params.server = DEFAULT_ENDPOINT; + } + return true; + } + return false; +} + +function manageConsentArguments(bidderRequest) { + let consent = null; + if (bidderRequest?.gdprConsent) { + consent = { + gdpr: bidderRequest.gdprConsent.gdprApplies ? '1' : '0', + }; + if (bidderRequest.gdprConsent.consentString) { + consent.consent_string = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + consent.addtl_consent = bidderRequest.gdprConsent.addtlConsent; + } + } + return consent; +} + +function buildRequests(validBidRequests, bidderRequest) { + const bidRequests = []; + for (const bidRequest of validBidRequests) { + for (const sizes of bidRequest.sizes) { + let extraParams = mergeParams(getDefaultParams(), bidRequest.params.extraParams); + // Apply GDPR parameters to request. + extraParams = mergeParams(extraParams, manageConsentArguments(bidderRequest)); + const serverURL = getServerURL(bidRequest.params.server, sizes, bidRequest.params.placementId, extraParams); + logInfo(LOG_PREFIX + serverURL, 'requests'); + extraParams.bidId = bidRequest.bidId; + bidRequests.push({ + method: 'GET', + url: serverURL, + data: extraParams, + bidRequest, + }); + } + logInfo(LOG_PREFIX + 'Building request from: ' + bidderRequest.url + ': ' + JSON.stringify(bidRequests), bidRequest.adUnitCode); + } + return bidRequests; +} +export function getDefaultParams() { + return { + ciu_szs: '1x1', + gdfp_req: '1', + env: 'vp', + output: 'xml_vast4', + unviewed_position_start: '1' + }; +} +export function mergeParams(params, extraParams) { + if (extraParams) { + for (const k in extraParams) { + params[k] = extraParams[k]; + } + } + return params; +} +export function paramsToQueryString(params) { + return Object.entries(params).filter(e => typeof e[1] !== 'undefined').map(e => { + if (e[1]) return encodeURIComponent(e[0]) + '=' + encodeURIComponent(e[1]); + else return encodeURIComponent(e[0]); + }).join('&'); +} +const getCacheBuster = () => Math.floor(Math.random() * (9999999999 - 1000000000)); + +// For testing purposes +let currentUrl = null; +export function getCurrentURL() { + if (!currentUrl) currentUrl = top.location.href; + return currentUrl; +} +export function setCurrentURL(url) { + currentUrl = url; +} +const getCurrentURLEncoded = () => encodeURIComponent(getCurrentURL()); +function getServerURL(server, sizes, iu, queryParams) { + const random = getCacheBuster(); + const size = sizes[0] + 'x' + sizes[1]; + let serverURL = '//' + server + '/gampad/ads'; + queryParams.sz = size; + queryParams.iu = iu; + queryParams.url = getCurrentURL(); + queryParams.description_url = getCurrentURL(); + queryParams.correlator = random; + + return serverURL; +} +function interpretResponse(serverResponse, bidRequest) { + const bidResponses = []; + + const bidResponse = {}; + let mediaType = 'video'; + if ( + bidRequest.bidRequest?.mediaTypes && + !bidRequest.bidRequest.mediaTypes['video'] + ) { + mediaType = 'banner'; + } + bidResponse.requestId = bidRequest.bidRequest.bidId; + bidResponse.width = bidRequest.bidRequest?.sizes[0][0]; + bidResponse.height = bidRequest.bidRequest?.sizes[0][1]; + bidResponse.ttl = TTL; + bidResponse.creativeId = 'ampCreativeID134'; + bidResponse.netRevenue = true; + bidResponse.mediaType = mediaType; + bidResponse.meta = { + advertiserDomains: [], + }; + let xmlStr = serverResponse.body; + const xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + const xmlData = parseXML(xml, bidResponse); + logInfo(LOG_PREFIX + 'Response from: ' + bidRequest.url + ': ' + JSON.stringify(xmlData), bidRequest.bidRequest.adUnitCode); + if (xmlData.cpm < 0 || !xmlData.creativeURL || !xmlData.bidUp) { + return []; + } + bidResponse.cpm = xmlData.cpm; + bidResponse.currency = xmlData.currency; + + if (mediaType === 'video') { + logInfo(LOG_PREFIX + xmlData.creativeURL, 'requests'); + bidResponse.vastUrl = xmlData.creativeURL; + } else { + bidResponse.adUrl = xmlData.creativeURL; + } + if (xmlData.trackingUrl) { + bidResponse.vastImpUrl = xmlData.trackingUrl; + bidResponse.trackingUrl = xmlData.trackingUrl; + } + bidResponses.push(bidResponse); + return bidResponses; +} +const replaceMacros = (txt, cpm, bid) => { + const size = bid.width + 'x' + bid.height; + txt = txt.replaceAll('%%CACHEBUSTER%%', getCacheBuster()); + txt = txt.replaceAll('@@CACHEBUSTER@@', getCacheBuster()); + txt = txt.replaceAll('%%REFERER%%', getCurrentURLEncoded()); + txt = txt.replaceAll('@@REFERER@@', getCurrentURLEncoded()); + txt = txt.replaceAll('%%REFERRER_URL_UNESC%%', getCurrentURLEncoded()); + txt = txt.replaceAll('@@REFERRER_URL_UNESC@@', getCurrentURLEncoded()); + txt = txt.replaceAll('%%PRICE_ESC%%', encodePrice(cpm)); + txt = txt.replaceAll('@@PRICE_ESC@@', encodePrice(cpm)); + txt = txt.replaceAll('%%SIZES%%', size); + txt = txt.replaceAll('@@SIZES@@', size); + return txt; +} +const encodePrice = (price) => { + price = parseFloat(price); + const s = 116.54; + const c = 1; + const a = 1; + let encodedPrice = s * Math.log10(price + a) + c; + encodedPrice = Math.min(200, encodedPrice); + encodedPrice = Math.round(Math.max(1, encodedPrice)); + + // Format the encoded price with leading zeros if necessary + const formattedEncodedPrice = encodedPrice.toString().padStart(3, '0'); + + // Build the encoding key + const encodingKey = `H--${formattedEncodedPrice}`; + + return encodeURIComponent(`vch=${encodingKey}`); +}; + +function extractCT(xml) { + let ct = null; + try { + try { + const vastAdTagURI = xml.getElementsByTagName('VASTAdTagURI')[0] + if (vastAdTagURI) { + let url = null; + for (const childNode of vastAdTagURI.childNodes) { + if (childNode.nodeValue.trim().includes('http')) { + url = decodeURIComponent(childNode.nodeValue); + } + } + const urlParams = new URLSearchParams(url); + ct = urlParams.get('ct') + } + } catch (e) { + } + if (!ct) { + const geoExtensions = xml.querySelectorAll('Extension[type="geo"]'); + geoExtensions.forEach((geoExtension) => { + const countryElement = geoExtension.querySelector('Country'); + if (countryElement) { + ct = countryElement.textContent; + } + }); + } + } catch (e) {} + return ct; +} + +function extractCPM(htmlContent, ct, cpm) { + const cpmMapDiv = htmlContent.querySelectorAll('[cpmMap]')[0]; + if (cpmMapDiv) { + let cpmMapJSON = JSON.parse(cpmMapDiv.getAttribute('cpmMap')); + if ((cpmMapJSON)) { + if (cpmMapJSON[ct]) { + cpm = cpmMapJSON[ct]; + } else if (cpmMapJSON['default']) { + cpm = cpmMapJSON['default']; + } + } + } + return cpm; +} + +function extractCurrency(htmlContent, currency) { + const currencyDiv = htmlContent.querySelectorAll('[cpmCurrency]')[0]; + if (currencyDiv) { + const currencyValue = currencyDiv.getAttribute('cpmCurrency'); + if (currencyValue && currencyValue !== '') { + currency = currencyValue; + } + } + return currency; +} + +function extractCreativeURL(htmlContent, ct, cpm, bid) { + let creativeURL = null; + const creativeMap = htmlContent.querySelectorAll('[creativeMap]')[0]; + if (creativeMap) { + const creativeMapString = creativeMap.getAttribute('creativeMap'); + + const creativeMapJSON = JSON.parse(creativeMapString); + let defaultURL = null; + for (const url of Object.keys(creativeMapJSON)) { + const geo = creativeMapJSON[url]; + if (geo.includes(ct)) { + creativeURL = replaceMacros(url, cpm, bid); + } else if (geo.includes('default')) { + defaultURL = url; + } + } + if (!creativeURL && defaultURL) creativeURL = replaceMacros(defaultURL, cpm, bid); + } + return creativeURL; +} + +function extractSyncs(htmlContent) { + let userSyncsJSON = null; + const userSyncs = htmlContent.querySelectorAll('[userSyncs]')[0]; + if (userSyncs) { + const userSyncsString = userSyncs.getAttribute('userSyncs'); + + userSyncsJSON = JSON.parse(userSyncsString); + } + return userSyncsJSON; +} + +function extractTrackingURL(htmlContent, ret) { + const trackingUrlDiv = htmlContent.querySelectorAll('[bidder-tracking-url]')[0]; + if (trackingUrlDiv) { + const trackingUrl = trackingUrlDiv.getAttribute('bidder-tracking-url'); + // eslint-disable-next-line no-console + logInfo(LOG_PREFIX + 'parseXML: trackingUrl: ', trackingUrl) + ret.trackingUrl = trackingUrl; + } +} + +export function parseXML(xml, bid) { + const ret = { cpm: 0.001, currency: 'EUR', creativeURL: null, bidUp: false }; + const ct = extractCT(xml); + if (!ct) return ret; + + try { + if (ct) { + const companion = xml.getElementsByTagName('Companion')[0]; + const htmlResource = companion.getElementsByTagName('HTMLResource')[0]; + const htmlContent = document.createElement('html'); + htmlContent.innerHTML = htmlResource.textContent; + + ret.cpm = extractCPM(htmlContent, ct, ret.cpm); + ret.currency = extractCurrency(htmlContent, ret.currency); + ret.creativeURL = extractCreativeURL(htmlContent, ct, ret.cpm, bid); + extractTrackingURL(htmlContent, ret); + ret.bidUp = isAllowedToBidUp(htmlContent, getCurrentURL()); + ret.userSyncs = extractSyncs(htmlContent); + } + } catch (e) { + // eslint-disable-next-line no-console + logError(LOG_PREFIX + 'Error parsing XML', e); + } + // eslint-disable-next-line no-console + logInfo(LOG_PREFIX + 'parseXML RET:', ret); + + return ret; +} +export function isAllowedToBidUp(html, currentURL) { + currentURL = currentURL.split('?')[0]; // Remove parameters + let allowedToPush = false; + try { + const domainsMap = html.querySelectorAll('[domainMap]')[0]; + if (domainsMap) { + let domains = JSON.parse(domainsMap.getAttribute('domainMap')); + if (domains.domainMap) { + domains = domains.domainMap; + } + domains.forEach((d) => { + if (currentURL.includes(d) || d === 'all' || d === '*') allowedToPush = true; + }) + } else { + allowedToPush = true; + } + if (allowedToPush) { + const excludedURL = html.querySelectorAll('[excludedURLs]')[0]; + if (excludedURL) { + const excludedURLsString = domainsMap.getAttribute('excludedURLs'); + if (excludedURLsString !== '') { + let excluded = JSON.parse(excludedURLsString); + excluded.forEach((d) => { + if (currentURL.includes(d)) allowedToPush = false; + }) + } + } + } + } catch (e) { + // eslint-disable-next-line no-console + logError(LOG_PREFIX + 'isAllowedToBidUp', e); + } + return allowedToPush; +} + +function getSyncData(options, syncs) { + const ret = []; + if (syncs?.length) { + for (const sync of syncs) { + if (sync.type === 'syncImage' && options.pixelEnabled) { + ret.push({url: sync.url, type: 'image'}); + } else if (sync.type === 'syncIframe' && options.iframeEnabled) { + ret.push({url: sync.url, type: 'iframe'}); + } + } + } + return ret; +} + +function getUserSyncs(syncOptions, serverResponses) { + const userSyncs = []; + for (const serverResponse of serverResponses) { + if (serverResponse.body) { + try { + const xmlStr = serverResponse.body; + const xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + const xmlData = parseXML(xml, {}); + if (xmlData.userSyncs) { + userSyncs.push(...getSyncData(syncOptions, xmlData.userSyncs)); + } + } catch (e) {} + } + } + return userSyncs; +} + +function onBidWon(bid) { + logInfo(`${LOG_PREFIX} WON AMPLIFFY`); + if (bid.trackingUrl) { + let url = bid.trackingUrl; + + // Replace macros with URL-encoded bid parameters + Object.keys(bid).forEach(key => { + const macroKey = `%%${key.toUpperCase()}%%`; + const value = encodeURIComponent(JSON.stringify(bid[key])); + url = url.split(macroKey).join(value); + }); + + triggerPixel(url, () => { + logInfo(`${LOG_PREFIX} send data success`); + }, + (e) => { + logError(`${LOG_PREFIX} send data error`, e); + }); + } +} +function onTimeOut() { + // eslint-disable-next-line no-console + logInfo(LOG_PREFIX + 'TIMEOUT'); +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ['ampliffy', 'amp', 'videoffy', 'publiffy'], + supportedMediaTypes: ['video', 'banner'], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onTimeOut, + onBidWon, +}; + +registerBidder(spec); diff --git a/modules/ampliffyBidAdapter.md b/modules/ampliffyBidAdapter.md new file mode 100644 index 00000000000..a425d910582 --- /dev/null +++ b/modules/ampliffyBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +``` +Module Name: Ampliffy Bidder Adapter +Module Type: Bidder Adapter +Maintainer: bidder@ampliffy.com +``` + +# Description + +Connects to Ampliffy Ad server for bids. + +Ampliffy bid adapter supports Video currently, and has initial support for Banner. + +For more information about [Ampliffy](https://www.ampliffy.com/en/), please contact [info@ampliffy.com](info@ampliffy.com). + +# Sample Ad Unit: For Publishers +```javascript +var videoAdUnit = [ +{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: '1213213/example/vrutal_/', + format: 'video' + } + }] +}]; +``` + +``` diff --git a/test/spec/modules/ampliffyBidAdapter_spec.js b/test/spec/modules/ampliffyBidAdapter_spec.js new file mode 100644 index 00000000000..5b86f692d7e --- /dev/null +++ b/test/spec/modules/ampliffyBidAdapter_spec.js @@ -0,0 +1,453 @@ +import { + parseXML, + isAllowedToBidUp, + spec, + getDefaultParams, + mergeParams, + paramsToQueryString, setCurrentURL +} from 'modules/ampliffyBidAdapter.js'; +import {expect} from 'chai'; +import {BANNER, VIDEO} from 'src/mediaTypes'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('Ampliffy bid adapter Test', 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'); + }); + }); + // Global definitions for all tests + const xmlStr = ` + + + + ]]> + + + + ES + `; + const xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + let companion = xml.getElementsByTagName('Companion')[0]; + let htmlResource = companion.getElementsByTagName('HTMLResource')[0]; + let htmlContent = document.createElement('html'); + htmlContent.innerHTML = htmlResource.textContent; + + describe('Is allowed to bid up', function () { + it('Should return true using a URL that is in domainMap', () => { + let allowedToBidUp = isAllowedToBidUp(htmlContent, 'https://testSports.com?id=131313&text=aaaaa&foo=foo'); + expect(allowedToBidUp).to.be.true; + }) + + it('Should return false using an url that is not in domainMap', () => { + let allowedToBidUp = isAllowedToBidUp(htmlContent, 'https://test.com'); + expect(allowedToBidUp).to.be.false; + }) + + it('Should return false using an url that is excluded.', () => { + let allowedToBidUp = isAllowedToBidUp(htmlContent, 'https://www.no-allowed.com/busqueda/sexo/sexo?test=1#item1'); + expect(allowedToBidUp).to.be.false; + }) + }) + + describe('Helper functions', function () { + it('Should default params not to be null', () => { + const defaultParams = getDefaultParams(); + + expect(defaultParams).not.to.be.null; + }) + it('Should the merge two object params into a new object', () => { + const params1 = { + 'hello': 'world', + 'ampTest': 'this will be replaced' + } + const params2 = { + 'test': 1, + 'ampTest': 'This will be replace the param with the same name in other array' + } + const allParams = mergeParams(params1, params2); + + const paramsComplete = + { + 'hello': 'world', + 'ampTest': 'This will be replace the param with the same name in other array', + 'test': 1, + } + expect(allParams).not.to.be.null; + expect(JSON.stringify(allParams)).to.equal(JSON.stringify(paramsComplete)); + }) + it('Params to QueryString', () => { + const params = { + 'test': 1, + 'ampTest': 'ret', + 'empty': null, + 'quoteMark': '?', + 'test1': undefined + } + const queryString = paramsToQueryString(params); + + expect(queryString).not.to.be.null; + expect(queryString).to.equal('test=1&Test=ret&empty"eMark=%3F'); + }) + }) + + describe('isBidRequestValid', function () { + it('Should return true when required params found', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'all' + }, + mediaTypes: { + banner: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + it('Should return false when param format is display but mediaTypes are for video', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'display' + }, + mediaTypes: { + video: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }) + it('Should return false when param format is video but mediaTypes are for banner', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'video' + }, + mediaTypes: { + banner: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }) + it('Should return true when param format is video and mediaTypes are for video', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'video' + }, + mediaTypes: { + video: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + it('Should return true when param format is display and mediaTypes are for banner', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'display' + }, + mediaTypes: { + banner: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + it('Should return true when param format is all and mediaTypes are for banner', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'all' + }, + mediaTypes: { + banner: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + it('Should return true when param format is all and mediaTypes are for video', function () { + const bidRequest = { + bidder: 'ampliffy', + params: { + server: 'bidder.ampliffy.com', + placementId: 1235465798, + format: 'all' + }, + mediaTypes: { + video: { + sizes: [1, 1] + } + }, + } + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + it('Should return false without placementId param', function () { + const bidRequest = { + bidder: 'ampliffy', + params: {} + } + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }) + it('Should return false without param object', function () { + const bidRequest = { + bidder: 'ampliffy', + } + expect(spec.isBidRequestValid(bidRequest)).to.be.false; + }) + }); + + describe('Build request function', function () { + const bidderRequest = { + 'bidderCode': 'ampliffy', + 'auctionId': 'c4a771bf-1791-4513-82b3-96c48d19ddff', + 'bidderRequestId': '1134bdcbe47f25', + 'bids': [{ + 'bidder': 'ampliffy', + 'params': { + 'placementId': 1235465798, + 'type': 'bidder.', + 'region': 'alan-development.k8s.', + 'adnetwork': 'ampliffy.com', + 'SERVER': 'bidder.ampliffy.com' + }, + 'crumbs': {'pubcid': '29844d69-c4e5-4b00-8602-6dd09815363a'}, + 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video1'}}}, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [[640, 480]], + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'playbackmethod': [2], + 'skip': 1 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'f85c1b10-bad3-4c3f-a2bb-2c484c405bc9', + 'sizes': [[640, 480]], + 'bidId': '2bc71d9c058842', + 'bidderRequestId': '1134bdcbe47f25', + 'auctionId': 'c4a771bf-1791-4513-82b3-96c48d19ddff', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }], + 'auctionStart': 1644029483655, + 'timeout': 3000, + 'refererInfo': { + 'referer': 'http://localhost:9999/integrationExamples/gpt/hello_world_video.html?pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': ['http://localhost:9999/integrationExamples/gpt/hello_world_video.html?pbjs_debug=true'], + 'canonicalUrl': null + }, + 'start': 1644029483708 + } + const validBidRequests = [ + { + 'bidder': 'ampliffy', + 'params': { + 'placementId': 1235465798, + 'type': 'bidder.', + 'region': 'alan-development.k8s.', + 'adnetwork': 'ampliffy.com', + 'SERVER': 'bidder.ampliffy.com' + }, + 'crumbs': {'pubcid': '29844d69-c4e5-4b00-8602-6dd09815363a'}, + 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video1'}}}, + 'mediaTypes': { + 'video': { + 'context': 'instream', + 'playerSize': [[640, 480]], + 'mimes': ['video/mp4'], + 'protocols': [1, 2, 3, 4, 5, 6, 7, 8], + 'playbackmethod': [2], + 'skip': 1 + } + }, + 'adUnitCode': 'video1', + 'transactionId': 'f85c1b10-bad3-4c3f-a2bb-2c484c405bc9', + 'sizes': [[640, 480]], + 'bidId': '2bc71d9c058842', + 'bidderRequestId': '1134bdcbe47f25', + 'auctionId': 'c4a771bf-1791-4513-82b3-96c48d19ddff', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ]; + it('Should return one or more bid requests', function () { + expect(spec.buildRequests(validBidRequests, bidderRequest).length).to.be.greaterThan(0); + }); + }) + describe('Interpret response', function () { + let bidRequest = { + bidRequest: { + adUnitCode: 'div-gpt-ad-1460505748561-0', + auctionId: '469bb2e2-351f-4d01-b782-cdbca5e3e0ed', + bidId: '2d40b8dcd02ade', + bidRequestsCount: 1, + bidder: 'ampliffy', + bidderRequestId: '128c07edc4680f', + bidderRequestsCount: 1, + bidderWinsCount: 0, + crumbs: { + pubcid: '29844d69-c4e5-4b00-8602-6dd09815363a' + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + ortb2Imp: {ext: {}}, + params: {placementId: 13144370}, + sizes: [ + [300, 250], + [300, 600] + ], + src: 'client', + transactionId: '103b2b58-6ed1-45e9-9486-c942d6042e3' + }, + data: {bidId: '2d40b8dcd02ade'}, + method: 'GET', + url: 'https://test.com', + }; + + it('Should extract a CPM and currency from the xml', () => { + let cpmData = parseXML(xml); + expect(cpmData).to.not.be.a('null'); + expect(cpmData.cpm).to.equal('.23'); + expect(cpmData.currency).to.equal('USD'); + }); + + it('It should return no ads when the CPM is less than zero.', () => { + const xmlStr1 = ` + + + + + + + + +
+
+
+
+ + + ]]> +
+
+ + ES +
+
+
`; + let serverResponse = { + 'body': xmlStr1, + } + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(0); + }) + + it('It should return no ads when the creative url is not in the xml', () => { + const xmlStr1 = ` + + + + + + + + +
+
+
+
+ + ]]> + + + ES + + + `; + let serverResponse = { + 'body': xmlStr1, + } + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(0); + }) + it('It should return a banner ad.', () => { + let serverResponse = { + 'body': xmlStr, + } + setCurrentURL('https://www.sports.com'); + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).greaterThan(0); + expect(bidResponses[0].mediaType).to.be.equal(BANNER); + expect(bidResponses[0].ad).not.to.be.null; + }) + it('It should return a video ad.', () => { + let serverResponse = { + 'body': xmlStr, + } + setCurrentURL('https://www.sports.com'); + bidRequest.bidRequest.mediaTypes = { + video: { + sizes: [ + [300, 250], + [300, 600] + ] + } + } + const bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).greaterThan(0); + expect(bidResponses[0].mediaType).to.be.equal(VIDEO); + expect(bidResponses[0].vastUrl).not.to.be.null; + }) + }); +}); From b93416b2a6838d7ae54691cd9d9167d336386e5c Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 9 Jan 2024 12:24:57 -0700 Subject: [PATCH 322/760] Utils.js (Warning in Integration Tests): add back getWindowFromDocument (#10865) * add back getWindowFromDocument * do not use core for 1 consumer --------- Co-authored-by: Demetrio Girardi --- modules/tpmnBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 89951a99f12..e3adb80fb2f 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -184,7 +184,7 @@ function createRenderer(bid) { function outstreamRender(bid, doc) { bid.renderer.push(() => { - const win = utils.getWindowFromDocument(doc) || window; + const win = (doc) ? doc.defaultView : window; win.ANOutstreamVideo.renderAd({ sizes: [bid.playerWidth, bid.playerHeight], targetId: bid.adUnitCode, From 2b25ec49305248d1565d09d15c4528615cede6ab Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 9 Jan 2024 14:28:54 -0500 Subject: [PATCH 323/760] Rubicon: remove transformBidParams function (#10919) Per https://github.com/prebid/Prebid.js/issues/6361 Confirmed that the server-side adapters for both PBS-Go and PBS-Java handle the type conversion. --- modules/rubiconBidAdapter.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 226950875e2..e9556b9afc6 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -22,7 +22,6 @@ import { _each } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; @@ -766,19 +765,6 @@ export const spec = { url: `https://${rubiConf.syncHost || 'eus'}.rubiconproject.com/usync.html` + params }; } - }, - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'accountId': 'number', - 'siteId': 'number', - 'zoneId': 'number' - }, params); } }; From 621cedc22a3375882fda416cea206776671f2bb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 06:42:36 -0700 Subject: [PATCH 324/760] Bump follow-redirects from 1.15.2 to 1.15.4 (#10922) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3020105714f..6c288e114d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.30.0-pre", + "version": "8.31.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -11009,9 +11009,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -34011,9 +34011,9 @@ } }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true }, "for-each": { From cdcfe15eba97b9b772e36528521396ede90ef7d2 Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Wed, 10 Jan 2024 09:09:52 -0500 Subject: [PATCH 325/760] Support cdep (#10921) Changes to bid adapter logic and tests to support passing `cdep` along in requests to Sharethrough's adserver endpoint. --- modules/sharethroughBidAdapter.js | 9 +++-- .../modules/sharethroughBidAdapter_spec.js | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 3684e793dcb..53cb67c4e6d 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -45,6 +45,7 @@ export const sharethroughAdapterSpec = { dnt: navigator.doNotTrack === '1' ? 1 : 0, h: window.screen.height, w: window.screen.width, + ext: {}, }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, @@ -63,6 +64,10 @@ export const sharethroughAdapterSpec = { test: 0, }; + if (bidderRequest.ortb2?.device?.ext?.cdep) { + req.device.ext['cdep'] = bidderRequest.ortb2.device.ext.cdep; + } + req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; req.user.ext.eids = bidRequests[0].userIdAsEids || []; @@ -220,9 +225,7 @@ export const sharethroughAdapterSpec = { const shouldCookieSync = syncOptions.pixelEnabled && deepAccess(serverResponses, '0.body.cookieSyncUrls') !== undefined; - return shouldCookieSync - ? serverResponses[0].body.cookieSyncUrls.map((url) => ({ type: 'image', url: url })) - : []; + return shouldCookieSync ? serverResponses[0].body.cookieSyncUrls.map((url) => ({ type: 'image', url: url })) : []; }, // Empty implementation for prebid core to be able to find it diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index eb0b45dda11..6a63ae681e7 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -588,6 +588,43 @@ describe('sharethrough adapter spec', function () { }); }); + describe('cookie deprecation', () => { + it('should not add cdep if we do not get it in an impression request', () => { + const builtRequests = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id', + ortb2: { + device: { + ext: { + propThatIsNotCdep: 'value-we-dont-care-about', + }, + }, + }, + }); + const noCdep = builtRequests.every((builtRequest) => { + const ourCdepValue = builtRequest.data.device?.ext?.cdep; + return ourCdepValue === undefined; + }); + expect(noCdep).to.be.true; + }); + + it('should add cdep if we DO get it in an impression request', () => { + const builtRequests = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id', + ortb2: { + device: { + ext: { + cdep: 'cdep-value', + }, + }, + }, + }); + const cdepPresent = builtRequests.every((builtRequest) => { + return builtRequest.data.device.ext.cdep === 'cdep-value'; + }); + expect(cdepPresent).to.be.true; + }); + }); + describe('first party data', () => { const firstPartyData = { site: { From 74f6bcea65a4c42d3738313663dd0e69bdf9d1b2 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Wed, 10 Jan 2024 16:31:47 +0200 Subject: [PATCH 326/760] Adkernel Bid Adapter: multiformat imp splitting (#10918) * Adkernel: multiformat adunit support * fix --- modules/adkernelBidAdapter.js | 103 +++++++++++++------ test/spec/modules/adkernelBidAdapter_spec.js | 48 +++++++-- 2 files changed, 114 insertions(+), 37 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 9d9da8cb0ab..add24772463 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -67,6 +67,11 @@ const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { return acc; }, {}); +const MULTI_FORMAT_SUFFIX = '__mf'; +const MULTI_FORMAT_SUFFIX_BANNER = 'b' + MULTI_FORMAT_SUFFIX; +const MULTI_FORMAT_SUFFIX_VIDEO = 'v' + MULTI_FORMAT_SUFFIX; +const MULTI_FORMAT_SUFFIX_NATIVE = 'n' + MULTI_FORMAT_SUFFIX; + /** * Adapter for requesting bids from AdKernel white-label display platform */ @@ -173,6 +178,9 @@ export const spec = { ttl: 360, netRevenue: true }; + if (prBid.requestId.endsWith(MULTI_FORMAT_SUFFIX)) { + prBid.requestId = stripMultiformatSuffix(prBid.requestId); + } if ('banner' in imp) { prBid.mediaType = BANNER; prBid.width = rtbBid.w; @@ -239,13 +247,13 @@ registerBidder(spec); function groupImpressionsByHostZone(bidRequests, refererInfo) { let secure = (refererInfo && refererInfo.page?.indexOf('https:') === 0); return Object.values( - bidRequests.map(bidRequest => buildImp(bidRequest, secure)) + bidRequests.map(bidRequest => buildImps(bidRequest, secure)) .reduce((acc, curr, index) => { let bidRequest = bidRequests[index]; let {zoneId, host} = bidRequest.params; let key = `${host}_${zoneId}`; acc[key] = acc[key] || {host: host, zoneId: zoneId, imps: []}; - acc[key].imps.push(curr); + acc[key].imps.push(...curr); return acc; }, {}) ); @@ -264,61 +272,90 @@ function getBidFloor(bid, mediaType, sizes) { } /** - * Builds rtb imp object for single adunit + * Builds rtb imp object(s) for single adunit * @param bidRequest {BidRequest} * @param secure {boolean} */ -function buildImp(bidRequest, secure) { - const imp = { +function buildImps(bidRequest, secure) { + let imp = { 'id': bidRequest.bidId, 'tagid': bidRequest.adUnitCode }; - var mediaType; + if (secure) { + imp.secure = 1; + } var sizes = []; - - if (bidRequest.mediaTypes?.banner) { + let mediaTypes = bidRequest.mediaTypes; + let isMultiformat = (~~!!mediaTypes?.banner + ~~!!mediaTypes?.video + ~~!!mediaTypes?.native) > 1; + let result = []; + let typedImp; + + if (mediaTypes?.banner) { + if (isMultiformat) { + typedImp = {...imp}; + typedImp.id = imp.id + MULTI_FORMAT_SUFFIX_BANNER; + } else { + typedImp = imp; + } sizes = getAdUnitSizes(bidRequest); - let pbBanner = bidRequest.mediaTypes.banner; - imp.banner = { + let pbBanner = mediaTypes.banner; + typedImp.banner = { ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, BANNER_FPD), ...getDefinedParamsOrEmpty(pbBanner, BANNER_PARAMS), format: sizes.map(wh => parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; - mediaType = BANNER; - } else if (bidRequest.mediaTypes?.video) { - let pbVideo = bidRequest.mediaTypes.video; - imp.video = { + initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : BANNER); + result.push(typedImp); + } + + if (mediaTypes?.video) { + if (isMultiformat) { + typedImp = {...imp}; + typedImp.id = typedImp.id + MULTI_FORMAT_SUFFIX_VIDEO; + } else { + typedImp = imp; + } + let pbVideo = mediaTypes.video; + typedImp.video = { ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, VIDEO_FPD), ...getDefinedParamsOrEmpty(pbVideo, VIDEO_PARAMS) }; if (pbVideo.playerSize) { sizes = pbVideo.playerSize[0]; - imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {}); + typedImp.video = Object.assign(typedImp.video, parseGPTSingleSizeArrayToRtbSize(sizes) || {}); } else if (pbVideo.w && pbVideo.h) { - imp.video.w = pbVideo.w; - imp.video.h = pbVideo.h; + typedImp.video.w = pbVideo.w; + typedImp.video.h = pbVideo.h; } - mediaType = VIDEO; - } else if (bidRequest.mediaTypes?.native) { - let nativeRequest = buildNativeRequest(bidRequest.mediaTypes.native); - imp.native = { + initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : VIDEO); + result.push(typedImp); + } + + if (mediaTypes?.native) { + if (isMultiformat) { + typedImp = {...imp}; + typedImp.id = typedImp.id + MULTI_FORMAT_SUFFIX_NATIVE; + } else { + typedImp = imp; + } + let nativeRequest = buildNativeRequest(mediaTypes.native); + typedImp.native = { ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, NATIVE_FPD), ver: '1.1', request: JSON.stringify(nativeRequest) }; - mediaType = NATIVE; - } else { - throw new Error('Unsupported bid received'); - } - let floor = getBidFloor(bidRequest, mediaType, sizes); - if (floor) { - imp.bidfloor = floor; + initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : NATIVE); + result.push(typedImp); } - if (secure) { - imp.secure = 1; + return result; +} + +function initImpBidfloor(imp, bid, sizes, mediaType) { + let bidfloor = getBidFloor(bid, mediaType, sizes); + if (bidfloor) { + imp.bidfloor = bidfloor; } - return imp; } function getDefinedParamsOrEmpty(object, params) { @@ -643,3 +680,7 @@ function buildNativeAd(nativeResp) { }); return cleanObj(nativeAd); } + +function stripMultiformatSuffix(impid) { + return impid.substr(0, impid.length - MULTI_FORMAT_SUFFIX.length - 1); +} diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index ac2e3785780..ade34478c20 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -250,6 +250,31 @@ describe('Adkernel adapter', function () { }], bidid: 'pTuOlf5KHUo', cur: 'EUR' + }, + multiformat_response = { + id: '47ce4badcf7482', + seatbid: [{ + bid: [{ + id: 'sZSYq5zYMxo_0', + impid: 'Bid_01b__mf', + crid: '100_003', + price: 0.00145, + adid: '158801', + adm: '', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', + cid: '16855' + }, { + id: 'sZSYq5zYMxo_1', + impid: 'Bid_01v__mf', + crid: '100_003', + price: 0.25, + adid: '158801', + nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_1&f=nurl', + cid: '16855' + }] + }], + bidid: 'pTuOlf5KHUo', + cur: 'USD' }; var sandbox; @@ -460,18 +485,29 @@ describe('Adkernel adapter', function () { }); describe('multiformat request building', function () { - let _, bidRequests; + let pbRequests, bidRequests; before(function () { - [_, bidRequests] = buildRequest([bid_multiformat]); + [pbRequests, bidRequests] = buildRequest([bid_multiformat]); }); it('should contain single request', function () { expect(bidRequests).to.have.length(1); - expect(bidRequests[0].imp).to.have.length(1); }); - it('should contain banner-only impression', function () { - expect(bidRequests[0].imp).to.have.length(1); + it('should contain both impression', function () { + expect(bidRequests[0].imp).to.have.length(2); expect(bidRequests[0].imp[0]).to.have.property('banner'); - expect(bidRequests[0].imp[0]).to.not.have.property('video'); + expect(bidRequests[0].imp[1]).to.have.property('video'); + // check that splitted imps do not share same impid + expect(bidRequests[0].imp[0].id).to.be.not.eql('Bid_01'); + expect(bidRequests[0].imp[1].id).to.be.not.eql('Bid_01'); + expect(bidRequests[0].imp[1].id).to.be.not.eql(bidRequests[0].imp[0].id); + }); + it('x', function() { + let bids = spec.interpretResponse({body: multiformat_response}, pbRequests[0]); + expect(bids).to.have.length(2); + expect(bids[0].requestId).to.be.eql('Bid_01'); + expect(bids[0].mediaType).to.be.eql('banner'); + expect(bids[1].requestId).to.be.eql('Bid_01'); + expect(bids[1].mediaType).to.be.eql('video'); }); }); From c94eb4e98abbc8fd2488b3d2d76955b294265eef Mon Sep 17 00:00:00 2001 From: GeoEdge-r-and-d <72186958+GeoEdge-r-and-d@users.noreply.github.com> Date: Thu, 11 Jan 2024 06:12:51 +0200 Subject: [PATCH 327/760] Geodge RTD module: update preload mechanism (#10911) * Update preload mechanism to work with an iframe * Update tests --------- Co-authored-by: daniel manan --- modules/geoedgeRtdProvider.js | 36 +++++++++++--- test/spec/modules/geoedgeRtdProvider_spec.js | 51 +++++++++++++------- 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 9bdf74c6e3c..37db5860001 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -17,11 +17,12 @@ import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; -import { generateUUID, insertElement, isEmpty, logError } from '../src/utils.js'; +import { generateUUID, createInvisibleIframe, insertElement, isEmpty, logError } from '../src/utils.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; +import { getRefererInfo } from '../src/refererDetection.js'; /** @type {string} */ const SUBMODULE_NAME = 'geoedge'; @@ -69,17 +70,38 @@ export function setWrapper(responseText) { wrapper = responseText; } +export function getInitialParams(key) { + let refererInfo = getRefererInfo(); + let params = { + wver: 'pbjs', + wtype: 'pbjs-module', + key, + meta: { + topUrl: refererInfo.page + }, + site: refererInfo.domain, + pimp: PV_ID, + fsRan: true, + frameApi: true + }; + return params; +} + +export function markAsLoaded() { + preloaded = true; +} + /** * preloads the client * @param {string} key */ export function preloadClient(key) { - let link = document.createElement('link'); - link.rel = 'preload'; - link.as = 'script'; - link.href = getClientUrl(key); - link.onload = () => { preloaded = true }; - insertElement(link); + let iframe = createInvisibleIframe(); + iframe.id = 'grumiFrame'; + insertElement(iframe); + iframe.contentWindow.grumi = getInitialParams(key); + let url = getClientUrl(key); + loadExternalScript(url, SUBMODULE_NAME, markAsLoaded, iframe.contentDocument); } /** diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index b0307a60a3c..211a3efa3c6 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -1,18 +1,21 @@ import * as utils from '../../../src/utils.js'; import {loadExternalScript} from '../../../src/adloader.js'; -import { +import * as geoedgeRtdModule from '../../../modules/geoedgeRtdProvider.js'; +import {server} from '../../../test/mocks/xhr.js'; +import * as events from '../../../src/events.js'; +import CONSTANTS from '../../../src/constants.json'; + +let { geoedgeSubmodule, getClientUrl, getInPageUrl, htmlPlaceholder, setWrapper, getMacros, - wrapper, - WRAPPER_URL -} from '../../../modules/geoedgeRtdProvider.js'; -import {server} from '../../../test/mocks/xhr.js'; -import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; + WRAPPER_URL, + preloadClient, + markAsLoaded +} = geoedgeRtdModule; let key = '123123123'; function makeConfig(gpt) { @@ -65,13 +68,11 @@ describe('Geoedge RTD module', function () { }); }); describe('init', function () { - let insertElementStub; - before(function () { - insertElementStub = sinon.stub(utils, 'insertElement'); + sinon.spy(geoedgeRtdModule, 'preloadClient'); }); after(function () { - utils.insertElement.restore(); + geoedgeRtdModule.preloadClient.restore(); }); it('should return false when missing params or key', function () { let missingParams = geoedgeSubmodule.init({}); @@ -87,14 +88,13 @@ describe('Geoedge RTD module', function () { let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; expect(isWrapperRequest).to.equal(true); }); - it('should preload the client', function () { - let isLinkPreloadAsScript = arg => arg.tagName === 'LINK' && arg.rel === 'preload' && arg.as === 'script' && arg.href === getClientUrl(key); - expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.equal(true); + it('should call preloadClient', function () { + expect(preloadClient.called); }); it('should emit billable events with applicable winning bids', function (done) { let counter = 0; events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { - if (event.vendor === 'geoedge' && event.type === 'impression') { + if (event.vendor === geoedgeSubmodule.name && event.type === 'impression') { counter += 1; } expect(counter).to.equal(1); @@ -104,7 +104,7 @@ describe('Geoedge RTD module', function () { }); it('should load the in page code when gpt params is true', function () { geoedgeSubmodule.init(makeConfig(true)); - let isInPageUrl = arg => arg == getInPageUrl(key); + let isInPageUrl = arg => arg === getInPageUrl(key); expect(loadExternalScript.calledWith(sinon.match(isInPageUrl))).to.equal(true); }); it('should set the window.grumi config object when gpt params is true', function () { @@ -112,10 +112,27 @@ describe('Geoedge RTD module', function () { expect(hasGrumiObj && window.grumi.key === key && window.grumi.fromPrebid).to.equal(true); }); }); + describe('preloadClient', function () { + let iframe; + preloadClient(key); + let loadExternalScriptCall = loadExternalScript.getCall(0); + it('should create an invisible iframe and insert it to the DOM', function () { + iframe = document.getElementById('grumiFrame'); + expect(iframe && iframe.style.display === 'none'); + }); + it('should assign params object to the iframe\'s window', function () { + let grumi = iframe.contentWindow.grumi; + expect(grumi.key).to.equal(key); + }); + it('should preload the client into the iframe', function () { + let isClientUrl = arg => arg === getClientUrl(key); + expect(loadExternalScriptCall.calledWithMatch(isClientUrl)).to.equal(true); + }); + }); describe('setWrapper', function () { it('should set the wrapper', function () { setWrapper(mockWrapper); - expect(wrapper).to.equal(mockWrapper); + expect(geoedgeRtdModule.wrapper).to.equal(mockWrapper); }); }); describe('getMacros', function () { From 28ba8bce409d246933bca301db34502ee3b710d3 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Thu, 11 Jan 2024 06:13:06 +0200 Subject: [PATCH 328/760] Add option for no signal bidders (#10867) --- modules/priceFloors.js | 22 ++++- test/spec/modules/priceFloors_spec.js | 118 ++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index f114f142bac..9c3869c480a 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -332,13 +332,29 @@ export function getFloorDataFromAdUnits(adUnits) { }, {}); } +function getNoFloorSignalBidersArray(floorData) { + const { data, enforcement } = floorData + // The data.noFloorSignalBidders higher priority then the enforcment + if (data?.noFloorSignalBidders?.length > 0) { + return data.noFloorSignalBidders + } else if (enforcement?.noFloorSignalBidders?.length > 0) { + return enforcement.noFloorSignalBidders + } + return [] +} + /** * @summary This function takes the adUnits for the auction and update them accordingly as well as returns the rules hashmap for the auction */ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { + const noFloorSignalBiddersArray = getNoFloorSignalBidersArray(floorData) + adUnits.forEach((adUnit) => { adUnit.bids.forEach(bid => { - if (floorData.skipped) { + // check if the bidder is in the no signal list + const isNoFloorSignaled = noFloorSignalBiddersArray.some(bidderName => bidderName === bid.bidder) + if (floorData.skipped || isNoFloorSignaled) { + isNoFloorSignaled && logInfo(`noFloorSignal to ${bid.bidder}`) delete bid.getFloor; } else { bid.getFloor = getFloor; @@ -346,6 +362,7 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { // information for bid and analytics adapters bid.auctionId = auctionId; bid.floorData = { + noFloorSignaled: isNoFloorSignaled, skipped: floorData.skipped, skipRate: deepAccess(floorData, 'data.skipRate') ?? floorData.skipRate, floorMin: floorData.floorMin, @@ -663,7 +680,8 @@ export function handleSetFloorsConfig(config) { 'enforceJS', enforceJS => enforceJS !== false, // defaults to true 'enforcePBS', enforcePBS => enforcePBS === true, // defaults to false 'floorDeals', floorDeals => floorDeals === true, // defaults to false - 'bidAdjustment', bidAdjustment => bidAdjustment !== false, // defaults to true + 'bidAdjustment', bidAdjustment => bidAdjustment !== false, // defaults to true, + 'noFloorSignalBidders', noFloorSignalBidders => noFloorSignalBidders || [] ]), 'additionalSchemaFields', additionalSchemaFields => typeof additionalSchemaFields === 'object' && Object.keys(additionalSchemaFields).length > 0 ? addFieldOverrides(additionalSchemaFields) : undefined, 'data', data => (data && parseFloorData(data, 'setConfig')) || undefined diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index f618e57e60d..a31f07fecef 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -773,6 +773,124 @@ describe('the price floors module', function () { floorProvider: undefined }); }); + it('should not do floor stuff if floors.data is defined by noFloorSignalBidders[]', function() { + handleSetFloorsConfig({ + ...basicFloorConfig, + data: { + ...basicFloorDataLow, + noFloorSignalBidders: ['someBidder', 'someOtherBidder'] + }}); + runStandardAuction(); + validateBidRequests(false, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: undefined, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined, + noFloorSignaled: true + }) + }); + it('should not do floor stuff if floors.enforcement is defined by noFloorSignalBidders[]', function() { + handleSetFloorsConfig({ ...basicFloorConfig, + enforcement: { + enforceJS: true, + noFloorSignalBidders: ['someBidder', 'someOtherBidder'] + }, + data: basicFloorDataLow + }); + runStandardAuction(); + validateBidRequests(false, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: undefined, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined, + noFloorSignaled: true + }) + }); + it('should not do floor stuff and use first floors.data.noFloorSignalBidders if its defined betwen enforcement.noFloorSignalBidders', function() { + handleSetFloorsConfig({ ...basicFloorConfig, + enforcement: { + enforceJS: true, + noFloorSignalBidders: ['someBidder'] + }, + data: { + ...basicFloorDataLow, + noFloorSignalBidders: ['someBidder', 'someOtherBidder'] + } + }); + runStandardAuction(); + validateBidRequests(false, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: undefined, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined, + noFloorSignaled: true + }) + }); + it('it shouldn`t return floor stuff for bidder in the noFloorSignalBidders list', function() { + handleSetFloorsConfig({ ...basicFloorConfig, + enforcement: { + enforceJS: true, + }, + data: { + ...basicFloorDataLow, + noFloorSignalBidders: ['someBidder'] + } + }); + runStandardAuction() + const bidRequestData = exposedAdUnits[0].bids.find(bid => bid.bidder === 'someBidder'); + expect(bidRequestData.hasOwnProperty('getFloor')).to.equal(false); + sinon.assert.match(bidRequestData.floorData, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: undefined, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined, + noFloorSignaled: true + }); + }) + it('it should return floor stuff if we defined wrong bidder name in data.noFloorSignalBidders', function() { + handleSetFloorsConfig({ ...basicFloorConfig, + enforcement: { + enforceJS: true, + }, + data: { + ...basicFloorDataLow, + noFloorSignalBidders: ['randomBiider'] + } + }); + runStandardAuction(); + validateBidRequests(true, { + skipped: false, + floorMin: undefined, + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: undefined, + location: 'setConfig', + skipRate: 0, + fetchStatus: undefined, + floorProvider: undefined, + noFloorSignaled: false + }) + }); it('should use adUnit level data if not setConfig or fetch has occured', function () { handleSetFloorsConfig({ ...basicFloorConfig, From a491505377fba8028fc1ecf837f3e03c565332cd Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:10:03 +0100 Subject: [PATCH 329/760] Criteo bid adapter: add device object to backend request (#10926) --- modules/criteoBidAdapter.js | 1 + test/spec/modules/criteoBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index dfc88ba653f..d91d8c56cf9 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -609,6 +609,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { request.user = bidderRequest.ortb2?.user || {}; request.site = bidderRequest.ortb2?.site || {}; request.app = bidderRequest.ortb2?.app || {}; + request.device = bidderRequest.ortb2?.device || {}; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 89d1d02b9d2..36132fe963b 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1906,6 +1906,22 @@ describe('The Criteo bidding adapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.slots[0].ext).to.not.have.property('ae'); }); + + it('should properly transmit device.ext.cdep if available', function () { + const bidderRequest = { + ortb2: { + device: { + ext: { + cdep: 'cookieDeprecationLabel' + } + } + } + }; + const bidRequests = []; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.device.ext.cdep).to.equal('cookieDeprecationLabel'); + }); }); describe('interpretResponse', function () { From 1c5d9255e810d1baa848628a591053118d2018e3 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 11 Jan 2024 04:13:29 -0800 Subject: [PATCH 330/760] Geolocation RTD provider: make module 'vendorless' for the purposes of TCF (#10931) --- modules/geolocationRtdProvider.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/geolocationRtdProvider.js b/modules/geolocationRtdProvider.js index b46a25e9246..6bfed7ee934 100644 --- a/modules/geolocationRtdProvider.js +++ b/modules/geolocationRtdProvider.js @@ -4,6 +4,7 @@ import { ACTIVITY_TRANSMIT_PRECISE_GEO } from '../src/activities/activities.js'; import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; import { isActivityAllowed } from '../src/activities/rules.js'; import { activityParams } from '../src/activities/activityParams.js'; +import {VENDORLESS_GVLID} from '../src/consentHandler.js'; let permissionsAvailable = true; let geolocation; @@ -54,6 +55,7 @@ function init(moduleConfig) { } export const geolocationSubmodule = { name: 'geolocation', + gvlid: VENDORLESS_GVLID, getBidRequestData: getGeolocationData, init: init, }; From c3401f985a1d5a32010e3d3f40ee9ef45a182132 Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Thu, 11 Jan 2024 17:15:02 +0100 Subject: [PATCH 331/760] LiveConnect Bid Adapter : add prebid version to liveConnect (#10920) * forward trackerVersion * refactoring * adjust test * use a proper lc version * adjust test * refactoring * test case * lint * package-lock * fix test * fix test --- modules/liveIntentIdSystem.js | 1 + package-lock.json | 14 +++++++------- package.json | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 10 ++++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index a1f156ca23d..2b4fc2476f8 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -115,6 +115,7 @@ function initializeLiveConnect(configParams) { } liveConnectConfig.wrapperName = 'prebid'; + liveConnectConfig.trackerVersion = '$prebid.version$'; liveConnectConfig.identityResolutionConfig = identityResolutionConfig; liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; liveConnectConfig.fireEventDelay = configParams.fireEventDelay; diff --git a/package-lock.json b/package-lock.json index 6c288e114d5..3f529edb774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.3.2" + "live-connect-js": "^6.3.4" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", @@ -16426,9 +16426,9 @@ } }, "node_modules/live-connect-js": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.3.tgz", - "integrity": "sha512-njpWQgoJuXhxm+XBJQvT672wR20/qROPUfUKwIXPwEtGXznJYlrUDPJDChEknBiKIo7P4jOeirAMwcZWPCN4ow==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", + "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", "dependencies": { "live-connect-common": "^v3.0.3", "tiny-hashes": "1.0.1" @@ -38231,9 +38231,9 @@ "integrity": "sha512-ZPycT04ROBUvPiksnLTunrKC3ROhBSeO99fQ+4qMIkgKwP2CvS44L7fK+0WFV4nAi+65KbzSng7JWcSlckfw8w==" }, "live-connect-js": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.3.tgz", - "integrity": "sha512-njpWQgoJuXhxm+XBJQvT672wR20/qROPUfUKwIXPwEtGXznJYlrUDPJDChEknBiKIo7P4jOeirAMwcZWPCN4ow==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.3.4.tgz", + "integrity": "sha512-lg2XeCaj/eEbK66QGGDEdz9IdT/K3ExZ83Qo6xGVLdP5XJ33xAUCk/gds34rRTmpIwUfAnboOpyj3UoYtS3QUQ==", "requires": { "live-connect-common": "^v3.0.3", "tiny-hashes": "1.0.1" diff --git a/package.json b/package.json index dda2e4e3b38..70ebfb0898c 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "just-clone": "^1.0.2", - "live-connect-js": "^6.3.2" + "live-connect-js": "^6.3.4" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 4af88e1a894..3af598c5d4e 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -94,6 +94,16 @@ describe('LiveIntentId', function() { }, 200); }); + it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { + liveIntentIdSubmodule.decode({}, { params: { + ...defaultConfigParams + }}); + setTimeout(() => { + expect(server.requests[0].url).to.contain('tv=$prebid.version$') + done(); + }, 200); + }); + it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { ...defaultConfigParams.params, From 000a0ad0fd3c8d3f23e4b63ed104dd2f46c8855a Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Thu, 11 Jan 2024 21:52:54 +0530 Subject: [PATCH 332/760] Insticator Bid Adaptor : add support for playerSize, plcmt, vastXML, & vastURL (#10903) * enhance insticatorBidAdapter: - support playerSize for video - Support plcmt tag for video * conditionall check for playerSize * - remove hardcoded banner type for video - Support vastXml and vasturl for video bids * remove trailing spaces * refactor spaces * add unit tests --- modules/insticatorBidAdapter.js | 78 +++++++++++- .../spec/modules/insticatorBidAdapter_spec.js | 120 ++++++++++++++++++ 2 files changed, 191 insertions(+), 7 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index a18c893b5fc..193337d1503 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -68,17 +68,42 @@ function buildBanner(bidRequest) { } function buildVideo(bidRequest) { - const w = deepAccess(bidRequest, 'mediaTypes.video.w'); - const h = deepAccess(bidRequest, 'mediaTypes.video.h'); + let w = deepAccess(bidRequest, 'mediaTypes.video.w'); + let h = deepAccess(bidRequest, 'mediaTypes.video.h'); const mimes = deepAccess(bidRequest, 'mediaTypes.video.mimes'); const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3; + const plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt') || undefined; + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + + if (!w && playerSize) { + if (Array.isArray(playerSize[0])) { + w = parseInt(playerSize[0][0], 10); + } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { + w = parseInt(playerSize[0], 10); + } + } + if (!h && playerSize) { + if (Array.isArray(playerSize[0])) { + h = parseInt(playerSize[0][1], 10); + } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { + h = parseInt(playerSize[1], 10); + } + } - return { + let videoObj = { placement, mimes, w, h, } + + if (plcmt) { + videoObj = { + ...videoObj, + plcmt + } + } + return videoObj } function buildImpression(bidRequest) { @@ -235,7 +260,11 @@ function buildBid(bid, bidderRequest) { meta.advertiserDomains = bid.adomain } - return { + let mediaType = 'banner'; + if (bid.adm && bid.adm.includes(' 0 ? {meta} : {}) }; + + if (mediaType === 'video') { + bidResponse.vastXml = bid.adm; + } + + // Inticator bid adaptor only returns `vastXml` for video bids. No VastUrl or videoCache. + if (!bidResponse.vastUrl && bidResponse.vastXml) { + bidResponse.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidResponse.vastXml.replace(/\\"/g, '"')); + } + + return bidResponse; } function buildBidSet(seatbid, bidderRequest) { @@ -315,9 +355,26 @@ function validateVideo(bid) { return true; } + let w = deepAccess(bid, 'mediaTypes.video.w'); + let h = deepAccess(bid, 'mediaTypes.video.h'); + const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); + if (!w && playerSize) { + if (Array.isArray(playerSize[0])) { + w = parseInt(playerSize[0][0], 10); + } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { + w = parseInt(playerSize[0], 10); + } + } + if (!h && playerSize) { + if (Array.isArray(playerSize[0])) { + h = parseInt(playerSize[0][1], 10); + } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { + h = parseInt(playerSize[1], 10); + } + } const videoSize = [ - deepAccess(bid, 'mediaTypes.video.w'), - deepAccess(bid, 'mediaTypes.video.h'), + w, + h, ]; if ( @@ -341,6 +398,13 @@ function validateVideo(bid) { return false; } + const plcmt = deepAccess(bid, 'mediaTypes.video.plcmt'); + + if (typeof plcmt !== 'undefined' && typeof plcmt !== 'number') { + logError('insticator: video plcmt is not a number'); + return false; + } + return true; } diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index e24bcb3b455..36b6dd8fbf2 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -179,6 +179,43 @@ describe('InsticatorBidAdapter', function () { } })).to.be.false; }); + + it('should return false if video plcmt is not a number', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + w: 250, + h: 300, + plcmt: 'NaN', + }, + } + } + })).to.be.false; + }); + + it('should return true if playerSize is present instead of w and h', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [250, 300], + placement: 1, + }, + } + } + })).to.be.true; + }); }); describe('buildRequests', function () { @@ -570,4 +607,87 @@ describe('InsticatorBidAdapter', function () { expect(spec.getUserSyncs({}, [response])).to.have.length(0); }) }); + + describe('Response with video Instream', function () { + const bidRequestVid = { + method: 'POST', + url: 'https://ex.ingage.tech/v1/openrtb', + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: '', + bidderRequest: { + bidderRequestId: '22edbae2733bf6', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + timeout: 300, + bids: [ + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-1', + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [[250, 300]], + placement: 2, + plcmt: 2, + } + }, + bidId: 'bid1', + } + ] + } + }; + + const bidResponseVid = { + body: { + id: '22edbae2733bf6', + bidid: 'foo9876', + cur: 'USD', + seatbid: [ + { + seat: 'some-dsp', + bid: [ + { + ad: '', + impid: 'bid1', + crid: 'crid1', + price: 0.5, + w: 300, + h: 250, + adm: '', + exp: 60, + adomain: ['test1.com'], + ext: { + meta: { + test: 1 + } + }, + } + ], + }, + ] + } + }; + const bidRequestWithVideo = utils.deepClone(bidRequestVid); + + it('should have related properties for video Instream', function() { + const serverResponseWithInstream = utils.deepClone(bidResponseVid); + serverResponseWithInstream.body.seatbid[0].bid[0].vastXml = ''; + serverResponseWithInstream.body.seatbid[0].bid[0].mediaType = 'video'; + const bidResponse = spec.interpretResponse(serverResponseWithInstream, bidRequestWithVideo)[0]; + expect(bidResponse).to.have.any.keys('mediaType', 'vastXml', 'vastUrl'); + expect(bidResponse).to.have.property('mediaType', 'video'); + expect(bidResponse.width).to.equal(300); + expect(bidResponse.height).to.equal(250); + expect(bidResponse).to.have.property('vastXml', ''); + expect(bidResponse.vastUrl).to.match(/^data:text\/xml;charset=utf-8;base64,[\w+/=]+$/) + }); + }) }); From c2d4a88e9a422988b653fa58449a6c5c9b1be08c Mon Sep 17 00:00:00 2001 From: Yohan Boutin Date: Thu, 11 Jan 2024 17:37:08 +0100 Subject: [PATCH 333/760] Seedtag Bid Adapter : add geom to bidRequest (#10906) * add adunit geometry parameter to the bid request * lint * add unit test * add size check * use global slot for all tests * fix test when slot is not available$ --- modules/seedtagBidAdapter.js | 23 ++++++++- test/spec/modules/seedtagBidAdapter_spec.js | 54 ++++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 7ac7d048c50..51c326c2954 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -107,7 +107,6 @@ function buildBidRequest(validBidRequest) { return mediaTypesMap[pbjsType]; } ); - const bidRequest = { id: validBidRequest.bidId, transactionId: validBidRequest.ortb2Imp?.ext?.tid, @@ -115,6 +114,7 @@ function buildBidRequest(validBidRequest) { supplyTypes: mediaTypes, adUnitId: params.adUnitId, adUnitCode: validBidRequest.adUnitCode, + geom: geom(validBidRequest.adUnitCode), placement: params.placement, requestCount: validBidRequest.bidderRequestsCount || 1, // FIXME : in unit test the parameter bidderRequestsCount is undefined }; @@ -198,6 +198,27 @@ function ttfb() { return ttfb >= 0 && ttfb <= performance.now() ? ttfb : 0; } +function geom(adunitCode) { + const slot = document.getElementById(adunitCode); + if (slot) { + const scrollY = window.scrollY; + const { top, left, width, height } = slot.getBoundingClientRect(); + const viewport = { + width: window.innerWidth, + height: window.innerHeight, + }; + + return { + scrollY, + top, + left, + width, + height, + viewport, + }; + } +} + export function getTimeoutUrl(data) { let queryParams = ''; if ( diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index fb666e89f73..516c5ec933a 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -2,10 +2,24 @@ import { expect } from 'chai'; import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from '../../../src/config.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; const PUBLISHER_ID = '0000-0000-01'; const ADUNIT_ID = '000000'; +const adUnitCode = '/19968336/header-bid-tag-0' + +// create a default adunit +const slot = document.createElement('div'); +slot.id = adUnitCode; +slot.style.width = '300px' +slot.style.height = '250px' +slot.style.position = 'absolute' +slot.style.top = '10px' +slot.style.left = '20px' + +document.body.appendChild(slot); + function getSlotConfigs(mediaTypes, params) { return { params: params, @@ -25,7 +39,7 @@ function getSlotConfigs(mediaTypes, params) { tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', } }, - adUnitCode: 'adunit-code', + adUnitCode: adUnitCode, }; } @@ -46,6 +60,13 @@ const createBannerSlotConfig = (placement, mediatypes) => { }; describe('Seedtag Adapter', function () { + beforeEach(function () { + mockGpt.reset(); + }); + + afterEach(function () { + mockGpt.enable(); + }); describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { @@ -277,7 +298,7 @@ describe('Seedtag Adapter', function () { expect(data.auctionStart).to.be.greaterThanOrEqual(now); expect(data.ttfb).to.be.greaterThanOrEqual(0); - expect(data.bidRequests[0].adUnitCode).to.equal('adunit-code'); + expect(data.bidRequests[0].adUnitCode).to.equal(adUnitCode); }); describe('GDPR params', function () { @@ -374,6 +395,35 @@ describe('Seedtag Adapter', function () { expect(videoBid.sizes[1][1]).to.equal(600); expect(videoBid.requestCount).to.equal(1); }); + + it('should have geom parameters if slot is available', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const bidRequests = data.bidRequests; + const bannerBid = bidRequests[0]; + + // on some CI, the DOM is not initialized, so we need to check if the slot is available + const slot = document.getElementById(adUnitCode) + if (slot) { + expect(bannerBid).to.have.property('geom') + + const params = [['width', 300], ['height', 250], ['top', 10], ['left', 20], ['scrollY', 0]] + params.forEach(([param, value]) => { + expect(bannerBid.geom).to.have.property(param) + expect(bannerBid.geom[param]).to.be.a('number') + expect(bannerBid.geom[param]).to.be.equal(value) + }) + + expect(bannerBid.geom).to.have.property('viewport') + const viewportParams = ['width', 'height'] + viewportParams.forEach(param => { + expect(bannerBid.geom.viewport).to.have.property(param) + expect(bannerBid.geom.viewport[param]).to.be.a('number') + }) + } else { + expect(bannerBid).to.not.have.property('geom') + } + }) }); describe('COPPA param', function () { From 1837f79b27040c0a4555f236a327ab055d0793c6 Mon Sep 17 00:00:00 2001 From: kiyoshi0205 <44894523+kiyoshi0205@users.noreply.github.com> Date: Fri, 12 Jan 2024 01:40:42 +0900 Subject: [PATCH 334/760] add dsp_genieeBidAdapter (#10834) Co-authored-by: kiyoshi fujiwara --- modules/dsp_genieeBidAdapter.js | 123 +++++++++++++ modules/dsp_genieeBidAdapter.md | 39 ++++ .../spec/modules/dsp_genieeBidAdapter_spec.js | 173 ++++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 modules/dsp_genieeBidAdapter.js create mode 100644 modules/dsp_genieeBidAdapter.md create mode 100644 test/spec/modules/dsp_genieeBidAdapter_spec.js diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js new file mode 100644 index 00000000000..517d6a93177 --- /dev/null +++ b/modules/dsp_genieeBidAdapter.js @@ -0,0 +1,123 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +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'; +const BIDDER_CODE = 'dsp_geniee'; +const ENDPOINT_URL = 'https://rt.gsspat.jp/prebid_auction'; +const ENDPOINT_URL_UNCOMFORTABLE = 'https://rt.gsspat.jp/prebid_uncomfortable'; +const ENDPOINT_USERSYNC = 'https://rt.gsspat.jp/prebid_cs'; +const VALID_CURRENCIES = ['USD', 'JPY']; +const converter = ortbConverter({ + context: { ttl: 300, netRevenue: true }, + // set optional parameters + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'ext', bidRequest.params); + return imp; + } +}); + +function USPConsent(consent) { + return typeof consent === 'string' && consent[0] === '1' && consent.toUpperCase()[2] === 'Y'; +} + +function invalidCurrency(currency) { + return typeof currency === 'string' && VALID_CURRENCIES.indexOf(currency.toUpperCase()) === -1; +} + +function hasTest(imp) { + if (typeof imp !== 'object') { + return false; + } + for (let i = 0; i < imp.length; i++) { + if (deepAccess(imp[i], 'ext.test') === 1) { + return true; + } + } + return false; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} - The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (_) { + return true; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || // gdpr + USPConsent(bidderRequest.uspConsent) || // usp + config.getConfig('coppa') || // coppa + invalidCurrency(config.getConfig('currency.adServerCurrency')) // currency validation + ) { + return { + method: 'GET', + url: ENDPOINT_URL_UNCOMFORTABLE + }; + } + + const payload = converter.toORTB({ validBidRequests, bidderRequest }); + + if (hasTest(deepAccess(payload, 'imp'))) { + deepSetValue(payload, 'test', 1); + } + + deepSetValue(payload, 'at', 1); // first price auction only + + return { + method: 'POST', + url: ENDPOINT_URL, + data: payload + }; + }, + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest - the master bidRequest object + * @return {bids} - An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse.body) { // empty response (no bids) + return []; + } + const bids = converter.fromORTB({ response: serverResponse.body, request: bidRequest.data }).bids; + return bids; + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + // gdpr & usp + if (deepAccess(gdprConsent, 'gdprApplies') || USPConsent(uspConsent)) { + return syncs; + } + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: ENDPOINT_USERSYNC + }); + } + return syncs; + } +}; +registerBidder(spec); diff --git a/modules/dsp_genieeBidAdapter.md b/modules/dsp_genieeBidAdapter.md new file mode 100644 index 00000000000..d51d66884af --- /dev/null +++ b/modules/dsp_genieeBidAdapter.md @@ -0,0 +1,39 @@ +# Overview + +```markdown +Module Name: Geniee Bid Adapter +Module Type: Bidder Adapter +Maintainer: dsp_back@geniee.co.jp +``` + +# Description +This is [Geniee](https://geniee.co.jp) Bidder Adapter for Prebid.js. + +Please contact us before using the adapter. + +We will provide ads when satisfy the following conditions: + +- There are a certain number bid requests by zone +- The request is a Banner ad +- Payment is possible in Japanese yen or US dollars +- The request is not for GDPR or COPPA users + +Thus, even if the following test, it will be no bids if the request does not reach a certain requests. + +# Test AdUnits +```javascript +var adUnits={ + code: 'geniee-test-ad', + bids: [{ + bidder: 'dsp_geniee', + params: { + test: 1, + } + }], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } +}; +``` diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js new file mode 100644 index 00000000000..94ec1011fbf --- /dev/null +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import { spec } from 'modules/dsp_genieeBidAdapter.js'; +import { config } from 'src/config'; + +describe('Geniee adapter tests', () => { + const validBidderRequest = { + code: 'sample_request', + bids: [{ + bidId: 'bid-id', + bidder: 'dsp_geniee', + params: { + test: 1 + } + }], + gdprConsent: { + gdprApplies: false + }, + uspConsent: '1YNY' + }; + + describe('isBidRequestValid function test', () => { + it('valid', () => { + expect(spec.isBidRequestValid(validBidderRequest.bids[0])).equal(true); + }); + }); + describe('buildRequests function test', () => { + it('auction', () => { + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + const auction_id = request.data.id; + expect(request).deep.equal({ + method: 'POST', + url: 'https://rt.gsspat.jp/prebid_auction', + data: { + at: 1, + id: auction_id, + imp: [ + { + ext: { + test: 1 + }, + id: 'bid-id' + } + ], + test: 1 + }, + }); + }); + it('uncomfortable (gdpr)', () => { + validBidderRequest.gdprConsent.gdprApplies = true; + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + expect(request).deep.equal({ + method: 'GET', + url: 'https://rt.gsspat.jp/prebid_uncomfortable', + }); + validBidderRequest.gdprConsent.gdprApplies = false; + }); + it('uncomfortable (usp)', () => { + validBidderRequest.uspConsent = '1YYY'; + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + expect(request).deep.equal({ + method: 'GET', + url: 'https://rt.gsspat.jp/prebid_uncomfortable', + }); + validBidderRequest.uspConsent = '1YNY'; + }); + it('uncomfortable (coppa)', () => { + config.setConfig({ coppa: true }); + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + expect(request).deep.equal({ + method: 'GET', + url: 'https://rt.gsspat.jp/prebid_uncomfortable', + }); + config.resetConfig(); + }); + it('uncomfortable (currency)', () => { + config.setConfig({ currency: { adServerCurrency: 'TWD' } }); + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + expect(request).deep.equal({ + method: 'GET', + url: 'https://rt.gsspat.jp/prebid_uncomfortable', + }); + config.resetConfig(); + }); + }); + describe('interpretResponse function test', () => { + it('sample bid', () => { + const request = spec.buildRequests(validBidderRequest.bids, validBidderRequest); + const auction_id = request.data.id; + const adm = "\n"; + const serverResponse = { + body: { + id: auction_id, + cur: 'JPY', + seatbid: [{ + bid: [{ + id: '7b77235d599e06d289e58ddfa9390443e22d7071', + impid: 'bid-id', + price: 0.6666000000000001, + adid: '8405715', + adm: adm, + adomain: ['geniee.co.jp'], + iurl: 'http://img.gsspat.jp/e/068c8e1eafbf0cb6ac1ee95c36152bd2/04f4bd4e6b71f978d343d84ecede3877.png', + cid: '8405715', + crid: '1383823', + cat: ['IAB1'], + w: 300, + h: 250, + mtype: 1 + }] + }] + } + }; + const bids = spec.interpretResponse(serverResponse, request); + expect(bids).deep.equal([{ + ad: adm, + cpm: 0.6666000000000001, + creativeId: '1383823', + creative_id: '1383823', + height: 250, + width: 300, + currency: 'JPY', + mediaType: 'banner', + meta: { + advertiserDomains: ['geniee.co.jp'] + }, + netRevenue: true, + requestId: 'bid-id', + seatBidId: '7b77235d599e06d289e58ddfa9390443e22d7071', + ttl: 300 + }]); + }); + it('no bid', () => { + const serverResponse = {}; + const bids = spec.interpretResponse(serverResponse, validBidderRequest); + expect(bids).deep.equal([]); + }); + }); + describe('getUserSyncs function test', () => { + it('sync enabled', () => { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + const serverResponses = []; + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).deep.equal([{ + type: 'image', + url: 'https://rt.gsspat.jp/prebid_cs' + }]); + }); + it('sync disabled (option false)', () => { + const syncOptions = { + iframeEnabled: false, + pixelEnabled: false + }; + const serverResponses = []; + const syncs = spec.getUserSyncs(syncOptions, serverResponses); + expect(syncs).deep.equal([]); + }); + it('sync disabled (gdpr)', () => { + const syncOptions = { + iframeEnabled: true, + pixelEnabled: true + }; + const serverResponses = []; + const gdprConsent = { + gdprApplies: true + }; + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent); + expect(syncs).deep.equal([]); + }); + }); +}); From 07f480966a1c81b93f81532de0aa95fbbe7c003b Mon Sep 17 00:00:00 2001 From: sebastienrufiange <131205907+sebastienrufiange@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:44:31 -0500 Subject: [PATCH 335/760] Contxtful RTD Provider: Initial Release (#10550) * feat: added contxtfulRtdProvider * fix: removed id in query param * fix: googletag * doc: typo * fix: added contxtful in adloader * doc: extra line * fix: added connector config option --- .../gpt/contxtfulRtdProvider_example.html | 91 ++++++++ modules/contxtfulRtdProvider.js | 150 +++++++++++++ modules/contxtfulRtdProvider.md | 65 ++++++ src/adloader.js | 3 +- .../spec/modules/contxtfulRtdProvider_spec.js | 200 ++++++++++++++++++ 5 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/gpt/contxtfulRtdProvider_example.html create mode 100644 modules/contxtfulRtdProvider.js create mode 100644 modules/contxtfulRtdProvider.md create mode 100644 test/spec/modules/contxtfulRtdProvider_spec.js diff --git a/integrationExamples/gpt/contxtfulRtdProvider_example.html b/integrationExamples/gpt/contxtfulRtdProvider_example.html new file mode 100644 index 00000000000..29284de81a2 --- /dev/null +++ b/integrationExamples/gpt/contxtfulRtdProvider_example.html @@ -0,0 +1,91 @@ + + + + + + + + + +

Contxtful RTD Provider

+
+
+ + + \ No newline at end of file diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js new file mode 100644 index 00000000000..69ff3c85079 --- /dev/null +++ b/modules/contxtfulRtdProvider.js @@ -0,0 +1,150 @@ +/** + * Contxtful Technologies Inc + * This RTD module provides receptivity feature that can be accessed using the + * getReceptivity() function. The value returned by this function enriches the ad-units + * that are passed within the `getTargetingData` functions and GAM. + */ + +import { submodule } from '../src/hook.js'; +import { + logInfo, + logError, + isStr, + isEmptyStr, + buildUrl, +} from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; + +const MODULE_NAME = 'contxtful'; +const MODULE = `${MODULE_NAME}RtdProvider`; + +const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io'; + +let initialReceptivity = null; +let contxtfulModule = null; + +/** + * Init function used to start sub module + * @param { { params: { version: String, customer: String, hostname: String } } } config + * @return { Boolean } + */ +function init(config) { + logInfo(MODULE, 'init', config); + initialReceptivity = null; + contxtfulModule = null; + + try { + const {version, customer, hostname} = extractParameters(config); + initCustomer(version, customer, hostname); + return true; + } catch (error) { + logError(MODULE, error); + return false; + } +} + +/** + * Extract required configuration for the sub module. + * validate that all required configuration are present and are valid. + * Throws an error if any config is missing of invalid. + * @param { { params: { version: String, customer: String, hostname: String } } } config + * @return { { version: String, customer: String, hostname: String } } + * @throws params.{name} should be a non-empty string + */ +function extractParameters(config) { + const version = config?.params?.version; + if (!isStr(version) || isEmptyStr(version)) { + throw Error(`${MODULE}: params.version should be a non-empty string`); + } + + const customer = config?.params?.customer; + if (!isStr(customer) || isEmptyStr(customer)) { + throw Error(`${MODULE}: params.customer should be a non-empty string`); + } + + const hostname = config?.params?.hostname || CONTXTFUL_RECEPTIVITY_DOMAIN; + + return {version, customer, hostname}; +} + +/** + * Initialize sub module for a customer. + * This will load the external resources for the sub module. + * @param { String } version + * @param { String } customer + * @param { String } hostname + */ +function initCustomer(version, customer, hostname) { + const CONNECTOR_URL = buildUrl({ + protocol: 'https', + host: hostname, + pathname: `/${version}/prebid/${customer}/connector/p.js`, + }); + + const externalScript = loadExternalScript(CONNECTOR_URL, MODULE_NAME); + addExternalScriptEventListener(externalScript); +} + +/** + * Add event listener to the script tag for the expected events from the external script. + * @param { HTMLScriptElement } script + */ +function addExternalScriptEventListener(script) { + if (!script) { + return; + } + + script.addEventListener('initialReceptivity', ({ detail }) => { + let receptivityState = detail?.ReceptivityState; + if (isStr(receptivityState) && !isEmptyStr(receptivityState)) { + initialReceptivity = receptivityState; + } + }); + + script.addEventListener('rxEngineIsReady', ({ detail: api }) => { + contxtfulModule = api; + }); +} + +/** + * Return current receptivity. + * @return { { ReceptivityState: String } } + */ +function getReceptivity() { + return { + ReceptivityState: contxtfulModule?.GetReceptivity()?.ReceptivityState || initialReceptivity + }; +} + +/** + * Set targeting data for ad server + * @param { [String] } adUnits + * @param {*} _config + * @param {*} _userConsent +* @return {{ code: { ReceptivityState: String } }} + */ +function getTargetingData(adUnits, _config, _userConsent) { + logInfo(MODULE, 'getTargetingData'); + if (!adUnits) { + return {}; + } + + const receptivity = getReceptivity(); + if (!receptivity?.ReceptivityState) { + return {}; + } + + return adUnits.reduce((targets, code) => { + targets[code] = receptivity; + return targets; + }, {}); +} + +export const contxtfulSubmodule = { + name: MODULE_NAME, + init, + extractParameters, + getTargetingData, +}; + +submodule('realTimeData', contxtfulSubmodule); diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md new file mode 100644 index 00000000000..dfefca2067a --- /dev/null +++ b/modules/contxtfulRtdProvider.md @@ -0,0 +1,65 @@ +# Overview + +**Module Name:** Contxtful RTD Provider +**Module Type:** RTD Provider +**Maintainer:** [prebid@contxtful.com](mailto:prebid@contxtful.com) + +# Description + +The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time. + +To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [prebid@contxtful.com](mailto:prebid@contxtful.com). + +# Configuration + +## Build Instructions + +To incorporate this module into your `prebid.js`, compile the module using the following command: + +```sh +gulp build --modules=contxtfulRtdProvider, +``` + +## Module Configuration + +Configure the `contxtfulRtdProvider` by passing the required settings through the `setConfig` function in `prebid.js`. + +```js +import pbjs from 'prebid.js'; + +pbjs.setConfig({ + "realTimeData": { + "auctionDelay": 1000, + "dataProviders": [ + { + "name": "contxtful", + "waitForIt": true, + "params": { + "version": "", + "customer": "" + } + } + ] + } +}); +``` + +### Configuration Parameters + +| Name | Type | Scope | Description | +|------------|----------|----------|-------------------------------------------| +| `version` | `string` | Required | Specifies the API version of Contxtful. | +| `customer` | `string` | Required | Your unique customer identifier. | + +# Usage + +The `contxtfulRtdProvider` module loads an external JavaScript file and authenticates with Contxtful APIs. The `getTargetingData` function then adds a `ReceptivityState` to each ad slot, which can have one of two values: `Receptive` or `NonReceptive`. + +```json +{ + "adUnitCode1": { "ReceptivityState": "Receptive" }, + "adUnitCode2": { "ReceptivityState": "NonReceptive" } +} +``` + +This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process. \ No newline at end of file diff --git a/src/adloader.js b/src/adloader.js index d1dca9627d8..664fd03d673 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -29,7 +29,8 @@ const _approvedLoadExternalJSList = [ 'geoedge', 'mediafilter', 'qortex', - 'dynamicAdBoost' + 'dynamicAdBoost', + 'contxtful' ] /** diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js new file mode 100644 index 00000000000..541c0e6e6dd --- /dev/null +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -0,0 +1,200 @@ +import { contxtfulSubmodule } from '../../../modules/contxtfulRtdProvider.js'; +import { expect } from 'chai'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +import * as events from '../../../src/events'; + +const _ = null; +const VERSION = 'v1'; +const CUSTOMER = 'CUSTOMER'; +const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/p.js`; +const INITIAL_RECEPTIVITY = { ReceptivityState: 'INITIAL_RECEPTIVITY' }; +const INITIAL_RECEPTIVITY_EVENT = new CustomEvent('initialReceptivity', { detail: INITIAL_RECEPTIVITY }); + +const CONTXTFUL_API = { GetReceptivity: sinon.stub() } +const RX_ENGINE_IS_READY_EVENT = new CustomEvent('rxEngineIsReady', {detail: CONTXTFUL_API}); + +function buildInitConfig(version, customer) { + return { + name: 'contxtful', + params: { + version, + customer, + }, + }; +} + +describe('contxtfulRtdProvider', function () { + let sandbox = sinon.sandbox.create(); + let loadExternalScriptTag; + let eventsEmitSpy; + + beforeEach(() => { + loadExternalScriptTag = document.createElement('script'); + loadExternalScriptStub.callsFake((_url, _moduleName) => loadExternalScriptTag); + + CONTXTFUL_API.GetReceptivity.reset(); + + eventsEmitSpy = sandbox.spy(events, ['emit']); + }); + + afterEach(function () { + delete window.Contxtful; + sandbox.restore(); + }); + + describe('extractParameters with invalid configuration', () => { + const { + params: { customer, version }, + } = buildInitConfig(VERSION, CUSTOMER); + const theories = [ + [ + null, + 'params.version should be a non-empty string', + 'null object for config', + ], + [ + {}, + 'params.version should be a non-empty string', + 'empty object for config', + ], + [ + { customer }, + 'params.version should be a non-empty string', + 'customer only in config', + ], + [ + { version }, + 'params.customer should be a non-empty string', + 'version only in config', + ], + [ + { customer, version: '' }, + 'params.version should be a non-empty string', + 'empty string for version', + ], + [ + { customer: '', version }, + 'params.customer should be a non-empty string', + 'empty string for customer', + ], + [ + { customer: '', version: '' }, + 'params.version should be a non-empty string', + 'empty string for version & customer', + ], + ]; + + theories.forEach(([params, expectedErrorMessage, _description]) => { + const config = { name: 'contxtful', params }; + it('throws the expected error', () => { + expect(() => contxtfulSubmodule.extractParameters(config)).to.throw( + expectedErrorMessage + ); + }); + }); + }); + + describe('initialization with invalid config', function () { + it('returns false', () => { + expect(contxtfulSubmodule.init({})).to.be.false; + }); + }); + + describe('initialization with valid config', function () { + it('returns true when initializing', () => { + const config = buildInitConfig(VERSION, CUSTOMER); + expect(contxtfulSubmodule.init(config)).to.be.true; + }); + + it('loads contxtful module script asynchronously', (done) => { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + + setTimeout(() => { + expect(loadExternalScriptStub.calledOnce).to.be.true; + expect(loadExternalScriptStub.args[0][0]).to.equal( + CONTXTFUL_CONNECTOR_ENDPOINT + ); + done(); + }, 10); + }); + }); + + describe('load external script return falsy', function () { + it('returns true when initializing', () => { + loadExternalScriptStub.callsFake(() => {}); + const config = buildInitConfig(VERSION, CUSTOMER); + expect(contxtfulSubmodule.init(config)).to.be.true; + }); + }); + + describe('rxEngine from external script', function () { + it('use rxEngine api to get receptivity', () => { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + loadExternalScriptTag.dispatchEvent(RX_ENGINE_IS_READY_EVENT); + + contxtfulSubmodule.getTargetingData(['ad-slot']); + + expect(CONTXTFUL_API.GetReceptivity.calledOnce).to.be.true; + }); + }); + + describe('initial receptivity is not dispatched', function () { + it('does not initialize receptivity value', () => { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); + expect(targetingData).to.deep.equal({}); + }); + }); + + describe('initial receptivity is invalid', function () { + const theories = [ + [new Event('initialReceptivity'), 'event without details'], + [new CustomEvent('initialReceptivity', { }), 'custom event without details'], + [new CustomEvent('initialReceptivity', { detail: {} }), 'custom event with invalid details'], + [new CustomEvent('initialReceptivity', { detail: { ReceptivityState: '' } }), 'custom event with details without ReceptivityState'], + ]; + + theories.forEach(([initialReceptivityEvent, _description]) => { + it('does not initialize receptivity value', () => { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + loadExternalScriptTag.dispatchEvent(initialReceptivityEvent); + + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); + expect(targetingData).to.deep.equal({}); + }); + }) + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + { 'ad-slot': { ReceptivityState: 'INITIAL_RECEPTIVITY' } }, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + 'ad-slot-1': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, + 'ad-slot-2': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, _description]) => { + it('adds "ReceptivityState" to the adUnits', function () { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + loadExternalScriptTag.dispatchEvent(INITIAL_RECEPTIVITY_EVENT); + + expect(contxtfulSubmodule.getTargetingData(adUnits)).to.deep.equal( + expected + ); + }); + }); + }); +}); From b8c6918a47a4869e5d9fb0ac25c350335b9bf28a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 Jan 2024 17:33:42 +0000 Subject: [PATCH 336/760] Prebid 8.31.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3f529edb774..af255ec674c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.31.0-pre", + "version": "8.31.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 70ebfb0898c..b2d9cabcad5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.31.0-pre", + "version": "8.31.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 759d5ac4f0046cb3a9f940c5a677401e871adbae Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 11 Jan 2024 17:33:43 +0000 Subject: [PATCH 337/760] Increment version to 8.32.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index af255ec674c..7114a39d509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.31.0", + "version": "8.32.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b2d9cabcad5..82db5e9f7c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.31.0", + "version": "8.32.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 672b53579b200f8497f82c3cb954a143f669b200 Mon Sep 17 00:00:00 2001 From: charlie0228 Date: Fri, 12 Jan 2024 23:32:14 +0800 Subject: [PATCH 338/760] Ad2iction Bid Adapter: initial release (#10877) * Import the project * Update version * fix: typo * refactor: rewrite * feat: add pre-defined function & add logger * feat: add test for bid adapter * feat: add readme for ad2iction bid adaptor * feat: remove unneeded callback event * feat: lint & add missing test * feat: remove debugger flag * feat: refactor & new feature for adapter * feat: update Maintainer & Description info --------- Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Charlie --- modules/ad2ictionBidAdapter.js | 59 +++++ modules/ad2ictionBidAdapter.md | 30 +++ test/spec/modules/ad2ictionBidAdapter_spec.js | 223 ++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 modules/ad2ictionBidAdapter.js create mode 100644 modules/ad2ictionBidAdapter.md create mode 100644 test/spec/modules/ad2ictionBidAdapter_spec.js diff --git a/modules/ad2ictionBidAdapter.js b/modules/ad2ictionBidAdapter.js new file mode 100644 index 00000000000..0f7cea45d14 --- /dev/null +++ b/modules/ad2ictionBidAdapter.js @@ -0,0 +1,59 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; + +export const BIDDER_CODE = 'ad2iction'; +export const SUPPORTED_AD_TYPES = [BANNER]; +export const API_ENDPOINT = 'https://ads.ad2iction.com/html/prebid/'; +export const API_VERSION_NUMBER = 3; +export const COOKIE_NAME = 'ad2udid'; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + code: BIDDER_CODE, + aliases: ['ad2'], + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: (bid) => { + return !!bid.params.id && typeof bid.params.id === 'string'; + }, + buildRequests: (validBidRequests, bidderRequest) => { + const ids = validBidRequests.map((bid) => { + return { bannerId: bid.params.id, bidId: bid.bidId }; + }); + + const options = { + contentType: 'application/json', + withCredentials: false, + }; + + const udid = storage.cookiesAreEnabled() && storage.getCookie(COOKIE_NAME); + + const data = { + ids: JSON.stringify(ids), + ortb2: bidderRequest.ortb2, + refererInfo: bidderRequest.refererInfo, + v: API_VERSION_NUMBER, + udid: udid || '', + _: Math.round(new Date().getTime()), + }; + + return { + method: 'POST', + url: API_ENDPOINT, + data, + options, + }; + }, + interpretResponse: (serverResponse, bidRequest) => { + if (!Array.isArray(serverResponse.body)) { + return []; + } + + const bidResponses = serverResponse.body; + + return bidResponses; + }, +}; + +registerBidder(spec); diff --git a/modules/ad2ictionBidAdapter.md b/modules/ad2ictionBidAdapter.md new file mode 100644 index 00000000000..47e355aa795 --- /dev/null +++ b/modules/ad2ictionBidAdapter.md @@ -0,0 +1,30 @@ +# Overview + +**Module Name**: Ad2iction Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@ad2iction.com + +# Description + +The Ad2iction Bidding adapter requires setup before beginning. Please contact us on https://www.ad2iction.com. + +# Sample Ad Unit Config +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [336, 280]] + } + }, + bids: [{ + bidder: 'ad2iction', + params: { + id: 'accepted-uuid' + } + }] + } +]; +``` diff --git a/test/spec/modules/ad2ictionBidAdapter_spec.js b/test/spec/modules/ad2ictionBidAdapter_spec.js new file mode 100644 index 00000000000..99800c6dd01 --- /dev/null +++ b/test/spec/modules/ad2ictionBidAdapter_spec.js @@ -0,0 +1,223 @@ +import { expect } from 'chai'; +import { + spec, + API_ENDPOINT, + API_VERSION_NUMBER, +} from 'modules/ad2ictionBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; + +describe('ad2ictionBidAdapter', 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: 'ad2iction', + params: { id: '11ab384c-e936-11ed-a6a7-f23c9173ed43' }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [336, 280], + ], + }, + }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [336, 280], + ], + bidId: '2a7a3b48778a1b', + bidderRequestId: '1e6509293abe6b', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when params id is not valid (letters)', function () { + const mockBid = { + ...bid, + params: { id: 1234 }, + }; + + expect(spec.isBidRequestValid(mockBid)).to.equal(false); + }); + + it('should return false when params id is not exist', function () { + const mockBid = { + ...bid, + }; + delete mockBid.params.id; + + expect(spec.isBidRequestValid(mockBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + const mockValidBidRequests = [ + { + bidder: 'ad2iction', + params: { id: '11ab384c-e936-11ed-a6a7-f23c9173ed43' }, + adUnitCode: 'adunit-code', + sizes: [ + [300, 250], + [336, 280], + ], + bidId: '57ffc0667379e1', + bidderRequestId: '4ddea14478a651', + }, + ]; + + const mockBidderRequest = { + bidderCode: 'ad2iction', + bidderRequestId: '4ddea14478a651', + bids: [ + { + bidder: 'ad2iction', + params: { id: '11ab384c-e936-11ed-a6a7-f23c9173ed43' }, + adUnitCode: 'adunit-code', + transactionId: null, + sizes: [ + [300, 250], + [336, 280], + ], + bidId: '57ffc0667379e1', + bidderRequestId: '4ddea14478a651', + }, + ], + timeout: 1200, + refererInfo: { + ref: 'https://example.com/referer.html', + }, + ortb2: { + source: {}, + site: { + ref: 'https://example.com/referer.html', + }, + device: { + w: 390, + h: 844, + language: 'zh', + }, + }, + start: 1702526505498, + }; + + it('should send bid request to API_ENDPOINT via POST', function () { + const request = spec.buildRequests( + mockValidBidRequests, + mockBidderRequest + ); + + expect(request.url).to.equal(API_ENDPOINT); + expect(request.method).to.equal('POST'); + }); + + it('should send bid request with API version', function () { + const request = spec.buildRequests( + mockValidBidRequests, + mockBidderRequest + ); + + expect(request.data.v).to.equal(API_VERSION_NUMBER); + }); + + it('should send bid request with dada fields', function () { + const request = spec.buildRequests( + mockValidBidRequests, + mockBidderRequest + ); + + expect(request.data).to.include.all.keys('udid', '_'); + expect(request.data).to.have.property('refererInfo'); + expect(request.data).to.have.property('ortb2'); + }); + }); + + describe('interpretResponse', function () { + it('should return an empty array to indicate no valid bids', function () { + const mockServerResponse = {}; + + const bidResponses = spec.interpretResponse(mockServerResponse); + + expect(bidResponses).is.an('array').that.is.empty; + }); + + it('should return a valid bid response', function () { + const MOCK_AD_DOM = "
" + const mockServerResponse = { + body: [ + { + requestId: '23a3d87fb6bde9', + cpm: 1.61, + currency: 'USD', + width: '336', + height: '280', + creativeId: '46271', + netRevenue: 'false', + ad: MOCK_AD_DOM, + meta: { + advertiserDomains: [''], + }, + ttl: 360, + }, + { + requestId: '3ce3efc40c890b', + cpm: 1.61, + currency: 'USD', + width: '336', + height: '280', + creativeId: '46271', + netRevenue: 'false', + ad: MOCK_AD_DOM, + meta: { + advertiserDomains: [''], + }, + ttl: 360, + }, + ], + }; + + const exceptServerResponse = [ + { + requestId: '23a3d87fb6bde9', + cpm: 1.61, + currency: 'USD', + width: '336', + height: '280', + creativeId: '46271', + netRevenue: 'false', + ad: MOCK_AD_DOM, + meta: { + advertiserDomains: [''], + }, + ttl: 360, + }, + { + requestId: '3ce3efc40c890b', + cpm: 1.61, + currency: 'USD', + width: '336', + height: '280', + creativeId: '46271', + netRevenue: 'false', + ad: MOCK_AD_DOM, + meta: { + advertiserDomains: [''], + }, + ttl: 360, + }, + ] + + const bidResponses = spec.interpretResponse(mockServerResponse); + + expect(bidResponses).to.eql(exceptServerResponse); + }); + }); +}); From c7f4d18318a92736f083f1bfeb3b0fe24dae2e0b Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Fri, 12 Jan 2024 18:46:16 +0100 Subject: [PATCH 339/760] LiveIntent UserId module: Make process and process.env optional (#10905) * Drop the minimal mode * Revert "Drop the minimal mode" This reverts commit 9f33731a528d4b5e45e526c46303f9ae729cb290. * Make process and process.env optional * Revert changes in spec * Use pbjsGlobals for confguring moduleMode * Bump LiveConnect version --- modules/liveIntentIdSystem.js | 2 +- plugins/pbjsGlobals.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 2b4fc2476f8..8dc7102091d 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -157,7 +157,7 @@ function tryFireEvent() { /** @type {Submodule} */ export const liveIntentIdSubmodule = { - moduleMode: process.env.LiveConnectMode, + moduleMode: '$$LIVE_INTENT_MODULE_MODE$$', /** * used to link submodule with config * @type {string} diff --git a/plugins/pbjsGlobals.js b/plugins/pbjsGlobals.js index 6d1eeb0c57d..62d29b567ed 100644 --- a/plugins/pbjsGlobals.js +++ b/plugins/pbjsGlobals.js @@ -34,7 +34,8 @@ module.exports = function(api, options) { '$$PREBID_GLOBAL$$': pbGlobal, '$$DEFINE_PREBID_GLOBAL$$': defineGlobal, '$$REPO_AND_VERSION$$': `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`, - '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/` + '$$PREBID_DIST_URL_BASE$$': options.prebidDistUrlBase || `https://cdn.jsdelivr.net/npm/prebid.js@${getNpmVersion(prebid.version)}/dist/`, + '$$LIVE_INTENT_MODULE_MODE$$': (process && process.env && process.env.LiveConnectMode) || 'standard' }; let identifierToStringLiteral = [ From 19a328194ff24d5050900a3d8352e08a5184fc66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+krzysztofequativ@users.noreply.github.com> Date: Fri, 12 Jan 2024 20:52:42 +0100 Subject: [PATCH 340/760] Smartadserver Bid Adapter: support additional video parameters (#10815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching * Add gpid support * Support additional video params * vpmt as array of numbers * Fix comment --------- Co-authored-by: Meven Courouble Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> --- modules/smartadserverBidAdapter.js | 47 ++++++-- .../modules/smartadserverBidAdapter_spec.js | 108 +++++++++++++++++- 2 files changed, 146 insertions(+), 9 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index ca43c26ffd7..313d466bd2e 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, deepClone, logError, isFn, isPlainObject } from '../src/utils.js'; +import { deepAccess, deepClone, isArrayOfNums, isFn, isInteger, isPlainObject, logError } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -55,20 +55,53 @@ export const spec = { * Fills the payload with specific video attributes. * * @param {*} payload Payload that will be sent in the ServerRequest - * @param {*} videoMediaType Video media type. + * @param {*} videoMediaType Video media type */ fillPayloadForVideoBidRequest: function(payload, videoMediaType, videoParams) { const playerSize = videoMediaType.playerSize[0]; - payload.isVideo = videoMediaType.context === 'instream'; + const map = { + maxbitrate: 'vbrmax', + maxduration: 'vdmax', + minbitrate: 'vbrmin', + minduration: 'vdmin', + placement: 'vpt', + plcmt: 'vplcmt', + skip: 'skip' + }; + payload.mediaType = VIDEO; + payload.isVideo = videoMediaType.context === 'instream'; + payload.videoData = {}; + + for (const [key, value] of Object.entries(map)) { + payload.videoData = { + ...payload.videoData, + ...this.getValuableProperty(value, videoMediaType[key]) + }; + } + payload.videoData = { - videoProtocol: this.getProtocolForVideoBidRequest(videoMediaType, videoParams), - playerWidth: playerSize[0], - playerHeight: playerSize[1], - adBreak: this.getStartDelayForVideoBidRequest(videoMediaType, videoParams) + ...payload.videoData, + ...this.getValuableProperty('playerWidth', playerSize[0]), + ...this.getValuableProperty('playerHeight', playerSize[1]), + ...this.getValuableProperty('adBreak', this.getStartDelayForVideoBidRequest(videoMediaType, videoParams)), + ...this.getValuableProperty('videoProtocol', this.getProtocolForVideoBidRequest(videoMediaType, videoParams)), + ...(isArrayOfNums(videoMediaType.api) && videoMediaType.api.length ? { iabframeworks: videoMediaType.api.toString() } : {}), + ...(isArrayOfNums(videoMediaType.playbackmethod) && videoMediaType.playbackmethod.length ? { vpmt: videoMediaType.playbackmethod } : {}) }; }, + /** + * Gets a property object if the value not falsy + * @param {string} property + * @param {number} value + * @returns object with the property or empty + */ + getValuableProperty: function(property, value) { + return typeof property === 'string' && isInteger(value) && value + ? { [property]: value } : {}; + }, + /** * Gets the protocols from either videoParams or VideoMediaType * @param {*} videoMediaType diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 9daa6a87826..58b4cd8c0d0 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -786,7 +786,7 @@ describe('Smart bid adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); - expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(null); + expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); }); @@ -833,6 +833,73 @@ describe('Smart bid adapter tests', function () { expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); }); + + it('should pass additional parameters', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + api: [1, 2, 3], + maxbitrate: 50, + minbitrate: 20, + maxduration: 30, + minduration: 5, + placement: 3, + playbackmethod: [2, 4], + playerSize: [[640, 480]], + plcmt: 1, + skip: 0 + } + }, + params: { + siteId: '123' + } + }]); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent.videoData).to.have.property('iabframeworks').and.to.equal('1,2,3'); + expect(requestContent.videoData).not.to.have.property('skip'); + expect(requestContent.videoData).to.have.property('vbrmax').and.to.equal(50); + expect(requestContent.videoData).to.have.property('vbrmin').and.to.equal(20); + expect(requestContent.videoData).to.have.property('vdmax').and.to.equal(30); + expect(requestContent.videoData).to.have.property('vdmin').and.to.equal(5); + expect(requestContent.videoData).to.have.property('vplcmt').and.to.equal(1); + expect(requestContent.videoData).to.have.property('vpmt').and.to.have.lengthOf(2); + expect(requestContent.videoData.vpmt[0]).to.equal(2); + expect(requestContent.videoData.vpmt[1]).to.equal(4); + expect(requestContent.videoData).to.have.property('vpt').and.to.equal(3); + }); + + it('should not pass not valuable parameters', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + maxbitrate: 20, + minbitrate: null, + maxduration: 0, + playbackmethod: [], + playerSize: [[640, 480]], + plcmt: 1 + } + }, + params: { + siteId: '123' + } + }]); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent.videoData).not.to.have.property('iabframeworks'); + expect(requestContent.videoData).to.have.property('vbrmax').and.to.equal(20); + expect(requestContent.videoData).not.to.have.property('vbrmin'); + expect(requestContent.videoData).not.to.have.property('vdmax'); + expect(requestContent.videoData).not.to.have.property('vdmin'); + expect(requestContent.videoData).to.have.property('vplcmt').and.to.equal(1); + expect(requestContent.videoData).not.to.have.property('vpmt'); + expect(requestContent.videoData).not.to.have.property('vpt'); + }); }); }); @@ -1029,7 +1096,7 @@ describe('Smart bid adapter tests', function () { expect(request[0]).to.have.property('method').and.to.equal('POST'); const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); - expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(null); + expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); }); @@ -1393,4 +1460,41 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('gpid').and.to.equal(gpid); }); }); + + describe('#getValuableProperty method', function () { + it('should return an object when calling with a number value', () => { + const obj = spec.getValuableProperty('prop', 3); + expect(obj).to.deep.equal({ prop: 3 }); + }); + + it('should return an empty object when calling with a string value', () => { + const obj = spec.getValuableProperty('prop', 'str'); + expect(obj).to.deep.equal({}); + }); + + it('should return an empty object when calling with a number property', () => { + const obj = spec.getValuableProperty(7, 'str'); + expect(obj).to.deep.equal({}); + }); + + it('should return an empty object when calling with a null value', () => { + const obj = spec.getValuableProperty('prop', null); + expect(obj).to.deep.equal({}); + }); + + it('should return an empty object when calling with an object value', () => { + const obj = spec.getValuableProperty('prop', {}); + expect(obj).to.deep.equal({}); + }); + + it('should return an empty object when calling with a 0 value', () => { + const obj = spec.getValuableProperty('prop', 0); + expect(obj).to.deep.equal({}); + }); + + it('should return an empty object when calling without the value argument', () => { + const obj = spec.getValuableProperty('prop'); + expect(obj).to.deep.equal({}); + }); + }); }); From 869edc7e0af19476a677eb965d84a78c5212685a Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:17:56 +0300 Subject: [PATCH 341/760] Core: fix jsdoc errors (#10941) --- libraries/cmp/cmpClient.js | 48 +++++++------- modules/ixBidAdapter.js | 66 +++++++++---------- modules/neuwoRtdProvider.js | 2 +- modules/prebidServerBidAdapter/index.js | 2 +- modules/pubxBidAdapter.js | 4 +- modules/rtbhouseBidAdapter.js | 6 +- modules/rtdModule/index.js | 2 +- modules/rubiconBidAdapter.js | 2 +- modules/sspBCBidAdapter.js | 2 +- modules/timeoutRtdProvider.js | 2 +- modules/yahoosspBidAdapter.js | 2 +- src/adRendering.js | 16 +++-- src/adServerManager.js | 2 +- src/adloader.js | 2 +- src/auction.js | 19 ++++-- src/auctionIndex.js | 45 +++++-------- src/auctionManager.js | 1 + src/config.js | 11 ++-- src/mediaTypes.js | 8 +-- src/userSync.js | 4 +- src/utils.js | 4 +- .../modules/sovrnAnalyticsAdapter_spec.js | 2 +- 22 files changed, 123 insertions(+), 129 deletions(-) diff --git a/libraries/cmp/cmpClient.js b/libraries/cmp/cmpClient.js index 03a50c37bb3..1d0b327cee4 100644 --- a/libraries/cmp/cmpClient.js +++ b/libraries/cmp/cmpClient.js @@ -4,46 +4,46 @@ import {GreedyPromise} from '../../src/utils/promise.js'; * @typedef {function} CMPClient * * @param {{}} params CMP parameters. Currently this is a subset of {command, callback, parameter, version}. - * @param {bool} once if true, discard cross-frame event listeners once a reply message is received. + * @param {boolean} once if true, discard cross-frame event listeners once a reply message is received. * @returns {Promise<*>} a promise to the API's "result" - see the `mode` argument to `cmpClient` on how that's determined. * @property {boolean} isDirect true if the CMP is directly accessible (no postMessage required) * @property {() => void} close close the client; currently, this just stops listening for cross-frame messages. */ +export const MODE_MIXED = 0; +export const MODE_RETURN = 1; +export const MODE_CALLBACK = 2; + /** * Returns a client function that can interface with a CMP regardless of where it's located. * - * @param apiName name of the CMP api, e.g. "__gpp" - * @param apiVersion? CMP API version - * @param apiArgs? names of the arguments taken by the api function, in order. - * @param callbackArgs? names of the cross-frame response payload properties that should be passed as callback arguments, in order - * @param mode? controls the callbacks passed to the underlying API, and how the promises returned by the client are resolved. + * @param {object} obj + * @param obj.apiName name of the CMP api, e.g. "__gpp" + * @param [obj.apiVersion] CMP API version + * @param [obj.apiArgs] names of the arguments taken by the api function, in order. + * @param [obj.callbackArgs] names of the cross-frame response payload properties that should be passed as callback arguments, in order + * @param [obj.mode] controls the callbacks passed to the underlying API, and how the promises returned by the client are resolved. * - * The client behaves differently when it's provided a `callback` argument vs when it's not - for short, let's name these - * cases "subscriptions" and "one-shot calls" respectively: + * The client behaves differently when it's provided a `callback` argument vs when it's not - for short, let's name these + * cases "subscriptions" and "one-shot calls" respectively: * - * With `mode: MODE_MIXED` (the default), promises returned on subscriptions are resolved to undefined when the callback - * is first run (that is, the promise resolves when the CMP replies, but what it replies with is discarded and - * left for the callback to deal with). For one-shot calls, the returned promise is resolved to the API's - * return value when it's directly accessible, or with the result from the first (and, presumably, the only) - * cross-frame reply when it's not; + * With `mode: MODE_MIXED` (the default), promises returned on subscriptions are resolved to undefined when the callback + * is first run (that is, the promise resolves when the CMP replies, but what it replies with is discarded and + * left for the callback to deal with). For one-shot calls, the returned promise is resolved to the API's + * return value when it's directly accessible, or with the result from the first (and, presumably, the only) + * cross-frame reply when it's not; * - * With `mode: MODE_RETURN`, the returned promise always resolves to the API's return value - which is taken to be undefined - * when cross-frame; + * With `mode: MODE_RETURN`, the returned promise always resolves to the API's return value - which is taken to be undefined + * when cross-frame; * - * With `mode: MODE_CALLBACK`, the underlying API is expected to never directly return anything significant; instead, - * it should always accept a callback and - for one-shot calls - invoke it only once with the result. The client will - * automatically generate an appropriate callback for one-shot calls and use the result it's given to resolve - * the returned promise. Subscriptions are treated in the same way as MODE_MIXED. + * With `mode: MODE_CALLBACK`, the underlying API is expected to never directly return anything significant; instead, + * it should always accept a callback and - for one-shot calls - invoke it only once with the result. The client will + * automatically generate an appropriate callback for one-shot calls and use the result it's given to resolve + * the returned promise. Subscriptions are treated in the same way as MODE_MIXED. * * @param win * @returns {CMPClient} CMP invocation function (or null if no CMP was found). */ - -export const MODE_MIXED = 0; -export const MODE_RETURN = 1; -export const MODE_CALLBACK = 2; - export function cmpClient( { apiName, diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 06bad99bfe8..4cc6807b670 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -535,7 +535,7 @@ function isValidSize(size) { * Determines whether or not the given size object is an element of the size * array. * - * @param {array} sizeArray The size array. + * @param {Array} sizeArray The size array. * @param {object} size The size object. * @return {boolean} True if the size object is an element of the size array, and false * otherwise. @@ -590,7 +590,7 @@ function checkVideoParams(mediaTypeVideoRef, paramsVideoRef) { * Get One size from Size Array * [[250,350]] -> [250, 350] * [250, 350] -> [250, 350] - * @param {array} sizes array of sizes + * @param {Array} sizes array of sizes */ function getFirstSize(sizes = []) { if (isValidSize(sizes)) { @@ -607,7 +607,7 @@ function getFirstSize(sizes = []) { * * @param {number} bidFloor The bidFloor parameter inside bid request config. * @param {number} bidFloorCur The bidFloorCur parameter inside bid request config. - * @return {bool} True if this is a valid bidFloor parameters format, and false + * @return {boolean} True if this is a valid bidFloor parameters format, and false * otherwise. */ function isValidBidFloorParams(bidFloor, bidFloorCur) { @@ -630,7 +630,7 @@ function nativeMediaTypeValid(bid) { * Get bid request object with the associated id. * * @param {*} id Id of the impression. - * @param {array} impressions List of impressions sent in the request. + * @param {Array} impressions List of impressions sent in the request. * @return {object} The impression with the associated id. */ function getBidRequest(id, impressions, validBidRequests) { @@ -648,7 +648,7 @@ function getBidRequest(id, impressions, validBidRequests) { /** * From the userIdAsEids array, filter for the ones our adserver can use, and modify them * for our purposes, e.g. add rtiPartner - * @param {array} allEids userIdAsEids passed in by prebid + * @param {Array} allEids userIdAsEids passed in by prebid * @return {object} contains toSend (eids to send to the adserver) and seenSources (used to filter * identity info from IX Library) */ @@ -676,11 +676,11 @@ function getEidInfo(allEids) { /** * Builds a request object to be sent to the ad server based on bid requests. * - * @param {array} validBidRequests A list of valid bid request config objects. + * @param {Array} validBidRequests A list of valid bid request config objects. * @param {object} bidderRequest An object containing other info like gdprConsent. * @param {object} impressions An object containing a list of impression objects describing the bids for each transaction - * @param {array} version Endpoint version denoting banner, video or native. - * @return {array} List of objects describing the request to the server. + * @param {Array} version Endpoint version denoting banner, video or native. + * @return {Array} List of objects describing the request to the server. * */ function buildRequest(validBidRequests, bidderRequest, impressions, version) { @@ -789,8 +789,8 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { /** * addRTI adds RTI info of the partner to retrieved user IDs from prebid ID module. * - * @param {array} userEids userEids info retrieved from prebid - * @param {array} eidInfo eidInfo info from prebid + * @param {Array} userEids userEids info retrieved from prebid + * @param {Array} eidInfo eidInfo info from prebid */ function addRTI(userEids, eidInfo) { let identityInfo = window.headertag.getIdentityInfo(); @@ -809,7 +809,7 @@ function addRTI(userEids, eidInfo) { /** * createRequest creates the base request object - * @param {array} validBidRequests A list of valid bid request config objects. + * @param {Array} validBidRequests A list of valid bid request config objects. * @return {object} Object describing the request to the server. */ function createRequest(validBidRequests) { @@ -849,9 +849,9 @@ function addRequestedFeatureToggles(r, requestedFeatureToggles) { * * @param {object} r Base reuqest object. * @param {object} bidderRequest An object containing other info like gdprConsent. - * @param {array} impressions A list of impressions to be added to the request. - * @param {array} validBidRequests A list of valid bid request config objects. - * @param {array} userEids User ID info retrieved from Prebid ID module. + * @param {Array} impressions A list of impressions to be added to the request. + * @param {Array} validBidRequests A list of valid bid request config objects. + * @param {Array} userEids User ID info retrieved from Prebid ID module. * @return {object} Enriched object describing the request to the server. */ function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids) { @@ -958,10 +958,10 @@ function applyRegulations(r, bidderRequest) { /** * addImpressions adds impressions to request object * - * @param {array} impressions List of impressions to be added to the request. - * @param {array} impKeys List of impression keys. + * @param {Array} impressions List of impressions to be added to the request. + * @param {Array} impKeys List of impression keys. * @param {object} r Reuqest object. - * @param {int} adUnitIndex Index of the current add unit + * @param {number} adUnitIndex Index of the current add unit * @return {object} Reqyest object with added impressions describing the request to the server. */ function addImpressions(impressions, impKeys, r, adUnitIndex) { @@ -1228,10 +1228,10 @@ function addAdUnitFPD(imp, bid) { /** * addIdentifiersInfo adds indentifier info to ixDaig. * - * @param {array} impressions List of impressions to be added to the request. + * @param {Array} impressions List of impressions to be added to the request. * @param {object} r Reuqest object. - * @param {array} impKeys List of impression keys. - * @param {int} adUnitIndex Index of the current add unit + * @param {Array} impKeys List of impression keys. + * @param {number} adUnitIndex Index of the current add unit * @param {object} payload Request payload object. * @param {string} baseUrl Base exchagne URL. * @return {object} Reqyest object with added indentigfier info to ixDiag. @@ -1254,7 +1254,7 @@ function addIdentifiersInfo(impressions, r, impKeys, adUnitIndex, payload, baseU /** * Return an object of user IDs stored by Prebid User ID module * - * @returns {array} ID providers that are present in userIds + * @returns {Array} ID providers that are present in userIds */ function _getUserIds(bidRequest) { const userIds = bidRequest.userId || {}; @@ -1265,8 +1265,8 @@ function _getUserIds(bidRequest) { /** * Calculates IX diagnostics values and packages them into an object * - * @param {array} validBidRequests - The valid bid requests from prebid - * @param {bool} fledgeEnabled - Flag indicating if protected audience (fledge) is enabled + * @param {Array} validBidRequests - The valid bid requests from prebid + * @param {boolean} fledgeEnabled - Flag indicating if protected audience (fledge) is enabled * @return {Object} IX diag values for ad units */ function buildIXDiag(validBidRequests, fledgeEnabled) { @@ -1327,8 +1327,8 @@ function buildIXDiag(validBidRequests, fledgeEnabled) { /** * - * @param {array} bannerSizeList list of banner sizes - * @param {array} bannerSize the size to be removed + * @param {Array} bannerSizeList list of banner sizes + * @param {Array} bannerSize the size to be removed * @return {boolean} true if successfully removed, false if not found */ @@ -1487,7 +1487,7 @@ function updateMissingSizes(validBidRequest, missingBannerSizes, imp) { /** * @param {object} bid ValidBidRequest object, used to adjust floor * @param {object} imp Impression object to be modified - * @param {array} newSize The new size to be applied + * @param {Array} newSize The new size to be applied * @return {object} newImp Updated impression object */ function createMissingBannerImp(bid, imp, newSize) { @@ -1786,7 +1786,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {array} validBidRequests A list of valid bid request config objects. + * @param {Array} validBidRequests A list of valid bid request config objects. * @param {object} bidderRequest A object contains bids and other info like gdprConsent. * @return {object} Info describing the request to the server. */ @@ -1874,7 +1874,7 @@ export const spec = { * * @param {object} serverResponse A successful response from the server. * @param {object} bidderRequest The bid request sent to the server. - * @return {array} An array of bids which were nested inside the server. + * @return {Array} An array of bids which were nested inside the server. */ interpretResponse: function (serverResponse, bidderRequest) { const bids = []; @@ -1966,8 +1966,8 @@ export const spec = { /** * Determine which user syncs should occur * @param {object} syncOptions - * @param {array} serverResponses - * @returns {array} User sync pixels + * @param {Array} serverResponses + * @returns {Array} User sync pixels */ getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; @@ -2009,8 +2009,8 @@ export const spec = { /** * Build img user sync url - * @param {int} syncsPerBidder number of syncs Per Bidder - * @param {int} index index to pass + * @param {number} syncsPerBidder number of syncs Per Bidder + * @param {number} index index to pass * @returns {string} img user sync url */ function buildImgSyncUrl(syncsPerBidder, index) { @@ -2029,7 +2029,7 @@ function buildImgSyncUrl(syncsPerBidder, index) { /** * Combines all imps into a single object - * @param {array} imps array of imps + * @param {Array} imps array of imps * @returns object */ export function combineImps(imps) { diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 881bbc10b11..7c594e2a1c3 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -71,7 +71,7 @@ export function addFragment(base, path, addition) { /** * Concatenate a base array and an array within an object * non-array bases will be arrays, non-arrays at object key will be discarded - * @param {array} base base array to add to + * @param {Array} base base array to add to * @param {object} source object to get an array from * @param {string} key dot-notated path to array within object * @returns base + source[key] if that's an array diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index bbc1ea21822..5ae9a9fc27a 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -593,7 +593,7 @@ function shouldEmitNonbids(s2sConfig, response) { /** * Global setter that sets eids permissions for bidders * This setter is to be used by userId module when included - * @param {array} newEidPermissions + * @param {Array} newEidPermissions */ function setEidPermissions(newEidPermissions) { eidPermissions = newEidPermissions; diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js index ee28d549475..60e5be2a321 100644 --- a/modules/pubxBidAdapter.js +++ b/modules/pubxBidAdapter.js @@ -55,8 +55,8 @@ export const spec = { /** * Determine which user syncs should occur * @param {object} syncOptions - * @param {array} serverResponses - * @returns {array} User sync pixels + * @param {Array} serverResponses + * @returns {Array} User sync pixels */ getUserSyncs: function (syncOptions, serverResponses) { const kwTag = document.getElementsByName('keywords'); diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 4ca4e4f90a9..5f94ec3dea4 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -195,7 +195,7 @@ registerBidder(spec); /** * @param {object} slot Ad Unit Params by Prebid - * @returns {int} floor by imp type + * @returns {number} floor by imp type */ function applyFloor(slot) { const floors = []; @@ -350,7 +350,7 @@ function mapNative(slot) { /** * @param {object} slot Slot config by Prebid - * @returns {array} Request Assets by OpenRTB Native Ads 1.1 §4.2 + * @returns {Array} Request Assets by OpenRTB Native Ads 1.1 §4.2 */ function mapNativeAssets(slot) { const params = slot.nativeParams || deepAccess(slot, 'mediaTypes.native'); @@ -413,7 +413,7 @@ function mapNativeAssets(slot) { /** * @param {object} image Prebid native.image/icon - * @param {int} type Image or icon code + * @param {number} type Image or icon code * @returns {object} Request Image by OpenRTB Native Ads 1.1 §4.4 */ function mapNativeImage(image, type) { diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index fb6e65e8a65..8968d4c795a 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -386,7 +386,7 @@ export function getAdUnitTargeting(auction) { /** * deep merge array of objects - * @param {array} arr - objects array + * @param {Array} arr - objects array * @return {Object} merged object */ export function deepMerge(arr) { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index e9556b9afc6..9882eb23ac3 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -1184,7 +1184,7 @@ export function hasValidVideoParams(bid) { /** * Make sure the required params are present * @param {Object} schain - * @param {Bool} + * @param {boolean} */ export function hasValidSupplyChainParams(schain) { let isValid = false; diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index f24af853a54..c351b76d7ea 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -38,7 +38,7 @@ var nativeAssetMap = { /** * return native asset type, based on asset id - * @param {int} id - native asset id + * @param {number} id - native asset id * @returns {string} asset type */ const getNativeAssetType = id => { diff --git a/modules/timeoutRtdProvider.js b/modules/timeoutRtdProvider.js index 323a5291e2d..fe9597f6f4e 100644 --- a/modules/timeoutRtdProvider.js +++ b/modules/timeoutRtdProvider.js @@ -67,7 +67,7 @@ function getConnectionSpeed() { * Calculate the time to be added to the timeout * @param {Array} adUnits * @param {Object} rules - * @return {int} + * @return {number} */ function calculateTimeoutModifier(adUnits, rules) { logInfo('Timeout rules', rules); diff --git a/modules/yahoosspBidAdapter.js b/modules/yahoosspBidAdapter.js index a66d76f8689..0453350a85a 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahoosspBidAdapter.js @@ -111,7 +111,7 @@ function extractUserSyncUrls(syncOptions, pixels) { * @param {object} consentData * @param {object} consentData.gpp * @param {string} consentData.gpp.gppConsent - * @param {array} consentData.gpp.applicableSections + * @param {Array} consentData.gpp.applicableSections * @param {object} consentData.gdpr * @param {object} consentData.gdpr.consentString * @param {object} consentData.gdpr.gdprApplies diff --git a/src/adRendering.js b/src/adRendering.js index c3e518eae4d..ac2639c4052 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -11,10 +11,11 @@ const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = constants /** * Emit the AD_RENDER_FAILED event. * - * @param reason one of the values in CONSTANTS.AD_RENDER_FAILED_REASON - * @param message failure description - * @param bid? bid response object that failed to render - * @param id? adId that failed to render + * @param {Object} data + * @param data.reason one of the values in CONSTANTS.AD_RENDER_FAILED_REASON + * @param data.message failure description + * @param [data.bid] bid response object that failed to render + * @param [data.id] adId that failed to render */ export function emitAdRenderFail({ reason, message, bid, id }) { const data = { reason, message }; @@ -28,10 +29,11 @@ export function emitAdRenderFail({ reason, message, bid, id }) { /** * Emit the AD_RENDER_SUCCEEDED event. * (Note: Invocation of this function indicates that the render function did not generate an error, it does not guarantee that tracking for this event has occurred yet.) - * @param doc document object that was used to `.write` the ad. Should be `null` if unavailable (e.g. for documents in + * @param {Object} data + * @param data.doc document object that was used to `.write` the ad. Should be `null` if unavailable (e.g. for documents in * a cross-origin frame). - * @param bid bid response object for the ad that was rendered - * @param id adId that was rendered. + * @param [data.bid] bid response object for the ad that was rendered + * @param [data.id] adId that was rendered. */ export function emitAdRenderSucceeded({ doc, bid, id }) { const data = { doc }; diff --git a/src/adServerManager.js b/src/adServerManager.js index af8fe34920e..7e1290b3983 100644 --- a/src/adServerManager.js +++ b/src/adServerManager.js @@ -34,7 +34,7 @@ const prebid = getGlobal(); /** * @typedef {Object} VideoSupport * - * @function {VideoAdUrlBuilder} buildVideoAdUrl + * @property {VideoAdUrlBuilder} buildVideoAdUrl */ /** diff --git a/src/adloader.js b/src/adloader.js index 664fd03d673..87683828aa8 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -40,7 +40,7 @@ const _approvedLoadExternalJSList = [ * @param {string} moduleCode bidderCode or module code of the module requesting this resource * @param {function} [callback] callback function to be called after the script is loaded * @param {Document} [doc] the context document, in which the script will be loaded, defaults to loaded document - * @param {object} an object of attributes to be added to the script with setAttribute by [key] and [value]; Only the attributes passed in the first request of a url will be added. + * @param {object} attributes an object of attributes to be added to the script with setAttribute by [key] and [value]; Only the attributes passed in the first request of a url will be added. */ export function loadExternalScript(url, moduleCode, callback, doc, attributes) { if (!moduleCode || !url) { diff --git a/src/auction.js b/src/auction.js index 0d677c540b9..37bb2604167 100644 --- a/src/auction.js +++ b/src/auction.js @@ -8,6 +8,13 @@ * */ +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/config.js').MediaTypePriceGranularity} MediaTypePriceGranularity + * @typedef {import('../src/mediaTypes.js').MediaType} MediaType + */ + /** * @typedef {Object} AdUnit An object containing the adUnit configuration. * @@ -128,8 +135,9 @@ export function resetAuctionState() { * @param {number} requestConfig.cbTimeout * @param {Array.} requestConfig.labels * @param {string} requestConfig.auctionId - * @param {{global: {}, bidder: {}}} ortb2Fragments first party data, separated into global + * @param {{global: {}, bidder: {}}} requestConfig.ortb2Fragments first party data, separated into global * (from getConfig('ortb2') + requestBids({ortb2})) and bidder (a map from bidderCode to ortb2) + * @param {Object} requestConfig.metrics * @returns {Auction} auction instance */ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, auctionId, ortb2Fragments, metrics}) { @@ -406,9 +414,10 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a /** * Hook into this to intercept bids before they are added to an auction. * + * @type {Function} * @param adUnitCode * @param bid - * @param {function(String)} reject: a function that, when called, rejects `bid` with the given reason. + * @param {function(String): void} reject a function that, when called, rejects `bid` with the given reason. */ export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { this.dispatch.call(null, adUnitCode, bid); @@ -750,8 +759,9 @@ export function getMediaTypeGranularity(mediaType, mediaTypes, mediaTypePriceGra /** * This function returns the price granularity defined. It can be either publisher defined or default value - * @param bid bid response object - * @param index + * @param {Bid} bid bid response object + * @param {object} obj + * @param {object} obj.index * @returns {string} granularity */ export const getPriceGranularity = (bid, {index = auctionManager.index} = {}) => { @@ -859,7 +869,6 @@ function defaultAdserverTargeting() { /** * @param {string} mediaType * @param {string} bidderCode - * @param {BidRequest} bidReq * @returns {*} */ export function getStandardBidderSettings(mediaType, bidderCode) { diff --git a/src/auctionIndex.js b/src/auctionIndex.js index bdd2b42f9c6..bb3f535b41a 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -1,24 +1,30 @@ +/** + * @typedef {Object} AuctionIndex + * + * @property {function({ auctionId: * }): *} getAuction Returns auction instance for `auctionId` + * @property {function({ transactionId: * }): *} getAdUnit Returns `adUnit` object for `transactionId`. + * You should prefer `getMediaTypes` for looking up bid media types. + * @property {function({ transactionId: *, requestId: * }): *} getMediaTypes Returns mediaTypes object from bidRequest (through `requestId`) falling back to the adUnit (through `transactionId`). + * The bidRequest is given precedence because its mediaTypes can differ from the adUnit's (if bidder-specific labels are in use). + * Bids that have no associated request do not have labels either, and use the adUnit's mediaTypes. + * @property {function({ requestId: *, bidderRequestId: * }): *} getBidderRequest Returns bidderRequest that matches both requestId and bidderRequestId (if either or both are provided). + * Bid responses are not guaranteed to have a corresponding request. + * @property {function({ requestId: * }): *} getBidRequest Returns bidRequest object for requestId. + * Bid responses are not guaranteed to have a corresponding request. + */ + /** * Retrieves request-related bid data. * All methods are designed to work with Bid (response) objects returned by bid adapters. */ export function AuctionIndex(getAuctions) { Object.assign(this, { - /** - * @param auctionId - * @returns {*} Auction instance for `auctionId` - */ getAuction({auctionId}) { if (auctionId != null) { return getAuctions() .find(auction => auction.getAuctionId() === auctionId); } }, - /** - * NOTE: you should prefer {@link #getMediaTypes} for looking up bid media types. - * @param transactionId - * @returns adUnit object for `transactionId` - */ getAdUnit({transactionId}) { if (transactionId != null) { return getAuctions() @@ -26,14 +32,6 @@ export function AuctionIndex(getAuctions) { .find(au => au.transactionId === transactionId); } }, - /** - * @param transactionId - * @param requestId? - * @returns {*} mediaTypes object from bidRequest (through requestId) falling back to the adUnit (through transactionId). - * - * The bidRequest is given precedence because its mediaTypes can differ from the adUnit's (if bidder-specific labels are in use). - * Bids that have no associated request do not have labels either, and use the adUnit's mediaTypes. - */ getMediaTypes({transactionId, requestId}) { if (requestId != null) { const req = this.getBidRequest({requestId}); @@ -47,13 +45,6 @@ export function AuctionIndex(getAuctions) { } } }, - /** - * @param requestId? - * @param bidderRequestId? - * @returns {*} bidderRequest that matches both requestId and bidderRequestId (if either or both are provided). - * - * NOTE: Bid responses are not guaranteed to have a corresponding request. - */ getBidderRequest({requestId, bidderRequestId}) { if (requestId != null || bidderRequestId != null) { let bers = getAuctions().flatMap(a => a.getBidRequests()); @@ -67,12 +58,6 @@ export function AuctionIndex(getAuctions) { } } }, - /** - * @param requestId - * @returns {*} bidRequest object for requestId - * - * NOTE: Bid responses are not guaranteed to have a corresponding request. - */ getBidRequest({requestId}) { if (requestId != null) { return getAuctions() diff --git a/src/auctionManager.js b/src/auctionManager.js index 498c200ba21..8f3cbb56333 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -17,6 +17,7 @@ * @property {function(): Object} getStandardBidderAdServerTargeting - returns standard bidder targeting for all the adapters. Refer http://prebid.org/dev-docs/publisher-api-reference.html#module_pbjs.bidderSettings for more details * @property {function(Object): void} addWinningBid - add a winning bid to an auction based on auctionId * @property {function(): void} clearAllAuctions - clear all auctions for testing + * @property {AuctionIndex} index */ import { uniques, logWarn } from './utils.js'; diff --git a/src/config.js b/src/config.js index d4dc07989af..e3bb5f146ed 100644 --- a/src/config.js +++ b/src/config.js @@ -59,13 +59,6 @@ const GRANULARITY_OPTIONS = { const ALL_TOPICS = '*'; -/** - * @typedef {object} PrebidConfig - * - * @property {string} cache.url Set a url if we should use prebid-cache to store video bids before adding - * bids to the auction. **NOTE** This must be set if you want to use the dfpAdServerVideo module. - */ - export function newConfig() { let listeners = []; let defaults; @@ -551,4 +544,8 @@ export function newConfig() { }; } +/** + * Set a `cache.url` if we should use prebid-cache to store video bids before adding bids to the auction. + * This must be set if you want to use the dfpAdServerVideo module. + */ export const config = newConfig(); diff --git a/src/mediaTypes.js b/src/mediaTypes.js index eea286f7af5..2afa2aefaf9 100644 --- a/src/mediaTypes.js +++ b/src/mediaTypes.js @@ -10,11 +10,11 @@ * @typedef {('adpod')} VideoContext */ -/** @type MediaType */ +/** @type {MediaType} */ export const NATIVE = 'native'; -/** @type MediaType */ +/** @type {MediaType} */ export const VIDEO = 'video'; -/** @type MediaType */ +/** @type {MediaType} */ export const BANNER = 'banner'; -/** @type VideoContext */ +/** @type {VideoContext} */ export const ADPOD = 'adpod'; diff --git a/src/userSync.js b/src/userSync.js index 7e23598a318..153b36816a4 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -309,7 +309,7 @@ export function newUserSync(deps) { * @function syncUsers * @summary Trigger all the user syncs based on publisher-defined timeout * @public - * @params {int} timeout The delay in ms before syncing data - default 0 + * @params {number} timeout The delay in ms before syncing data - default 0 */ publicApi.syncUsers = (timeout = 0) => { if (timeout) { @@ -358,7 +358,7 @@ export const userSync = newUserSync(Object.defineProperties({ * * @property {boolean} enableOverride * @property {boolean} syncEnabled - * @property {int} syncsPerBidder + * @property {number} syncsPerBidder * @property {string[]} enabledBidders * @property {Object} filterSettings */ diff --git a/src/utils.js b/src/utils.js index 288f0d5da5d..67ed7339675 100644 --- a/src/utils.js +++ b/src/utils.js @@ -653,7 +653,7 @@ export function checkCookieSupport() { * * @param {function} func The function which should be executed, once the returned function has been executed * numRequiredCalls times. - * @param {int} numRequiredCalls The number of times which the returned function needs to be called before + * @param {number} numRequiredCalls The number of times which the returned function needs to be called before * func is. */ export function delayExecution(func, numRequiredCalls) { @@ -672,7 +672,7 @@ export function delayExecution(func, numRequiredCalls) { /** * https://stackoverflow.com/a/34890276/428704 * @export - * @param {array} xs + * @param {Array} xs * @param {string} key * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}} */ diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js index 68552eb3d8a..973e90abd5a 100644 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -12,7 +12,7 @@ let constants = require('src/constants.json'); /** * Emit analytics events - * @param {array} eventArr - array of objects to define the events that will fire + * @param {Array} eventArr - array of objects to define the events that will fire * @param {object} eventObj - key is eventType, value is event * @param {string} auctionId - the auction id to attached to the events */ From 8069abdd8d32fe83050704d6c13f939a8e3e0a4d Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Mon, 15 Jan 2024 22:31:35 +0100 Subject: [PATCH 342/760] Richaudience Bid Adapter : add function onTimeout (#10891) * RichaudienceBidAdapter add function onTimeout * Add unit test * revert: Revert changes in integrationExamples/creative.html * fix: Remove useless package in richaudiences test module --------- Co-authored-by: Sergi Gimenez --- modules/richaudienceBidAdapter.js | 21 ++++++++- .../modules/richaudienceBidAdapter_spec.js | 44 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 1625912ddb8..9668435e2fe 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isStr} from '../src/utils.js'; +import {deepAccess, isStr, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -183,6 +183,13 @@ export const spec = { } return syncs }, + + onTimeout: function (data) { + let url = raiGetTimeoutURL(data); + if (url) { + triggerPixel(url); + } + } }; registerBidder(spec); @@ -332,3 +339,15 @@ function raiGetFloor(bid, config) { return 0 } } + +function raiGetTimeoutURL(data) { + let {params, timeout} = data[0] + let url = 'https://s.richaudience.com/err/?ec=6&ev=[timeout_publisher]&pla=[placement_hash]&int=PREBID&pltfm=&node=&dm=[domain]'; + + url = url.replace('[timeout_publisher]', timeout) + url = url.replace('[placement_hash]', params[0].pid) + if (REFERER != null) { + url = url.replace('[domain]', REFERER) + } + return url +} diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index ea45ff7e0b0..52ec6db500e 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -4,6 +4,8 @@ import { spec } from 'modules/richaudienceBidAdapter.js'; import {config} from 'src/config.js'; +import * as utils from 'src/utils.js'; +import sinon from 'sinon'; describe('Richaudience adapter tests', function () { var DEFAULT_PARAMS_NEW_SIZES = [{ @@ -64,6 +66,30 @@ describe('Richaudience adapter tests', function () { user: {} }]; + var DEFAULT_PARAMS_VIDEO_TIMEOUT = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'] + } + }, + bidder: 'richaudience', + params: [{ + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }], + timeout: 3000, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }] + var DEFAULT_PARAMS_VIDEO_IN = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', @@ -879,6 +905,24 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('gpid').and.to.equal('/19968336/header-bid-tag-1#example-2'); }) + describe('onTimeout', function () { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function() { + utils.triggerPixel.restore(); + }); + it('onTimeout exist as a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should send timeout', function () { + spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); + expect(utils.triggerPixel.called).to.equal(true); + expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=http%3A%2F%2Fdomain.com'); + }); + }); + describe('userSync', function () { it('Verifies user syncs iframe include', function () { config.setConfig({ From aed0f067e8eeea40a7d9a89097c0998e22c13de4 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Tue, 16 Jan 2024 04:39:30 +0530 Subject: [PATCH 343/760] PubMatic Bid Adapter : add support to read and pass badv to adserver (#10943) * Added support for badv parameter of ortb2 * Added test cases --------- Co-authored-by: pm-azhar-mulla --- modules/pubmaticBidAdapter.js | 5 ++++- test/spec/modules/pubmaticBidAdapter_spec.js | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 3371573ecc2..192c657d976 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1215,7 +1215,7 @@ export const spec = { // First Party Data const commonFpd = (bidderRequest && bidderRequest.ortb2) || {}; - const { user, device, site, bcat } = commonFpd; + const { user, device, site, bcat, badv } = commonFpd; if (site) { const { page, domain, ref } = payload.site; mergeDeep(payload, {site: site}); @@ -1226,6 +1226,9 @@ export const spec = { if (user) { mergeDeep(payload, {user: user}); } + if (badv) { + mergeDeep(payload, {badv: badv}); + } if (bcat) { blockedIabCategories = blockedIabCategories.concat(bcat); } diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 544ba3d0fbf..5d59ff99a89 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1922,6 +1922,15 @@ describe('PubMatic adapter', function () { expect(data.user.yob).to.equal(1985); }); + it('ortb2.badv should be merged in the request', function() { + const ortb2 = { + badv: ['example.com'] + }; + const request = spec.buildRequests(bidRequests, {ortb2}); + let data = JSON.parse(request.data); + expect(data.badv).to.deep.equal(['example.com']); + }); + describe('ortb2Imp', function() { describe('ortb2Imp.ext.gpid', function() { beforeEach(function () { From dc80cff18092f6902095f4a0d0efb691cecc2f4c Mon Sep 17 00:00:00 2001 From: barRubi <151519394+barRubi@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:46:41 +0200 Subject: [PATCH 344/760] Taboola Bid Adapter : refactor using ortb conversion library (#10910) * refactor using ORTB conversion library * refactor using ORTB conversion library * refactor using ORTB conversion library * refactor using ORTB conversion library --------- Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> --- modules/taboolaBidAdapter.js | 240 +++++++++----------- test/spec/modules/taboolaBidAdapter_spec.js | 188 +++++++-------- 2 files changed, 202 insertions(+), 226 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 7402b725fd2..3b83514b6fd 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,9 +3,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {deepAccess, getWindowSelf, replaceAuctionPrice} from '../src/utils.js'; +import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -81,6 +82,30 @@ export const internal = { } } +const converter = ortbConverter({ + context: { + netRevenue: true, + mediaType: BANNER, + ttl: 300 + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + fillTaboolaImpData(bidRequest, imp); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const reqData = buildRequest(imps, bidderRequest, context); + fillTaboolaReqData(bidderRequest, context.bidRequests[0], reqData) + return reqData; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.nurl = bid.nurl; + bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + return bidResponse + } +}); + export const spec = { supportedMediaTypes: [BANNER], gvlid: GVLID, @@ -93,85 +118,35 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { const [bidRequest] = validBidRequests; - const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; + const data = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); const {publisherId} = bidRequest.params; - const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); - const device = {ua: navigator.userAgent}; - const imps = getImps(validBidRequests); - const user = { - buyeruid: userData.getUserId(gdprConsent, uspConsent), - ext: {} - }; - const regs = { - coppa: 0, - ext: {} - }; - - if (gdprConsent.gdprApplies) { - user.ext.consent = bidderRequest.gdprConsent.consentString; - regs.ext.gdpr = 1; - } - - if (uspConsent) { - regs.ext.us_privacy = uspConsent; - } - - if (bidderRequest.ortb2?.regs?.gpp) { - regs.ext.gpp = bidderRequest.ortb2.regs.gpp; - regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (config.getConfig('coppa')) { - regs.coppa = 1; - } - - const ortb2 = bidderRequest.ortb2 || { - bcat: [], - badv: [], - wlang: [] - }; - - const request = { - id: bidderRequest.bidderRequestId, - imp: imps, - site, - device, - source: {fd: 1}, - tmax: (bidderRequest.timeout == undefined) ? undefined : parseInt(bidderRequest.timeout), - bcat: ortb2.bcat || bidRequest.params.bcat || [], - badv: ortb2.badv || bidRequest.params.badv || [], - wlang: ortb2.wlang || bidRequest.params.wlang || [], - user, - regs, - ext: { - pageType: ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType - } - }; - const url = END_POINT_URL + '?publisher=' + publisherId; return { url, method: 'POST', - data: JSON.stringify(request), + data: data, bids: validBidRequests, options: { withCredentials: false }, }; }, - interpretResponse: (serverResponse, {bids}) => { - if (!bids) { + interpretResponse: (serverResponse, request) => { + if (!request || !request.bids || !request.data) { return []; } - const {bidResponses, cur: currency} = getBidResponses(serverResponse); + if (!serverResponse || !serverResponse.body) { + return []; + } - if (!bidResponses) { + if (!serverResponse.body.seatbid || !serverResponse.body.seatbid.length || !serverResponse.body.seatbid[0].bid || !serverResponse.body.seatbid[0].bid.length) { return []; } - return bidResponses.map((bidResponse) => getBid(bids, currency, bidResponse)).filter(Boolean); + const bids = converter.fromORTB({response: serverResponse.body, request: request.data}).bids; + return bids; }, onBidWon: (bid) => { if (bid.nurl) { @@ -235,34 +210,76 @@ function getSiteProperties({publisherId}, refererInfo, ortb2) { } } -function getImps(validBidRequests) { - return validBidRequests.map((bid, id) => { - const {tagId, position} = bid.params; - const imp = { - id: id + 1, - banner: getBanners(bid, position), - tagid: tagId - } - if (typeof bid.getFloor === 'function') { - const floorInfo = bid.getFloor({ - currency: CURRENCY, - mediaType: BANNER, - size: '*' - }); - if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { - imp.bidfloor = parseFloat(floorInfo.floor); - imp.bidfloorcur = CURRENCY; - } - } else { - const {bidfloor = null, bidfloorcur = CURRENCY} = bid.params; - imp.bidfloor = bidfloor; - imp.bidfloorcur = bidfloorcur; - } - imp['ext'] = { - gpid: deepAccess(bid, 'ortb2Imp.ext.gpid') +function fillTaboolaReqData(bidderRequest, bidRequest, data) { + const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; + const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); + const device = {ua: navigator.userAgent}; + const user = { + buyeruid: userData.getUserId(gdprConsent, uspConsent), + ext: {} + }; + const regs = { + coppa: 0, + ext: {} + }; + + if (gdprConsent.gdprApplies) { + user.ext.consent = bidderRequest.gdprConsent.consentString; + regs.ext.gdpr = 1; + } + + if (uspConsent) { + regs.ext.us_privacy = uspConsent; + } + + if (bidderRequest.ortb2?.regs?.gpp) { + regs.ext.gpp = bidderRequest.ortb2.regs.gpp; + regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (config.getConfig('coppa')) { + regs.coppa = 1; + } + + const ortb2 = bidderRequest.ortb2 || { + bcat: [], + badv: [], + wlang: [] + }; + + data.id = bidderRequest.bidderRequestId; + data.site = site; + data.device = device; + data.source = {fd: 1}; + data.tmax = (bidderRequest.timeout == undefined) ? undefined : parseInt(bidderRequest.timeout); + data.bcat = ortb2.bcat || bidRequest.params.bcat || []; + data.badv = ortb2.badv || bidRequest.params.badv || []; + data.wlang = ortb2.wlang || bidRequest.params.wlang || []; + data.user = user; + data.regs = regs; + deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType); +} + +function fillTaboolaImpData(bid, imp) { + const {tagId, position} = bid.params; + imp.banner = getBanners(bid, position); + imp.tagid = tagId; + + if (typeof bid.getFloor === 'function') { + const floorInfo = bid.getFloor({ + currency: CURRENCY, + size: '*' + }); + if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + imp.bidfloor = parseFloat(floorInfo.floor); + imp.bidfloorcur = CURRENCY; } - return imp; - }); + } else { + const {bidfloor = null, bidfloorcur = CURRENCY} = bid.params; + imp.bidfloor = bidfloor; + imp.bidfloorcur = bidfloorcur; + } + deepSetValue(imp, 'ext.gpid', deepAccess(bid, 'ortb2Imp.ext.gpid')); } function getBanners(bid, pos) { @@ -283,51 +300,4 @@ function getSizes(sizes) { } } -function getBidResponses({body}) { - if (!body) { - return []; - } - - const {seatbid, cur} = body; - - if (!seatbid.length || !seatbid[0].bid || !seatbid[0].bid.length) { - return []; - } - - return { - bidResponses: seatbid[0].bid, - cur - }; -} - -function getBid(bids, currency, bidResponse) { - if (!bidResponse) { - return; - } - let { - price: cpm, nurl, crid: creativeId, adm: ad, w: width, h: height, exp: ttl, adomain: advertiserDomains, meta = {} - } = bidResponse; - let requestId = bids[bidResponse.impid - 1].bidId; - if (advertiserDomains && advertiserDomains.length > 0) { - meta.advertiserDomains = advertiserDomains - } - - ad = replaceAuctionPrice(ad, cpm); - - return { - requestId, - ttl, - mediaType: BANNER, - cpm, - creativeId, - currency, - ad, - width, - height, - meta, - nurl, - netRevenue: true - }; -} - registerBidder(spec); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 280376e4e56..dd91c410d08 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -173,10 +173,10 @@ describe('Taboola Adapter', function () { } it('should build display request', function () { + const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); const expectedData = { - id: 'mock-uuid', 'imp': [{ - 'id': 1, + 'id': res.data.imp[0].id, 'banner': { format: [{ w: displayBidRequestParams.sizes[0][0], @@ -193,6 +193,8 @@ describe('Taboola Adapter', function () { 'bidfloorcur': 'USD', 'ext': {} }], + id: 'mock-uuid', + 'test': 0, 'site': { 'id': commonBidRequest.params.publisherId, 'name': commonBidRequest.params.publisherId, @@ -215,10 +217,8 @@ describe('Taboola Adapter', function () { 'ext': {} }; - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); - expect(res.url).to.equal(`${END_POINT_URL}?publisher=${commonBidRequest.params.publisherId}`); - expect(res.data).to.deep.equal(JSON.stringify(expectedData)); + expect(JSON.stringify(res.data)).to.deep.equal(JSON.stringify(expectedData)); }); it('should pass optional parameters in request', function () { @@ -233,9 +233,8 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([bidRequest], commonBidderRequest); - const resData = JSON.parse(res.data); - expect(resData.imp[0].bidfloor).to.deep.equal(0.25); - expect(resData.imp[0].bidfloorcur).to.deep.equal('EUR'); + expect(res.data.imp[0].bidfloor).to.deep.equal(0.25); + expect(res.data.imp[0].bidfloorcur).to.deep.equal('EUR'); }); it('should pass bid floor', function () { @@ -250,9 +249,8 @@ describe('Taboola Adapter', function () { } }; const res = spec.buildRequests([bidRequest], commonBidderRequest); - const resData = JSON.parse(res.data); - expect(resData.imp[0].bidfloor).to.deep.equal(2.7); - expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); + expect(res.data.imp[0].bidfloor).to.deep.equal(2.7); + expect(res.data.imp[0].bidfloorcur).to.deep.equal('USD'); }); it('should pass bid floor even if it is a bid floor param', function () { @@ -272,9 +270,8 @@ describe('Taboola Adapter', function () { } }; const res = spec.buildRequests([bidRequest], commonBidderRequest); - const resData = JSON.parse(res.data); - expect(resData.imp[0].bidfloor).to.deep.equal(2.7); - expect(resData.imp[0].bidfloorcur).to.deep.equal('USD'); + expect(res.data.imp[0].bidfloor).to.deep.equal(2.7); + expect(res.data.imp[0].bidfloorcur).to.deep.equal('USD'); }); it('should pass impression position', function () { @@ -288,8 +285,7 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([bidRequest], commonBidderRequest); - const resData = JSON.parse(res.data); - expect(resData.imp[0].banner.pos).to.deep.equal(2); + expect(res.data.imp[0].banner.pos).to.deep.equal(2); }); it('should pass gpid if configured', function () { @@ -305,8 +301,23 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([bidRequest], commonBidderRequest); - const resData = JSON.parse(res.data); - expect(resData.imp[0].ext.gpid).to.deep.equal('/homepage/#1'); + expect(res.data.imp[0].ext.gpid).to.deep.equal('/homepage/#1'); + }); + + it('should pass new parameter to imp ext', function () { + const ortb2Imp = { + ext: { + example: 'example' + } + } + const bidRequest = { + ...defaultBidRequest, + ortb2Imp: ortb2Imp, + params: {...commonBidRequest.params} + }; + + const res = spec.buildRequests([bidRequest], commonBidderRequest); + expect(res.data.imp[0].ext.example).to.deep.equal('example'); }); it('should pass bidder timeout', function () { @@ -315,8 +326,7 @@ describe('Taboola Adapter', function () { timeout: 500 } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.tmax).to.equal(500); + expect(res.data.tmax).to.equal(500); }); it('should pass bidder tmax as int', function () { @@ -325,8 +335,7 @@ describe('Taboola Adapter', function () { timeout: '500' } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.tmax).to.equal(500); + expect(res.data.tmax).to.equal(500); }); it('should pass bidder timeout as null', function () { @@ -335,8 +344,7 @@ describe('Taboola Adapter', function () { timeout: null } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.tmax).to.equal(undefined); + expect(res.data.tmax).to.equal(undefined); }); describe('first party data', function () { @@ -350,10 +358,9 @@ describe('Taboola Adapter', function () { } } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.bcat).to.deep.equal(bidderRequest.ortb2.bcat) - expect(resData.badv).to.deep.equal(bidderRequest.ortb2.badv) - expect(resData.wlang).to.deep.equal(bidderRequest.ortb2.wlang) + expect(res.data.bcat).to.deep.equal(bidderRequest.ortb2.bcat) + expect(res.data.badv).to.deep.equal(bidderRequest.ortb2.badv) + expect(res.data.wlang).to.deep.equal(bidderRequest.ortb2.wlang) }); it('should pass pageType if exists in ortb2', function () { @@ -368,8 +375,20 @@ describe('Taboola Adapter', function () { } } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.ext.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); + expect(res.data.ext.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); + }); + + it('should pass additional parameter in request', function () { + const bidderRequest = { + ...commonBidderRequest, + ortb2: { + ext: { + example: 'example' + } + } + } + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.ext.example).to.deep.equal(bidderRequest.ortb2.ext.example); }); }); @@ -386,9 +405,8 @@ describe('Taboola Adapter', function () { }; const res = spec.buildRequests([defaultBidRequest], bidderRequest) - const resData = JSON.parse(res.data) - expect(resData.user.ext.consent).to.equal('consentString') - expect(resData.regs.ext.gdpr).to.equal(1) + expect(res.data.user.ext.consent).to.equal('consentString') + expect(res.data.regs.ext.gdpr).to.equal(1) }); it('should pass GPP consent if exist in ortb2', function () { @@ -400,9 +418,8 @@ describe('Taboola Adapter', function () { } const res = spec.buildRequests([defaultBidRequest], {...commonBidderRequest, ortb2}) - const resData = JSON.parse(res.data) - expect(resData.regs.ext.gpp).to.equal('testGpp') - expect(resData.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) + expect(res.data.regs.ext.gpp).to.equal('testGpp') + expect(res.data.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) }); it('should pass us privacy consent', function () { @@ -413,16 +430,14 @@ describe('Taboola Adapter', function () { uspConsent: 'consentString' } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.regs.ext.us_privacy).to.equal('consentString'); + expect(res.data.regs.ext.us_privacy).to.equal('consentString'); }); it('should pass coppa consent', function () { config.setConfig({coppa: true}) const res = spec.buildRequests([defaultBidRequest], commonBidderRequest) - const resData = JSON.parse(res.data); - expect(resData.regs.coppa).to.equal(1) + expect(res.data.regs.coppa).to.equal(1) config.resetConfig() }); @@ -439,8 +454,7 @@ describe('Taboola Adapter', function () { timeout: 500 } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(51525152); + expect(res.data.user.buyeruid).to.equal(51525152); }); it('should get user id from cookie if local storage isn`t defined', function () { @@ -454,9 +468,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest }; const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - - expect(resData.user.buyeruid).to.equal('12121212'); + expect(res.data.user.buyeruid).to.equal('12121212'); }); it('should get user id from tgid cookie if local storage isn`t defined', function () { @@ -470,9 +482,8 @@ describe('Taboola Adapter', function () { ...commonBidderRequest }; const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal('d966c5be-c49f-4f73-8cd1-37b6b5790653-tuct9f7bf10'); + expect(res.data.user.buyeruid).to.equal('d966c5be-c49f-4f73-8cd1-37b6b5790653-tuct9f7bf10'); }); it('should get user id from TRC if local storage and cookie isn`t defined', function () { @@ -488,8 +499,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(window.TRC.user_id); + expect(res.data.user.buyeruid).to.equal(window.TRC.user_id); delete window.TRC; }); @@ -502,8 +512,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(0); + expect(res.data.user.buyeruid).to.equal(0); }); it('should set buyeruid to be 0 if it`s a new user', function () { @@ -511,13 +520,29 @@ describe('Taboola Adapter', function () { ...commonBidderRequest } const res = spec.buildRequests([defaultBidRequest], bidderRequest); - const resData = JSON.parse(res.data); - expect(resData.user.buyeruid).to.equal(0); + expect(res.data.user.buyeruid).to.equal(0); }); }); }) describe('interpretResponse', function () { + const defaultBidRequest = { + ...createBidRequest(), + ...displayBidRequestParams, + }; + const commonBidderRequest = { + bidderRequestId: 'mock-uuid', + refererInfo: { + page: 'https://example.com/ref', + ref: 'https://ref', + domain: 'example.com', + } + }; + const bidderRequest = { + ...commonBidderRequest + }; + const request = spec.buildRequests([defaultBidRequest], bidderRequest); + const serverResponse = { body: { 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', @@ -526,7 +551,7 @@ describe('Taboola Adapter', function () { 'bid': [ { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '1', + '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', @@ -551,15 +576,6 @@ describe('Taboola Adapter', function () { } }; - const request = { - bids: [ - { - ...commonBidRequest, - ...displayBidRequestParams - } - ] - } - it('should return empty array if no valid bids', function () { const res = spec.interpretResponse(serverResponse, []) expect(res).to.be.an('array').that.is.empty @@ -593,18 +609,7 @@ describe('Taboola Adapter', function () { }); it('should interpret multi impression request', function () { - const multiRequest = { - bids: [ - { - ...createBidRequest(), - ...displayBidRequestParams - }, - { - ...createBidRequest(), - ...displayBidRequestParams - } - ] - } + const multiRequest = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); const multiServerResponse = { body: { @@ -614,7 +619,7 @@ describe('Taboola Adapter', function () { 'bid': [ { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '2', + 'impid': multiRequest.data.imp[0].id, 'price': 0.342068, 'adid': '2785119545551083381', 'adm': 'ADM2', @@ -631,7 +636,7 @@ describe('Taboola Adapter', function () { }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '1', + 'impid': multiRequest.data.imp[1].id, 'price': 0.342068, 'adid': '2785119545551083381', 'adm': 'ADM1', @@ -662,6 +667,8 @@ describe('Taboola Adapter', function () { requestId: multiRequest.bids[1].bidId, cpm: bid.price, creativeId: bid.crid, + creative_id: bid.crid, + seatBidId: multiServerResponse.body.seatbid[0].bid[0].id, ttl: 60, netRevenue: true, currency: multiServerResponse.body.cur, @@ -678,6 +685,8 @@ describe('Taboola Adapter', function () { requestId: multiRequest.bids[0].bidId, cpm: bid.price, creativeId: bid.crid, + creative_id: bid.crid, + seatBidId: multiServerResponse.body.seatbid[0].bid[1].id, ttl: 60, netRevenue: true, currency: multiServerResponse.body.cur, @@ -701,8 +710,10 @@ describe('Taboola Adapter', function () { 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, @@ -728,8 +739,10 @@ describe('Taboola Adapter', function () { 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: 125, netRevenue: true, currency: serverResponse.body.cur, @@ -748,18 +761,7 @@ describe('Taboola Adapter', function () { }); it('should replace AUCTION_PRICE macro in adm', function () { - const multiRequest = { - bids: [ - { - ...createBidRequest(), - ...displayBidRequestParams - }, - { - ...createBidRequest(), - ...displayBidRequestParams - } - ] - } + const multiRequest = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); const multiServerResponseWithMacro = { body: { 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', @@ -768,7 +770,7 @@ describe('Taboola Adapter', function () { 'bid': [ { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '2', + 'impid': multiRequest.data.imp[0].id, 'price': 0.34, 'adid': '2785119545551083381', 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', @@ -785,7 +787,7 @@ describe('Taboola Adapter', function () { }, { 'id': '0b3dd94348-134b-435f-8db5-6bf5afgfc39e86c', - 'impid': '1', + 'impid': multiRequest.data.imp[1].id, 'price': 0.35, 'adid': '2785119545551083381', 'adm': 'ADM2,\\nwp:\'${AUCTION_PRICE}\'', @@ -815,6 +817,8 @@ describe('Taboola Adapter', function () { requestId: multiRequest.bids[1].bidId, cpm: multiServerResponseWithMacro.body.seatbid[0].bid[0].price, creativeId: bid.crid, + creative_id: bid.crid, + seatBidId: multiServerResponseWithMacro.body.seatbid[0].bid[0].id, ttl: 60, netRevenue: true, currency: multiServerResponseWithMacro.body.cur, @@ -831,6 +835,8 @@ describe('Taboola Adapter', function () { requestId: multiRequest.bids[0].bidId, cpm: multiServerResponseWithMacro.body.seatbid[0].bid[1].price, creativeId: bid.crid, + creative_id: bid.crid, + seatBidId: multiServerResponseWithMacro.body.seatbid[0].bid[1].id, ttl: 60, netRevenue: true, currency: multiServerResponseWithMacro.body.cur, From 37aba0a35e34ce9a1a4df276777008f50f895b72 Mon Sep 17 00:00:00 2001 From: Tachfine Date: Tue, 16 Jan 2024 14:24:40 +0100 Subject: [PATCH 345/760] [Fledge] Add support for seller signals per imp (#10948) Load seller signals for each impression and include them with the response level seller signals before returning the bid and fledge auction config. --- modules/criteoBidAdapter.js | 10 +++- test/spec/modules/criteoBidAdapter_spec.js | 59 +++++++++++++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index d91d8c56cf9..2fbf3d9ab9e 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -276,20 +276,26 @@ export const spec = { if (isArray(body.ext?.igbid)) { const seller = body.ext.seller || FLEDGE_SELLER_DOMAIN; const sellerTimeout = body.ext.sellerTimeout || FLEDGE_SELLER_TIMEOUT; - const sellerSignals = body.ext.sellerSignals || {}; 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; } if (!sellerSignals.sellerCurrency && bidRequest.params.bidFloorCur) { sellerSignals.sellerCurrency = bidRequest.params.bidFloorCur; } - const bidId = bidRequest.bidId; + if (body?.ext?.sellerSignalsPerImp !== undefined) { + const sellerSignalsPerImp = body.ext.sellerSignalsPerImp[bidId]; + if (sellerSignalsPerImp !== undefined) { + sellerSignals = {...sellerSignals, ...sellerSignalsPerImp}; + } + } fledgeAuctionConfigs.push({ bidId, config: { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 36132fe963b..5dc6d74d90e 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2470,12 +2470,30 @@ describe('The Criteo bidding adapter', function () { foo: 'baz', }, }] + }, { + impid: 'test-bidId-2', + igbuyer: [{ + origin: 'https://first-buyer-domain.com', + buyerdata: { + foo: 'bar', + }, + }, { + origin: 'https://second-buyer-domain.com', + buyerdata: { + foo: 'baz', + }, + }] }], seller: 'https://seller-domain.com', sellerTimeout: 500, sellerSignals: { foo: 'bar', }, + sellerSignalsPerImp: { + 'test-bidId': { + foo2: 'bar2', + } + }, }, }, }; @@ -2502,15 +2520,54 @@ describe('The Criteo bidding adapter', function () { bidFloorCur: 'EUR' } }, + { + bidId: 'test-bidId-2', + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + bidFloor: 1, + bidFloorCur: 'EUR' + } + }, ]; const request = spec.buildRequests(bidRequests, bidderRequest); const interpretedResponse = spec.interpretResponse(response, request); expect(interpretedResponse).to.have.property('bids'); expect(interpretedResponse).to.have.property('fledgeAuctionConfigs'); expect(interpretedResponse.bids).to.have.lengthOf(0); - expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(1); + 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' + }, + }, + seller: 'https://seller-domain.com', + sellerTimeout: 500, + sellerSignals: { + foo: 'bar', + foo2: 'bar2', + floor: 1, + sellerCurrency: 'EUR', + }, + }, + }); + expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ + bidId: 'test-bidId-2', config: { auctionSignals: {}, decisionLogicUrl: 'https://grid-mercury.criteo.com/fledge/decision', From 5bf8083d5c91a9665230dedee6d3a839494a68c0 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Tue, 16 Jan 2024 18:49:01 +0200 Subject: [PATCH 346/760] Adman Bid Adapter : add GVLID (#10949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Adman bid adapter * Add supportedMediaTypes property * Update ADman Media bidder adapter * Remove console.log * Fix typo * revert package-json.lock * Delete package-lock.json * back to original package-lock.json * catch pbjs error * catch pbjs error * catch pbjs error * log * remove eu url * remove eu url * remove eu url * remove eu url * remove eu url * Update admanBidAdapter.js add consnet to sync url * Update admanBidAdapter.js fix import * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js lint fix * Update admanBidAdapter.js check consent object data availability * сompatible with prebid v5 * add Lotame Panorama ID * update getUserSyncs * fix * fix tests * remove package-lock.json * update sync url * update test * add idx (UserID Module) * update tests * remove traffic param * handle transactionID param * send transactionID param in imp.ext * rename transactionID to transactionId * update tests * additional content handle * rollback content * content handle via hb integration * update gdprConsent * return old package-lock * add GVLID * rem package-lock.json from changes --------- Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman Co-authored-by: SmartyAdman <> --- modules/admanBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 5eac02b7420..b78737722bd 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -4,6 +4,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +const GVLID = 149; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; const URL_SYNC = 'https://sync.admanmedia.com'; @@ -57,6 +58,7 @@ function getUserId(eids, id, source, uidExt) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid) => { From d6c39f555c4bd8ac134ec48d08a2c254362f70a4 Mon Sep 17 00:00:00 2001 From: faithnh Date: Wed, 17 Jan 2024 02:22:49 +0900 Subject: [PATCH 347/760] Unicorn Bid Adapter : support id5 (#10947) * support id5 * fix lint change quote --- modules/unicornBidAdapter.js | 23 +++++++++++ test/spec/modules/unicornBidAdapter_spec.js | 45 ++++++++++++++++----- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 66aaf4a17e5..429ba8f60ba 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -87,10 +87,33 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { accountId: deepAccess(validBidRequests[0], 'params.accountId') } }; + const eids = initializeEids(validBidRequests[0]); + if (eids.length > 0) { + request.user.eids = eids; + } + logInfo('[UNICORN] OpenRTB Formatted Request:', request); return JSON.stringify(request); } +const initializeEids = (bidRequest) => { + let eids = []; + + let id5 = deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + eids.push({ + source: 'id5-sync.com', + uids: [ + { + id: id5 + } + ] + }); + } + + return eids; +} + const interpretResponse = (serverResponse, request) => { logInfo('[UNICORN] interpretResponse.serverResponse:', serverResponse); logInfo('[UNICORN] interpretResponse.request:', request); diff --git a/test/spec/modules/unicornBidAdapter_spec.js b/test/spec/modules/unicornBidAdapter_spec.js index 0abb09bfb78..bd9175dac1e 100644 --- a/test/spec/modules/unicornBidAdapter_spec.js +++ b/test/spec/modules/unicornBidAdapter_spec.js @@ -1,4 +1,5 @@ import {assert, expect} from 'chai'; +import * as utils from 'src/utils.js'; import {spec} from 'modules/unicornBidAdapter.js'; import * as _ from 'lodash'; @@ -496,6 +497,17 @@ describe('unicornBidAdapterTest', () => { }); describe('buildBidRequest', () => { + const removeUntestableAttrs = data => { + delete data['device']; + delete data['site']['domain']; + delete data['site']['page']; + delete data['id']; + data['imp'].forEach(imp => { + delete imp['id']; + }) + delete data['user']['id']; + return data; + }; before(function () { $$PREBID_GLOBAL$$.bidderSettings = { unicorn: { @@ -508,17 +520,6 @@ describe('unicornBidAdapterTest', () => { }); it('buildBidRequest', () => { const req = spec.buildRequests(validBidRequests, bidderRequest); - const removeUntestableAttrs = data => { - delete data['device']; - delete data['site']['domain']; - delete data['site']['page']; - delete data['id']; - data['imp'].forEach(imp => { - delete imp['id']; - }) - delete data['user']['id']; - return data; - }; const uid = JSON.parse(req.data)['user']['id']; const reqData = removeUntestableAttrs(JSON.parse(req.data)); const openRTBRequestData = removeUntestableAttrs(openRTBRequest); @@ -527,6 +528,28 @@ describe('unicornBidAdapterTest', () => { const uid2 = JSON.parse(req2.data)['user']['id']; assert.deepStrictEqual(uid, uid2); }); + it('test if contains ID5', () => { + let _validBidRequests = utils.deepClone(validBidRequests); + _validBidRequests[0].userId = { + id5id: { + uid: 'id5_XXXXX' + } + } + const req = spec.buildRequests(_validBidRequests, bidderRequest); + const reqData = removeUntestableAttrs(JSON.parse(req.data)); + const openRTBRequestData = removeUntestableAttrs(utils.deepClone(openRTBRequest)); + openRTBRequestData.user.eids = [ + { + source: 'id5-sync.com', + uids: [ + { + id: 'id5_XXXXX' + } + ] + } + ] + assert.deepStrictEqual(reqData, openRTBRequestData); + }) }); describe('interpretResponse', () => { From a63e74890aa02cc5b2ddf17310595daacce5de2c Mon Sep 17 00:00:00 2001 From: Deepthi Neeladri Date: Wed, 17 Jan 2024 16:18:01 +0530 Subject: [PATCH 348/760] Yahoo SSP Bid Adapter : update maintainer email address (#10954) * change * removing global filtet * reverting page * message * adapter change * remove space * renderer exist case * reverting package-lock.json * adding schain object * adding tagid * syntaxx error fix * video.html * space trailing * space * tagid * inventoryId and placement * rewarded video * comment * update maintainer email address --------- Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: Deepthi Neeladri Sravana Co-authored-by: dsravana --- modules/yahoosspBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/yahoosspBidAdapter.md b/modules/yahoosspBidAdapter.md index e7074e64682..62fe0f22a55 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahoosspBidAdapter.md @@ -1,7 +1,7 @@ # Overview **Module Name:** Yahoo Advertising Bid Adapter **Module Type:** Bidder Adapter -**Maintainer:** hb-fe-tech@yahooinc.com +**Maintainer:** prebid-tech-team@yahooinc.com # Description The Yahoo Advertising Bid Adapter is an OpenRTB interface that consolidates all previous "Oath.inc" adapters such as: "aol", "oneMobile", "oneDisplay" & "oneVideo" supply-side platforms. From 2e4b98eb78618cc427a7fb12b78fee976b350210 Mon Sep 17 00:00:00 2001 From: Scott Sundahl <37344964+ssundahlTTD@users.noreply.github.com> Date: Wed, 17 Jan 2024 07:39:20 -0700 Subject: [PATCH 349/760] EUID Id Module : add support for client side token generation (#10885) * enable cstg for euid * test added for euid cstg * fixed euid cstg test and updated docs --- modules/euidIdSystem.js | 10 ++++- modules/euidIdSystem.md | 52 +++++++++++++++++++++++++- modules/uid2IdSystem.js | 14 +------ modules/uid2IdSystem_shared.js | 12 ++++++ test/spec/modules/euidIdSystem_spec.js | 34 +++++++++++++++-- test/spec/modules/uid2IdSystem_spec.js | 44 ++++++++++------------ 6 files changed, 124 insertions(+), 42 deletions(-) diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index 6a3a0869c0e..a29da69d6c7 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -12,7 +12,7 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here. // eslint-disable-next-line prebid/validate-imports -import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js'; +import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; const MODULE_NAME = 'euid'; const MODULE_REVISION = Uid2CodeVersion; @@ -99,6 +99,14 @@ export const euidIdSubmodule = { internalStorage: ADVERTISING_COOKIE }; + if (FEATURES.UID2_CSTG) { + mappedConfig.cstg = { + serverPublicKey: config?.params?.serverPublicKey, + subscriptionId: config?.params?.subscriptionId, + ...extractIdentityFromParams(config?.params ?? {}) + } + } + _logInfo(`EUID configuration loaded and mapped.`, mappedConfig); const result = Uid2GetId(mappedConfig, storage, _logInfo, _logWarn); _logInfo(`EUID getId returned`, result); return result; diff --git a/modules/euidIdSystem.md b/modules/euidIdSystem.md index e3e16bce89d..72e40b8ce7b 100644 --- a/modules/euidIdSystem.md +++ b/modules/euidIdSystem.md @@ -1,9 +1,59 @@ ## EUID User ID Submodule -EUID requires initial tokens to be generated server-side. The EUID module handles storing, providing, and optionally refreshing them. The module can operate in one of two different modes: *Client Refresh* mode or *Server Only* mode. +The EUID module handles storing, providing, and optionally refreshing tokens. While initial tokens traditionally required server-side generation, the introduction of the *Client-Side Token Generation (CSTG)* mode offers publishers the flexibility to generate EUID tokens directly from the module, eliminating this need. Publishers can choose to operate the module in one of three distinct modes: *Client Refresh* mode, *Server Only* mode and *Client-Side Token Generation* mode. *Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. +*Client-Side Token Generation* mode is included in EUID module by default. However, it's important to note that this mode is created and made available recently. For publishers who do not intend to use it, you have the option to instruct the build to exclude the code related to this feature: + +``` + $ gulp build --modules=uid2IdSystem --disable UID2_CSTG +``` +If you do plan to use Client-Side Token Generation (CSTG) mode, please consult the EUID Team first as they will provide required configuration values for you to use (see the Client-Side Token Generation (CSTG) mode section below for details) + +**This mode is created and made available recently. Please consult EUID Team first as they will provide required configuration values for you to use.** + +For publishers seeking a purely client-side integration without the complexities of server-side involvement, the CSTG mode is highly recommended. This mode requires the provision of a public key, subscription ID and [directly identifying information (DII)](https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii) - either emails or phone numbers. In the CSTG mode, the module takes on the responsibility of encrypting the DII, generating the EUID token, and handling token refreshes when necessary. + +To configure the module to use this mode, you must: +1. Set `parmas.serverPublicKey` and `params.subscriptionId` (please reach out to the UID2 team to obtain these values) +2. Provide **ONLY ONE DII** by setting **ONLY ONE** of `params.email`/`params.phone`/`params.emailHash`/`params.phoneHash` + +Below is a table that provides guidance on when to use each directly identifying information (DII) parameter, along with information on whether normalization and hashing are required by the publisher for each parameter. + +| DII param | When to use it | Normalization required by publisher? | Hashing required by publisher? | +|------------------|-------------------------------------------------------|--------------------------------------|--------------------------------| +| params.email | When you have users' email address | No | No | +| params.phone | When you have user's phone number | Yes | No | +| params.emailHash | When you have user's hashed, normalized email address | Yes | Yes | +| params.phoneHash | When you have user's hashed, normalized phone number | Yes | Yes | + + +*Note that setting params.email will normalize email addresses, but params.phone requires phone numbers to be normalized.* + +Refer to [Normalization and Encoding](#normalization-and-encoding) for details on email address normalization, SHA-256 hashing and Base64 encoding. + +### CSTG example + +Configuration: +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'euid', + params: { + serverPublicKey: '...server public key...', + subscriptionId: '...subcription id...', + email: 'user@email.com', + //phone: '+0000000', + //emailHash: '...email hash...', + //phoneHash: '...phone hash ...' + } + }] + } +}); +``` + ## Client Refresh mode This is the recommended mode for most scenarios. In this mode, the full response body from the EUID Token Generate or Token Refresh endpoint must be provided to the module. As long as the refresh token remains valid, the module will refresh the advertising token as needed. diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index b9b3dfa2380..13c0303e0b3 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -13,7 +13,7 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // RE below lint exception: UID2 and EUID are separate modules, but the protocol is the same and shared code makes sense here. // eslint-disable-next-line prebid/validate-imports -import { Uid2GetId, Uid2CodeVersion } from './uid2IdSystem_shared.js'; +import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; const MODULE_NAME = 'uid2'; @@ -34,18 +34,6 @@ function createLogger(logger, prefix) { } } -function extractIdentityFromParams(params) { - const keysToCheck = ['emailHash', 'phoneHash', 'email', 'phone']; - - for (let key of keysToCheck) { - if (params.hasOwnProperty(key)) { - return { [key]: params[key] }; - } - } - - return {}; -} - const _logInfo = createLogger(logInfo, LOG_PRE_FIX); const _logWarn = createLogger(logWarn, LOG_PRE_FIX); diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 29837c5f012..acc440eafc5 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -755,3 +755,15 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { storageManager.storeValue(tokens); return { id: tokens }; } + +export function extractIdentityFromParams(params) { + const keysToCheck = ['emailHash', 'phoneHash', 'email', 'phone']; + + for (let key of keysToCheck) { + if (params.hasOwnProperty(key)) { + return { [key]: params[key] }; + } + } + + return {}; +} diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index 9e4598bb5f5..98770fa80bc 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -3,6 +3,7 @@ import {config} from 'src/config.js'; import {euidIdSubmodule} from 'modules/euidIdSystem.js'; import 'modules/consentManagement.js'; import 'src/prebid.js'; +import * as utils from 'src/utils.js'; import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; @@ -22,13 +23,18 @@ const auctionDelayMs = 10; const makeEuidIdentityContainer = (token) => ({euid: {id: token}}); const useLocalStorage = true; + const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'euid', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}, ...extraSettings}] }, debug }); +const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } +const clientSideGeneratedToken = 'client-side-generated-advertising-token'; + const apiUrl = 'https://prod.euid.eu/v2/token/refresh'; +const cstgApiUrl = 'https://prod.euid.eu/v2/token/client-generate'; const headers = { 'Content-Type': 'application/json' }; -const makeSuccessResponseBody = () => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: refreshedToken } })); +const makeSuccessResponseBody = (token) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeEuidIdentityContainer(token)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); @@ -36,6 +42,7 @@ describe('EUID module', function() { let suiteSandbox, restoreSubtleToUndefined = false; const configureEuidResponse = (httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); + const configureEuidCstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); before(function() { uninstallGdprEnforcement(); @@ -43,10 +50,18 @@ describe('EUID module', function() { suiteSandbox = sinon.sandbox.create(); if (typeof window.crypto.subtle === 'undefined') { restoreSubtleToUndefined = true; - window.crypto.subtle = { importKey: () => {}, decrypt: () => {} }; + window.crypto.subtle = { importKey: () => {}, digest: () => {}, decrypt: () => {}, deriveKey: () => {}, encrypt: () => {}, generateKey: () => {}, exportKey: () => {} }; } suiteSandbox.stub(window.crypto.subtle, 'importKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'digest').callsFake(() => Promise.resolve('hashed_value')); suiteSandbox.stub(window.crypto.subtle, 'decrypt').callsFake((settings, key, data) => Promise.resolve(new Uint8Array([...settings.iv, ...data]))); + suiteSandbox.stub(window.crypto.subtle, 'deriveKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'exportKey').callsFake(() => Promise.resolve()); + suiteSandbox.stub(window.crypto.subtle, 'encrypt').callsFake(() => Promise.resolve(new ArrayBuffer())); + suiteSandbox.stub(window.crypto.subtle, 'generateKey').callsFake(() => Promise.resolve({ + privateKey: {}, + publicKey: {} + })); }); after(function() { suiteSandbox.restore(); @@ -113,10 +128,23 @@ describe('EUID module', function() { it('When an expired token is provided and the API responds in time, the refreshed token is provided to the auction.', async function() { setGdprApplies(true); const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); - configureEuidResponse(200, makeSuccessResponseBody()); + configureEuidResponse(200, makeSuccessResponseBody(refreshedToken)); config.setConfig(makePrebidConfig({euidToken})); apiHelpers.respondAfterDelay(1, server); const bid = await runAuction(); expectToken(bid, refreshedToken); }); + + if (FEATURES.UID2_CSTG) { + it('Should use client side generated EUID token in the auction.', async function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); + configureEuidCstgResponse(200, makeSuccessResponseBody(clientSideGeneratedToken)); + config.setConfig(makePrebidConfig({ euidToken, ...cstgConfigParams, email: 'test@test.com' })); + apiHelpers.respondAfterDelay(1, server); + + const bid = await runAuction(); + expectToken(bid, clientSideGeneratedToken); + }); + } }); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 8e3728704c7..901e0c57e32 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -476,37 +476,33 @@ describe(`UID2 module`, function () { }) describe('When the storedToken is expired and can be refreshed ', function() { - it('it should calls refresh API', function() { - testApiSuccessAndFailure(async function(apiSucceeds) { - const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true); - const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; - coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); - config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); - apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + testApiSuccessAndFailure(async function(apiSucceeds) { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true); + const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); - const bid = await runAuction(); + const bid = await runAuction(); - if (apiSucceeds) expectToken(bid, refreshedToken); - else expectNoIdentity(bid); - }, refreshApiUrl, 'it should use refreshed token in the auction', 'the auction should have no uid2'); - }); + if (apiSucceeds) expectToken(bid, refreshedToken); + else expectNoIdentity(bid); + }, refreshApiUrl, 'it should use refreshed token in the auction', 'the auction should have no uid2'); }) describe('When the storedToken is expired for refresh', function() { - it('it should calls CSTG API and not use the stored token', function() { - testApiSuccessAndFailure(async function(apiSucceeds) { - const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true, true); - const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; - coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); - config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); - apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); + testApiSuccessAndFailure(async function(apiSucceeds) { + const refreshedIdentity = apiHelpers.makeTokenResponse(refreshedToken, true, true, true); + const moduleCookie = {originalIdentity: makeOriginalIdentity('test@test.com'), latestToken: refreshedIdentity}; + coreStorage.setCookie(moduleCookieName, JSON.stringify(moduleCookie), cookieHelpers.getFutureCookieExpiry()); + config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); + apiHelpers.respondAfterDelay(auctionDelayMs / 10, server); - const bid = await runAuction(); + const bid = await runAuction(); - if (apiSucceeds) expectToken(bid, clientSideGeneratedToken); - else expectNoIdentity(bid); - }, cstgApiUrl, 'it should use generated token in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken); - }); + if (apiSucceeds) expectToken(bid, clientSideGeneratedToken); + else expectNoIdentity(bid); + }, cstgApiUrl, 'it should use generated token in the auction', 'the auction should have no uid2', false, clientSideGeneratedToken); }) }) From 34e619221143057f5ed7ae84abd848629db8f87d Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:48:10 +0100 Subject: [PATCH 350/760] add alias support to riseBidAdapter (#10956) --- modules/riseBidAdapter.js | 7 ++++++- test/spec/modules/riseBidAdapter_spec.js | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 44cc91aa87b..78854129f8a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -20,6 +20,7 @@ const BIDDER_CODE = 'rise'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_GVLID = 1043; const DEFAULT_SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb-multi', @@ -32,7 +33,11 @@ const SUPPORTED_SYNC_METHODS = { export const spec = { code: BIDDER_CODE, - gvlid: 1043, + aliases: [ + { code: 'risexchange', gvlid: DEFAULT_GVLID }, + { code: 'openwebxchange', gvlid: 280 } + ], + gvlid: DEFAULT_GVLID, version: ADAPTER_VERSION, supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 4be56c79913..28bd123cb5d 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -22,6 +22,12 @@ describe('riseAdapter', 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, From faec96e5747ed20d192b40b4fb49cf269e6be5f1 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Thu, 18 Jan 2024 19:18:22 +0100 Subject: [PATCH 351/760] Richaudience Bid Adapter): change url tracking (#10963) * RichaudienceBidAdapter add function onTimeout * Add unit test * revert: Revert changes in integrationExamples/creative.html * fix: Remove useless package in richaudiences test module * Change referer with host * Fix(RichaudienceBidAdapter): Change url tracking * deploy * change test --------- Co-authored-by: Sergi Gimenez --- modules/contxtfulRtdProvider.js | 2 +- modules/dsp_genieeBidAdapter.js | 46 +++++++++---------- modules/richaudienceBidAdapter.js | 2 +- package-lock.json | 2 +- .../modules/richaudienceBidAdapter_spec.js | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 69ff3c85079..6d4b2a2ce29 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -121,7 +121,7 @@ function getReceptivity() { * @param { [String] } adUnits * @param {*} _config * @param {*} _userConsent -* @return {{ code: { ReceptivityState: String } }} + * @return {{ code: { ReceptivityState: String } }} */ function getTargetingData(adUnits, _config, _userConsent) { logInfo(MODULE, 'getTargetingData'); diff --git a/modules/dsp_genieeBidAdapter.js b/modules/dsp_genieeBidAdapter.js index 517d6a93177..17865e6793a 100644 --- a/modules/dsp_genieeBidAdapter.js +++ b/modules/dsp_genieeBidAdapter.js @@ -42,21 +42,21 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], /** - * Determines whether or not the given bid request is valid. - * - * @param {BidRequest} - The bid params to validate. - * @return boolean True if this is a valid bid, and false otherwise. - */ + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} - The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ isBidRequestValid: function (_) { return true; }, /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @param {bidderRequest} - the master bidRequest object - * @return ServerRequest Info describing the request to the server. - */ + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @param {bidderRequest} - the master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ buildRequests: function (validBidRequests, bidderRequest) { if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || // gdpr USPConsent(bidderRequest.uspConsent) || // usp @@ -84,12 +84,12 @@ export const spec = { }; }, /** - * Unpack the response from the server into a list of bids. - * - * @param {ServerResponse} serverResponse A successful response from the server. - * @param {BidRequest} bidRequest - the master bidRequest object - * @return {bids} - An array of bids which were nested inside the server. - */ + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest - the master bidRequest object + * @return {bids} - An array of bids which were nested inside the server. + */ interpretResponse: function (serverResponse, bidRequest) { if (!serverResponse.body) { // empty response (no bids) return []; @@ -99,12 +99,12 @@ export const spec = { }, /** - * Register the user sync pixels which should be dropped after the auction. - * - * @param {SyncOptions} syncOptions Which user syncs are allowed? - * @param {ServerResponse[]} serverResponses List of server's responses. - * @return {UserSync[]} The user syncs which should be dropped. - */ + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; // gdpr & usp diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 9668435e2fe..36513aeda47 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -347,7 +347,7 @@ function raiGetTimeoutURL(data) { url = url.replace('[timeout_publisher]', timeout) url = url.replace('[placement_hash]', params[0].pid) if (REFERER != null) { - url = url.replace('[domain]', REFERER) + url = url.replace('[domain]', document.location.host) } return url } diff --git a/package-lock.json b/package-lock.json index 7114a39d509..ff5ad5282d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.31.0-pre", + "version": "8.32.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 52ec6db500e..20c60ca328a 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -919,7 +919,7 @@ describe('Richaudience adapter tests', function () { it('should send timeout', function () { spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); expect(utils.triggerPixel.called).to.equal(true); - expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=http%3A%2F%2Fdomain.com'); + expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=localhost:9876'); }); }); From 953e38798ef0a4bdc0053b21ca7f57bd23850426 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 18 Jan 2024 21:21:48 +0000 Subject: [PATCH 352/760] Prebid 8.32.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff5ad5282d9..2da7efe90e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.32.0-pre", + "version": "8.32.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 82db5e9f7c2..cba237195dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.32.0-pre", + "version": "8.32.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a7e6ce95de5686879a633246c0cfb3073e3f5f25 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 18 Jan 2024 21:21:48 +0000 Subject: [PATCH 353/760] Increment version to 8.33.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2da7efe90e2..bac5bc0c929 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.32.0", + "version": "8.33.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index cba237195dd..70b0bd4a144 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.32.0", + "version": "8.33.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f95f5203d62f8dfa0b7bdeddea7074b95dbec545 Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Thu, 18 Jan 2024 16:30:57 -0500 Subject: [PATCH 354/760] Send experian rtid in the bid request payload (#10961) --- modules/sonobiBidAdapter.js | 7 ++++++- test/spec/modules/sonobiBidAdapter_spec.js | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index a2d1f385623..2c84854e507 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -6,7 +6,7 @@ import { Renderer } from '../src/Renderer.js'; import { userSync } from '../src/userSync.js'; import { bidderSettings } from '../src/bidderSettings.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; -import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); @@ -150,6 +150,11 @@ export const spec = { payload.coppa = 0; } + if (deepAccess(bidderRequest, 'ortb2.experianRtidData') && deepAccess(bidderRequest, 'ortb2.experianRtidKey')) { + payload.expData = deepAccess(bidderRequest, 'ortb2.experianRtidData'); + payload.expKey = deepAccess(bidderRequest, 'ortb2.experianRtidKey'); + } + // If there is no key_maker data, then don't make the request. if (isEmpty(data)) { return null; diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 164aa06d9b7..83db7c0a812 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -1,8 +1,8 @@ -import {expect} from 'chai'; -import {_getPlatform, spec} from 'modules/sonobiBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {userSync} from '../../../src/userSync.js'; -import {config} from 'src/config.js'; +import { expect } from 'chai'; +import { _getPlatform, spec } from 'modules/sonobiBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { userSync } from '../../../src/userSync.js'; +import { config } from 'src/config.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; describe('SonobiBidAdapter', function () { @@ -359,7 +359,9 @@ describe('SonobiBidAdapter', function () { 'page': 'https://example.com', 'stack': ['https://example.com'] }, - uspConsent: 'someCCPAString' + uspConsent: 'someCCPAString', + ortb2: {} + }; it('should set fpd if there is any data in ortb2', function () { @@ -493,6 +495,14 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.hfa).to.equal('hfakey') }) + it('should return a properly formatted request with expData and expKey', function () { + bidderRequests.ortb2.experianRtidData = 'IkhlbGxvLCB3b3JsZC4gSGVsbG8sIHdvcmxkLiBIZWxsbywgd29ybGQuIg=='; + bidderRequests.ortb2.experianRtidKey = 'sovrn-encryption-key-1'; + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.data.expData).to.equal('IkhlbGxvLCB3b3JsZC4gSGVsbG8sIHdvcmxkLiBIZWxsbywgd29ybGQuIg=='); + expect(bidRequests.data.expKey).to.equal('sovrn-encryption-key-1'); + }) + it('should return null if there is nothing to bid on', function () { const bidRequests = spec.buildRequests([{ params: {} }], bidderRequests) expect(bidRequests).to.equal(null); From bc7aa938b18371d888e33982f29a2a36299f868c Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Mon, 22 Jan 2024 15:07:42 +0200 Subject: [PATCH 355/760] Tagoras Bid Adapter : Initial release (#10826) * MinuteMediaPlus Bid Adapter: pass gpp consent to userSync server. * tagorasBidAdapter submission * update maintainer * Remove unneeded GVLID. * Adjust syncs condition logic in tagorasBidAdapter. --- modules/tagorasBidAdapter.js | 342 ++++++++++ modules/tagorasBidAdapter.md | 35 ++ test/spec/modules/tagorasBidAdapter_spec.js | 651 ++++++++++++++++++++ 3 files changed, 1028 insertions(+) create mode 100644 modules/tagorasBidAdapter.js create mode 100644 modules/tagorasBidAdapter.md create mode 100644 test/spec/modules/tagorasBidAdapter_spec.js diff --git a/modules/tagorasBidAdapter.js b/modules/tagorasBidAdapter.js new file mode 100644 index 00000000000..0138ba3daf9 --- /dev/null +++ b/modules/tagorasBidAdapter.js @@ -0,0 +1,342 @@ +import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; + +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'tagoras'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.tagoras.io`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + let {bidFloor, ext} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const cId = extractCID(params); + const pId = extractPID(params); + const subDomain = extractSubDomain(params); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + gpid: gpid, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + + _each(ext, (value, key) => { + dto.data['ext.' + key] = value; + }); + + return dto; +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + const requests = []; + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + const {bidId} = request.data; + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach(result => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + metaData, + advertiserDomains, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: bidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + const {gppString, applicableSections} = gppConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` + + if (gppString && applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppString); + params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); + } + + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.tagoras.io/api/sync/iframe/${params}` + }); + } else if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.tagoras.io/api/sync/image/${params}` + }); + } + return syncs; +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/tagorasBidAdapter.md b/modules/tagorasBidAdapter.md new file mode 100644 index 00000000000..83290bff525 --- /dev/null +++ b/modules/tagorasBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +**Module Name:** Tagoras Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** prebid@tagoras.io + +# Description + +Module that connects to Tagoras's demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'tagoras', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/tagorasBidAdapter_spec.js b/test/spec/modules/tagorasBidAdapter_spec.js new file mode 100644 index 00000000000..7559567dcff --- /dev/null +++ b/test/spec/modules/tagorasBidAdapter_spec.js @@ -0,0 +1,651 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, +} from 'modules/tagorasBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; + +export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; + +const SUB_DOMAIN = 'exchange'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + 'transactionId': '56e184c6-bde9-497b-b9b9-cf47a61381ee', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + } + } +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['tagoras.io'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +}; + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('TagorasBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + tagoras: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + bidderRequestId: '12a8ae9ada9c13', + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + prebidVersion: version, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + uqs: getTopWindowQueryParams(), + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + }, + gpid: '' + } + }); + }); + + it('should build banner request for each size', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + gppString: 'gpp_string', + gppSid: [7], + usPrivacy: 'consent_string', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + gpid: '0123456789', + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + } + }); + }); + + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + sandbox.restore(); + }); + }); + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.tagoras.io/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }); + + it('should generate url with consent data', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'consent_string' + }; + const uspConsent = 'usp_string'; + const gppConsent = { + gppString: 'gpp_string', + applicableSections: [7] + } + + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.tagoras.io/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&gpp=gpp_string&gpp_sid=7', + 'type': 'image' + }]); + }); + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['tagoras.io'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['tagoras.io'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['tagoras.io'] + } + }); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + tagoras: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + tagoras: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); +}); From d93eb4c8ee0e7b079619b6ac45b22329ccc836fd Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Mon, 22 Jan 2024 23:54:41 +0530 Subject: [PATCH 356/760] Insticator Bid Adapter: support optional video params (#10969) * add support for video params acc to ortb2.5 * update the optionalParams logic for buildVideo * update check for protocols * udpate validation for video params and added test case --- modules/insticatorBidAdapter.js | 60 ++++++++++++++-- .../spec/modules/insticatorBidAdapter_spec.js | 70 +++++++++++++++++++ 2 files changed, 124 insertions(+), 6 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 193337d1503..32ec5b482c4 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -12,6 +12,35 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; +const isSubarray = (arr, target) => { + if (!isArrayOfNums(arr) || arr.length === 0) { + return false; + } + const targetSet = new Set(target); + return arr.every(el => targetSet.has(el)); +}; + +export const OPTIONAL_VIDEO_PARAMS = { + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), + 'protocols': (value) => isSubarray(value, [2, 3, 5, 6, 7, 8]), // protocols values supported by Inticator, according to the OpenRTB spec + 'startdelay': (value) => isInteger(value), + 'linearity': (value) => isInteger(value) && [1].includes(value), + 'skip': (value) => isInteger(value) && [1, 0].includes(value), + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), + 'playbackmethod': (value) => isSubarray(value, [1, 2, 3, 4]), + 'playbackend': (value) => isInteger(value) && [1, 2, 3].includes(value), + 'delivery': (value) => isSubarray(value, [1, 2, 3]), + 'pos': (value) => isInteger(value) && [0, 1, 2, 3, 4, 5, 6, 7].includes(value), + 'api': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7]), +}; + export const storage = getStorageManager({bidderCode: BIDDER_CODE}); config.setDefaults({ @@ -90,18 +119,24 @@ function buildVideo(bidRequest) { } } + const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video'); + let optionalParams = {}; + for (const param in OPTIONAL_VIDEO_PARAMS) { + if (bidRequestVideo[param]) { + optionalParams[param] = bidRequestVideo[param]; + } + } + let videoObj = { placement, mimes, w, h, + ...optionalParams } if (plcmt) { - videoObj = { - ...videoObj, - plcmt - } + videoObj['plcmt'] = plcmt; } return videoObj } @@ -405,6 +440,20 @@ function validateVideo(bid) { return false; } + for (const param in OPTIONAL_VIDEO_PARAMS) { + if (video[param]) { + if (!OPTIONAL_VIDEO_PARAMS[param](video[param])) { + logError(`insticator: video ${param} is invalid or not supported by insticator`); + return false + } + } + } + + if (video.minduration && video.maxduration && video.minduration > video.maxduration) { + logError('insticator: video minduration is greater than maxduration'); + return false; + } + return true; } @@ -446,7 +495,6 @@ export const spec = { interpretResponse: function (serverResponse, request) { const bidderRequest = request.bidderRequest; const body = serverResponse.body; - if (!body || body.id !== bidderRequest.bidderRequestId) { logError('insticator: response id does not match bidderRequestId'); return []; diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 36b6dd8fbf2..f9aa3b913c6 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -216,6 +216,76 @@ describe('InsticatorBidAdapter', function () { } })).to.be.true; }); + + it('should return true if optional video fields are valid', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [250, 300], + placement: 1, + startdelay: 1, + skip: 1, + skipmin: 1, + skipafter: 1, + minduration: 1, + maxduration: 1, + api: [1, 2], + protocols: [2], + battr: [1, 2], + playbackmethod: [1, 2], + playbackend: 1, + delivery: [1, 2], + pos: 1, + }, + } + } + })).to.be.true; + }); + + it('should return false if optional video fields are not valid', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [250, 300], + placement: 1, + startdelay: 'NaN', + }, + } + } + })).to.be.false; + }); + + it('should return false if video min duration > max duration', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [250, 300], + placement: 1, + minduration: 5, + maxduration: 4, + }, + } + } + })).to.be.false; + }); }); describe('buildRequests', function () { From 4092a053f52655523fc7d8e8e4197a5a57dee883 Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Mon, 22 Jan 2024 19:35:02 +0100 Subject: [PATCH 357/760] pass document instance to custom renderer (#10959) --- libraries/creativeRender/direct.js | 2 +- src/adRendering.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/creativeRender/direct.js b/libraries/creativeRender/direct.js index 0b4fb2f0a75..19d34e16844 100644 --- a/libraries/creativeRender/direct.js +++ b/libraries/creativeRender/direct.js @@ -52,7 +52,7 @@ export function renderAdDirect(doc, adId, options) { message: `renderAd was prevented from writing to the main document.` }) } else { - handleRender(renderFn, {adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid}); + handleRender(renderFn, {adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); } } } catch (e) { diff --git a/src/adRendering.js b/src/adRendering.js index ac2639c4052..f8fe0044f9b 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -43,7 +43,7 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { events.emit(AD_RENDER_SUCCEEDED, data); } -export function handleRender(renderFn, {adId, options, bidResponse}) { +export function handleRender(renderFn, {adId, options, bidResponse, doc}) { if (bidResponse == null) { emitAdRenderFail({ reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, @@ -63,7 +63,7 @@ export function handleRender(renderFn, {adId, options, bidResponse}) { const {adId, ad, adUrl, width, height, renderer, cpm, originalCpm, mediaType} = bidResponse; // rendering for outstream safeframe if (isRendererRequired(renderer)) { - executeRenderer(renderer, bidResponse); + executeRenderer(renderer, bidResponse, doc); } else if (adId) { if (mediaType === VIDEO) { emitAdRenderFail({ From e589c4b6b86f7b854c6644c91640d2d9de2bc497 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:49:45 +0100 Subject: [PATCH 358/760] Oxxion Rtd Module: tracking time to run (#10955) * Oxxion Rtd Module: tracking time to run * Oxxion Rtd Module: tracking time to run * Oxxion Rtd Module: tracking time to run --- modules/oxxionRtdProvider.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/oxxionRtdProvider.js b/modules/oxxionRtdProvider.js index c979a10d00c..f968fc10e4b 100644 --- a/modules/oxxionRtdProvider.js +++ b/modules/oxxionRtdProvider.js @@ -23,6 +23,7 @@ function init(config, userConsent) { } function getAdUnits(reqBidsConfigObj, callback, config, userConsent) { + const moduleStarted = new Date(); logInfo(LOG_PREFIX + 'started with ', config); if (typeof config.params.threshold != 'undefined' && typeof config.params.samplingRate == 'number') { let filteredBids; @@ -47,6 +48,11 @@ function getAdUnits(reqBidsConfigObj, callback, config, userConsent) { }); } if (typeof callback == 'function') { callback(); } + const timeToRun = new Date() - moduleStarted; + logInfo(LOG_PREFIX + ' time to run: ' + timeToRun); + if (getRandomNumber(50) == 1) { + ajax('https://' + config.params.domain + '.oxxion.io/ova/time', null, JSON.stringify({'duration': timeToRun, 'auctionId': reqBidsConfigObj.auctionId}), {method: 'POST', withCredentials: true}); + } }).catch(error => logError(LOG_PREFIX, 'bidInterestError', error)); } } From 01ef25ee7328a04a09872ae3c2a7db6042d8b8d8 Mon Sep 17 00:00:00 2001 From: IQzoneIT Date: Mon, 22 Jan 2024 22:51:53 +0200 Subject: [PATCH 359/760] IQX Bid Adapter : initial release (#10952) * new adapter - IQX * chang maintainer --------- Co-authored-by: Chucky-choo --- modules/iqxBidAdapter.js | 207 +++++++++++ modules/iqxBidAdapter.md | 54 +++ test/spec/modules/iqxBidAdapter_spec.js | 455 ++++++++++++++++++++++++ 3 files changed, 716 insertions(+) create mode 100644 modules/iqxBidAdapter.js create mode 100644 modules/iqxBidAdapter.md create mode 100644 test/spec/modules/iqxBidAdapter_spec.js diff --git a/modules/iqxBidAdapter.js b/modules/iqxBidAdapter.js new file mode 100644 index 00000000000..1bef158c4a2 --- /dev/null +++ b/modules/iqxBidAdapter.js @@ -0,0 +1,207 @@ +import {config} from '../src/config.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; +import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; + +const CUR = 'USD'; +const BIDDER_CODE = 'iqx'; +const ENDPOINT = 'https://pbjs.iqzonertb.live'; + +/** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(req) { + if (req && typeof req.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { + logError('Env or pid is not present in bidder params'); + return false; + } + + if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +/** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(validBidRequests, bidderRequest) { + const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; + const requests = validBidRequests.map(req => { + const request = {}; + request.bidId = req.bidId; + request.banner = deepAccess(req, 'mediaTypes.banner'); + request.auctionId = req.ortb2?.source?.tid; + request.transactionId = req.ortb2Imp?.ext?.tid; + request.sizes = parseSizesInput(getAdUnitSizes(req)); + request.schain = req.schain; + request.location = { + page: refererInfo.page, + location: refererInfo.location, + domain: refererInfo.domain, + whost: window.location.host, + ref: refererInfo.ref, + isAmp: refererInfo.isAmp + }; + request.device = { + ua: navigator.userAgent, + lang: navigator.language + }; + request.env = { + env: req.params.env, + pid: req.params.pid + }; + request.ortb2 = req.ortb2; + request.ortb2Imp = req.ortb2Imp; + request.tz = new Date().getTimezoneOffset(); + request.ext = req.params.ext; + request.bc = req.bidRequestsCount; + request.floor = getBidFloor(req); + + if (req.userIdAsEids && req.userIdAsEids.length !== 0) { + request.userEids = req.userIdAsEids; + } else { + request.userEids = []; + } + if (gdprConsent.gdprApplies) { + request.gdprApplies = Number(gdprConsent.gdprApplies); + request.consentString = gdprConsent.consentString; + } else { + request.gdprApplies = 0; + request.consentString = ''; + } + if (uspConsent) { + request.usPrivacy = uspConsent; + } else { + request.usPrivacy = ''; + } + if (config.getConfig('coppa')) { + request.coppa = 1; + } else { + request.coppa = 0; + } + + const video = deepAccess(req, 'mediaTypes.video'); + if (video) { + request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); + request.video = video; + } + + return request; + }); + + return { + method: 'POST', + url: ENDPOINT + '/bid', + data: JSON.stringify(requests), + withCredentials: true, + bidderRequest, + options: { + contentType: 'application/json', + } + }; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ +function interpretResponse(serverResponse, {bidderRequest}) { + const response = []; + if (!isArray(deepAccess(serverResponse, 'body.data'))) { + return response; + } + + serverResponse.body.data.forEach(serverBid => { + const bid = { + requestId: bidderRequest.bidId, + dealId: bidderRequest.dealId || null, + ...serverBid + }; + response.push(bid); + }); + + return response; +} + +/** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} serverResponses List of server's responses. + * @return {UserSync[]} The user syncs which should be dropped. + */ +function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); + + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + + pixels.forEach(pixel => { + const [type, url] = pixel; + const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; + if (type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push(sync) + } else if (type === 'image' && syncOptions.pixelEnabled) { + syncs.push(sync) + } + }); + } + + return syncs; +} + +/** + * Get valid floor value from getFloor fuction. + * + * @param {Object} bid Current bid request. + * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. + */ +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency: CUR, + mediaType: '*', + size: '*' + }); + + if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { + return floor.floor; + } + + return null; +} + +export const spec = { + code: BIDDER_CODE, + aliases: ['iqx'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/iqxBidAdapter.md b/modules/iqxBidAdapter.md new file mode 100644 index 00000000000..c48864c4306 --- /dev/null +++ b/modules/iqxBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: IQX Bidder Adapter +Module Type: IQX Bidder Adapter +Maintainer: it@iqzone.com +``` + +# Description + +Module that connects to iqx.com demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'iqx', + params: { + env: 'iqx', + pid: '40', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'iqx', + params: { + env: 'iqx', + pid: '40', + ext: {} + } + }] + } +]; +``` diff --git a/test/spec/modules/iqxBidAdapter_spec.js b/test/spec/modules/iqxBidAdapter_spec.js new file mode 100644 index 00000000000..f5e680c8e0b --- /dev/null +++ b/test/spec/modules/iqxBidAdapter_spec.js @@ -0,0 +1,455 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec, getBidFloor} from 'modules/iqxBidAdapter.js'; +import {deepClone} from 'src/utils'; + +const ENDPOINT = 'https://pbjs.iqzonertb.live'; + +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'iqx', + params: { + env: 'iqx', + pid: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; +describe('iqxBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required env param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.env; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('coppa').and.to.equal(0); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + env: 'iqx', + pid: '40' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with coppa 1', function () { + config.setConfig({ + coppa: true + }); + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('coppa').and.equals(1); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['iqx'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['iqx']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: defaultRequestVideo}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + it('should return null when getFloor is not a function', () => { + const bid = {getFloor: 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when getFloor doesnt return an object', () => { + const bid = {getFloor: () => 2}; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when floor is not a number', () => { + const bid = { + getFloor: () => ({floor: 'string', currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}) From a8d36a6b0114f72bad81ca5b6821cf65ab42f71f Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:52:56 +0900 Subject: [PATCH 360/760] AJA Bid Adapter: add Global Placement ID support, remove native/video ad support (#10945) * add gpid support, remove native/video ad support * also added cdep support --- modules/ajaBidAdapter.js | 157 +++++++----------------- modules/ajaBidAdapter.md | 60 +-------- test/spec/modules/ajaBidAdapter_spec.js | 145 ++-------------------- 3 files changed, 55 insertions(+), 307 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 9049197e565..9e2d4efb5ff 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -1,7 +1,6 @@ -import {createTrackPixelHtml, logError, logWarn, deepAccess, getBidIdParameter} from '../src/utils.js'; -import { Renderer } from '../src/Renderer.js'; +import {createTrackPixelHtml, logError, getBidIdParameter} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER, NATIVE } from '../src/mediaTypes.js'; +import { BANNER } from '../src/mediaTypes.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; const BidderCode = 'aja'; @@ -25,7 +24,7 @@ const BannerSizeMap = { export const spec = { code: BidderCode, - supportedMediaTypes: [VIDEO, BANNER, NATIVE], + supportedMediaTypes: [BANNER], /** * Determines whether or not the given bid has all the params needed to make a valid request. @@ -51,12 +50,20 @@ export const spec = { for (let i = 0, len = validBidRequests.length; i < len; i++) { const bidRequest = validBidRequests[i]; + if ( + (bidRequest.mediaTypes?.native || bidRequest.mediaTypes?.video) && + bidRequest.mediaTypes?.banner) { + continue + } + let queryString = ''; const asi = getBidIdParameter('asi', bidRequest.params); queryString = tryAppendQueryString(queryString, 'asi', asi); queryString = tryAppendQueryString(queryString, 'skt', SDKType); + queryString = tryAppendQueryString(queryString, 'gpid', bidRequest.ortb2Imp?.ext?.gpid) queryString = tryAppendQueryString(queryString, 'tid', bidRequest.ortb2Imp?.ext?.tid) + queryString = tryAppendQueryString(queryString, 'cdep', bidRequest.ortb2?.device?.ext?.cdep) queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); @@ -64,19 +71,8 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); } - const banner = deepAccess(bidRequest, `mediaTypes.${BANNER}`) - if (banner) { - const adFormatIDs = []; - for (const size of banner.sizes || []) { - if (size.length !== 2) { - continue - } - - const adFormatID = BannerSizeMap[`${size[0]}x${size[1]}`]; - if (adFormatID) { - adFormatIDs.push(adFormatID); - } - } + const adFormatIDs = pickAdFormats(bidRequest) + if (adFormatIDs && adFormatIDs.length > 0) { queryString = tryAppendQueryString(queryString, 'ad_format_ids', adFormatIDs.join(',')); } @@ -87,7 +83,7 @@ export const spec = { })); } - const sua = deepAccess(bidRequest, 'ortb2.device.sua'); + const sua = bidRequest.ortb2?.device?.sua if (sua) { queryString = tryAppendQueryString(queryString, 'sua', JSON.stringify(sua)); } @@ -110,9 +106,17 @@ export const spec = { } const ad = bidderResponseBody.ad; + if (AdType.Banner !== ad.ad_type) { + return [] + } + const bannerAd = bidderResponseBody.ad.banner; const bid = { requestId: ad.prebid_id, + mediaType: BANNER, + ad: bannerAd.tag, + width: bannerAd.w, + height: bannerAd.h, cpm: ad.price, creativeId: ad.creative_id, dealId: ad.deal_id, @@ -120,80 +124,16 @@ export const spec = { netRevenue: true, ttl: 300, // 5 minutes meta: { - advertiserDomains: [] + advertiserDomains: bannerAd.adomain, }, } - - if (AdType.Video === ad.ad_type) { - const videoAd = bidderResponseBody.ad.video; - Object.assign(bid, { - vastXml: videoAd.vtag, - width: videoAd.w, - height: videoAd.h, - renderer: newRenderer(bidderResponseBody), - adResponse: bidderResponseBody, - mediaType: VIDEO - }); - - Array.prototype.push.apply(bid.meta.advertiserDomains, videoAd.adomain) - } else if (AdType.Banner === ad.ad_type) { - const bannerAd = bidderResponseBody.ad.banner; - Object.assign(bid, { - width: bannerAd.w, - height: bannerAd.h, - ad: bannerAd.tag, - mediaType: BANNER + try { + bannerAd.imps.forEach(impTracker => { + const tracker = createTrackPixelHtml(impTracker); + bid.ad += tracker; }); - try { - bannerAd.imps.forEach(impTracker => { - const tracker = createTrackPixelHtml(impTracker); - bid.ad += tracker; - }); - } catch (error) { - logError('Error appending tracking pixel', error); - } - - Array.prototype.push.apply(bid.meta.advertiserDomains, bannerAd.adomain) - } else if (AdType.Native === ad.ad_type) { - const nativeAds = ad.native.template_and_ads.ads; - if (nativeAds.length === 0) { - return []; - } - - const nativeAd = nativeAds[0]; - const assets = nativeAd.assets; - - Object.assign(bid, { - mediaType: NATIVE - }); - - bid.native = { - title: assets.title, - body: assets.description, - cta: assets.cta_text, - sponsoredBy: assets.sponsor, - clickUrl: assets.lp_link, - impressionTrackers: nativeAd.imps, - privacyLink: assets.adchoice_url - }; - - if (assets.img_main !== undefined) { - bid.native.image = { - url: assets.img_main, - width: parseInt(assets.img_main_width, 10), - height: parseInt(assets.img_main_height, 10) - }; - } - - if (assets.img_icon !== undefined) { - bid.native.icon = { - url: assets.img_icon, - width: parseInt(assets.img_icon_width, 10), - height: parseInt(assets.img_icon_height, 10) - }; - } - - Array.prototype.push.apply(bid.meta.advertiserDomains, nativeAd.adomain) + } catch (error) { + logError('Error appending tracking pixel', error); } return [bid]; @@ -229,34 +169,23 @@ export const spec = { }, } -function newRenderer(bidderResponse) { - const renderer = Renderer.install({ - id: bidderResponse.ad.prebid_id, - url: bidderResponse.ad.video.purl, - loaded: false, - }); +function pickAdFormats(bidRequest) { + let sizes = bidRequest.sizes || [] + sizes.push(...(bidRequest.mediaTypes?.banner?.sizes || [])) - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on newRenderer', err); - } + const adFormatIDs = []; + for (const size of sizes) { + if (size.length !== 2) { + continue + } - return renderer; -} + const adFormatID = BannerSizeMap[`${size[0]}x${size[1]}`]; + if (adFormatID) { + adFormatIDs.push(adFormatID); + } + } -function outstreamRender(bid) { - bid.renderer.push(() => { - window['aja_vast_player'].init({ - vast_tag: bid.adResponse.ad.video.vtag, - ad_unit_code: bid.adUnitCode, // target div id to render video - width: bid.width, - height: bid.height, - progress: bid.adResponse.ad.video.progress, - loop: bid.adResponse.ad.video.loop, - inread: bid.adResponse.ad.video.inread - }); - }); + return [...new Set(adFormatIDs)] } registerBidder(spec); diff --git a/modules/ajaBidAdapter.md b/modules/ajaBidAdapter.md index 66155875f4d..92ffecaeb9f 100644 --- a/modules/ajaBidAdapter.md +++ b/modules/ajaBidAdapter.md @@ -8,7 +8,7 @@ Maintainer: ssp_support@aja-kk.co.jp # Description Connects to Aja exchange for bids. -Aja bid adapter supports Banner and Outstream Video. +Aja bid adapter supports Banner. # Test Parameters ```js @@ -29,64 +29,6 @@ var adUnits = [ asi: 'tk82gbLmg' } }] - }, - // Video outstream adUnit - { - code: 'prebid_video', - mediaTypes: { - video: { - context: 'outstream', - playerSize: [300, 250] - } - }, - bids: [{ - bidder: 'aja', - params: { - asi: '1-KwEG_iR' - } - }] - }, - // Native adUnit - { - code: 'prebid_native', - mediaTypes: { - native: { - image: { - required: true, - sendId: false - }, - title: { - required: true, - sendId: true - }, - sponsoredBy: { - required: false, - sendId: true - }, - clickUrl: { - required: false, - sendId: true - }, - body: { - required: false, - sendId: true - }, - icon: { - required: false, - sendId: false - }, - privacyLink: { - required: true, - sendId: true - }, - } - }, - bids: [{ - bidder: 'aja', - params: { - asi: 'qxueUGliR' - } - }] } ]; ``` diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 7cf5698f7d4..3137c9dc24e 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -62,8 +62,17 @@ describe('AjaAdapter', function () { model: 'SM-G955U', bitness: '64', architecture: '' + }, + ext: { + cdep: 'example_label_1' } } + }, + ortb2Imp: { + ext: { + tid: 'cea1eb09-d970-48dc-8585-634d3a7b0544', + gpid: '/1111/homepage#300x250' + } } } ]; @@ -78,7 +87,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); + expect(requests[0].data).to.equal('asi=123456&skt=5&gpid=%2F1111%2Fhomepage%23300x250&tid=cea1eb09-d970-48dc-8585-634d3a7b0544&cdep=example_label_1&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&ad_format_ids=2&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); }); }); @@ -116,7 +125,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&eids=%7B%22eids%22%3A%5B%7B%22source%22%3A%22pubcid.org%22%2C%22uids%22%3A%5B%7B%22id%22%3A%22some-random-id-value%22%2C%22atype%22%3A1%7D%5D%7D%5D%7D&'); + expect(requests[0].data).to.equal('asi=123456&skt=5&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&ad_format_ids=2&eids=%7B%22eids%22%3A%5B%7B%22source%22%3A%22pubcid.org%22%2C%22uids%22%3A%5B%7B%22id%22%3A%22some-random-id-value%22%2C%22atype%22%3A1%7D%5D%7D%5D%7D&'); }); }); @@ -173,138 +182,6 @@ describe('AjaAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); - it('handles video responses', function () { - let response = { - 'is_ad_return': true, - 'ad': { - 'ad_type': 3, - 'prebid_id': '51ef8751f9aead', - 'price': 12.34, - 'currency': 'JPY', - 'creative_id': '123abc', - 'video': { - 'w': 300, - 'h': 250, - 'vtag': '', - 'purl': 'https://cdn/player', - 'progress': true, - 'loop': false, - 'inread': false, - 'adomain': [ - 'www.example.com' - ] - } - }, - 'syncs': [ - 'https://example.com' - ] - }; - - let bidderRequest; - let result = spec.interpretResponse({ body: response }, {bidderRequest}); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('renderer'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - - it('handles native response', function () { - let response = { - 'is_ad_return': true, - 'ad': { - 'ad_type': 2, - 'prebid_id': '51ef8751f9aead', - 'price': 12.34, - 'currency': 'JPY', - 'creative_id': '123abc', - 'native': { - 'template_and_ads': { - 'head': '', - 'body_wrapper': '', - 'body': '', - 'ads': [ - { - 'ad_format_id': 10, - 'assets': { - 'ad_spot_id': '123abc', - 'index': 0, - 'adchoice_url': 'https://aja-kk.co.jp/optout', - 'cta_text': 'cta', - 'img_icon': 'https://example.com/img_icon', - 'img_icon_width': '50', - 'img_icon_height': '50', - 'img_main': 'https://example.com/img_main', - 'img_main_width': '200', - 'img_main_height': '100', - 'lp_link': 'https://example.com/lp?k=v', - 'sponsor': 'sponsor', - 'title': 'ad_title', - 'description': 'ad_desc' - }, - 'imps': [ - 'https://example.com/imp' - ], - 'inviews': [ - 'https://example.com/inview' - ], - 'jstracker': '', - 'disable_trimming': false, - 'adomain': [ - 'www.example.com' - ] - } - ] - } - } - }, - 'syncs': [ - 'https://example.com' - ] - }; - - let expectedResponse = [ - { - 'requestId': '51ef8751f9aead', - 'cpm': 12.34, - 'creativeId': '123abc', - 'dealId': undefined, - 'mediaType': 'native', - 'currency': 'JPY', - 'ttl': 300, - 'netRevenue': true, - 'native': { - 'title': 'ad_title', - 'body': 'ad_desc', - 'cta': 'cta', - 'sponsoredBy': 'sponsor', - 'image': { - 'url': 'https://example.com/img_main', - 'width': 200, - 'height': 100 - }, - 'icon': { - 'url': 'https://example.com/img_icon', - 'width': 50, - 'height': 50 - }, - 'clickUrl': 'https://example.com/lp?k=v', - 'impressionTrackers': [ - 'https://example.com/imp' - ], - 'privacyLink': 'https://aja-kk.co.jp/optout' - }, - 'meta': { - 'advertiserDomains': [ - 'www.example.com' - ] - } - } - ]; - - let bidderRequest; - let result = spec.interpretResponse({ body: response }, {bidderRequest}) - expect(result).to.deep.equal(expectedResponse) - }); - it('handles nobid responses', function () { let response = { 'is_ad_return': false, From 48d8766a40be2e8183310fc06fb7ce63d5622590 Mon Sep 17 00:00:00 2001 From: AdFusionPrebid <144114711+AdFusionPrebid@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:11:29 +0100 Subject: [PATCH 361/760] AdFusion Bid Adapter : currency support (#10938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adfusion bid adapter test * Add adapter and docs * add currency support * kick of integration tests --------- Co-authored-by: Łukasz Co-authored-by: Chris Huie --- modules/adfusionBidAdapter.js | 30 ++++++++++++++++ test/spec/modules/adfusionBidAdapter_spec.js | 37 ++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/modules/adfusionBidAdapter.js b/modules/adfusionBidAdapter.js index b3638159c2a..a206ee5e899 100644 --- a/modules/adfusionBidAdapter.js +++ b/modules/adfusionBidAdapter.js @@ -5,6 +5,7 @@ import * as utils from '../src/utils.js'; const adpterVersion = '1.0'; export const REQUEST_URL = 'https://spicyrtb.com/auction/prebid'; +export const DEFAULT_CURRENCY = 'USD'; export const spec = { code: 'adfusion', @@ -23,6 +24,17 @@ const converter = ortbConverter({ context: { netRevenue: true, ttl: 300, + currency: DEFAULT_CURRENCY, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = getBidFloor(bidRequest); + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } + + return imp; }, request(buildRequest, imps, bidderRequest, context) { const req = buildRequest(imps, bidderRequest, context); @@ -88,3 +100,21 @@ function isBannerBid(bid) { function interpretResponse(resp, req) { return converter.fromORTB({ request: req.data, response: resp.body }); } + +function getBidFloor(bid) { + if (utils.isFn(bid.getFloor)) { + let floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*', + }); + if ( + utils.isPlainObject(floor) && + !isNaN(floor.floor) && + floor.currency === DEFAULT_CURRENCY + ) { + return floor.floor; + } + } + return null; +} diff --git a/test/spec/modules/adfusionBidAdapter_spec.js b/test/spec/modules/adfusionBidAdapter_spec.js index 638831c33f3..82705b727b4 100644 --- a/test/spec/modules/adfusionBidAdapter_spec.js +++ b/test/spec/modules/adfusionBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/adfusionBidAdapter'; import 'modules/priceFloors.js'; +import 'modules/currency.js'; import { newBidder } from 'src/adapters/bidderFactory'; describe('adfusionBidAdapter', function () { @@ -24,7 +25,7 @@ describe('adfusionBidAdapter', function () { transactionId: 'test-transactionId-1', }; - it('should return true when required params found', function () { + it('should return true when required params are found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); @@ -36,7 +37,7 @@ describe('adfusionBidAdapter', function () { }); describe('buildRequests', function () { - let bidRequests, bidderRequest; + let bidRequests, bannerBidRequest, bidderRequest; beforeEach(function () { bidRequests = [ { @@ -75,6 +76,25 @@ describe('adfusionBidAdapter', function () { transactionId: 'test-transactionId-2', }, ]; + bannerBidRequest = { + bidder: 'adfusion', + params: { + accountId: 1234, + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + }; bidderRequest = { refererInfo: {} }; }); @@ -89,9 +109,22 @@ describe('adfusionBidAdapter', function () { expect(request).to.be.an('array'); expect(request[0].data).to.be.an('object'); expect(request[0].method).to.equal('POST'); + expect(request[0].currency).to.not.equal('USD'); expect(request[0].url).to.not.equal(''); expect(request[0].url).to.not.equal(undefined); expect(request[0].url).to.not.equal(null); }); + + it('should add bid floor', function () { + let bidRequest = Object.assign({}, bannerBidRequest); + let payload = spec.buildRequests([bidRequest], bidderRequest)[0].data; + expect(payload.imp[0].bidfloorcur).to.not.exist; + + let getFloorResponse = { currency: 'USD', floor: 3 }; + bidRequest.getFloor = () => getFloorResponse; + payload = spec.buildRequests([bidRequest], bidderRequest)[0].data; + expect(payload.imp[0].bidfloor).to.equal(3); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); + }); }); }); From 65fd60588e98c96fd15e537f893f5122420ec30a Mon Sep 17 00:00:00 2001 From: ramyferjaniadot <90328697+ramyferjaniadot@users.noreply.github.com> Date: Tue, 23 Jan 2024 16:06:23 +0100 Subject: [PATCH 362/760] [TECH-6244] fix: add gvlid to adot adapter (#11) (#10975) --- modules/adotBidAdapter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index c34af4d3d17..b48a7ec43b0 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -9,6 +9,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'adot'; const ADAPTER_VERSION = 'v2.0.0'; +const GVLID = 272; const BID_METHOD = 'POST'; const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidrequest'; const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols']; @@ -635,7 +636,8 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - getFloor + getFloor, + gvlid: GVLID }; registerBidder(spec); From 5a73e16f6696b98c731c079bc3138c9e881ee129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Tue, 23 Jan 2024 19:56:03 +0100 Subject: [PATCH 363/760] Agma Analytics Module : fix getting global config data (#10968) * Fix getting global config data * Increase internal version number --- modules/agmaAnalyticsAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js index afbc3e771ec..e43dee063c5 100644 --- a/modules/agmaAnalyticsAdapter.js +++ b/modules/agmaAnalyticsAdapter.js @@ -17,7 +17,7 @@ import { config } from '../src/config.js'; const GVLID = 1122; const ModuleCode = 'agma'; const analyticsType = 'endpoint'; -const scriptVersion = '1.7.0'; +const scriptVersion = '1.7.1'; const batchDelayInMs = 1000; const agmaURL = 'https://pbc.agma-analytics.de/v1'; const pageViewId = generateUUID(); @@ -61,7 +61,7 @@ export const getOrtb2Data = (options) => { } } try { - const configData = config.getConfig('agma'); + const configData = config.getConfig(); // try to fallback to global config if (configData.ortb2) { site = site || configData.ortb2.site; From c6a9ebce5dc7db3599ca39c11b511b3916591e64 Mon Sep 17 00:00:00 2001 From: Marco Cosentino <807030+cosenmarco@users.noreply.github.com> Date: Tue, 23 Jan 2024 23:37:44 +0100 Subject: [PATCH 364/760] ID5 ID Module : ID5 will be able to optionally delegate its logic to an external module (#10742) * id-7317 Adding ability to load exernal module by param configuration * id-7317 Fixing bugs with id5 external module * id-7313 Addinf documentation to new externalModuleUrl parameter * id-7317 Typo * id-7317 Fix Lint error * id-7317 Some improvements from PR * id-7317 Some test iprovements * id-7317 Using loadExternalScript() utility instead of loading the script directly * id-7317 Lint error * id-7317 Fixing nb increments * ID5 User Id module - pass gpp consent data to external module --------- Co-authored-by: abazylewicz Co-authored-by: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> --- modules/id5IdSystem.js | 242 ++++-- modules/id5IdSystem.md | 2 + src/adloader.js | 3 +- test/spec/modules/id5IdSystem_spec.js | 1033 +++++++++++++------------ 4 files changed, 713 insertions(+), 567 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 5ed12d8def2..96ae5386de6 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -15,12 +15,14 @@ import { logWarn, safeJSONParse } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; +import {fetch} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; 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'; const MODULE_NAME = 'id5Id'; const GVLID = 131; @@ -37,6 +39,70 @@ const LEGACY_COOKIE_NAMES = ['pbjs-id5id', 'id5id.1st', 'id5id']; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); +/** + * @typedef {Object} IdResponse + * @property {string} [universal_uid] - The encrypted ID5 ID to pass to bidders + * @property {Object} [ext] - The extensions object to pass to bidders + * @property {Object} [ab_testing] - A/B testing configuration +*/ + +/** + * @typedef {Object} FetchCallConfig + * @property {string} [url] - The URL for the fetch endpoint + * @property {Object} [overrides] - Overrides to apply to fetch parameters +*/ + +/** + * @typedef {Object} ExtensionsCallConfig + * @property {string} [url] - The URL for the extensions endpoint + * @property {string} [method] - Overrides the HTTP method to use to make the call + * @property {Object} [body] - Specifies a body to pass to the extensions endpoint +*/ + +/** + * @typedef {Object} DynamicConfig + * @property {FetchCallConfig} [fetchCall] - The fetch call configuration + * @property {ExtensionsCallConfig} [extensionsCall] - The extensions call configuration +*/ + +/** + * @typedef {Object} ABTestingConfig + * @property {boolean} enabled - Tells whether A/B testing is enabled for this instance + * @property {number} controlGroupPct - A/B testing probability + */ + +/** + * @typedef {Object} Multiplexing + * @property {boolean} [disabled] - Disable multiplexing (instance will work in single mode) + */ + +/** + * @typedef {Object} Diagnostics + * @property {boolean} [publishingDisabled] - Disable diagnostics publishing + * @property {number} [publishAfterLoadInMsec] - Delay in ms after script load after which collected diagnostics are published + * @property {boolean} [publishBeforeWindowUnload] - When true, diagnostics publishing is triggered on Window 'beforeunload' event + * @property {number} [publishingSampleRatio] - Diagnostics publishing sample ratio + */ + +/** + * @typedef {Object} Segment + * @property {string} [destination] - GVL ID or ID5-XX Partner ID. Mandatory + * @property {Array} [ids] - The segment IDs to push. Must contain at least one segment ID. + */ + +/** + * @typedef {Object} Id5PrebidConfig + * @property {number} partner - The ID5 partner ID + * @property {string} pd - The ID5 partner data string + * @property {ABTestingConfig} abTesting - The A/B testing configuration + * @property {boolean} disableExtensions - Disabled extensions call + * @property {string} [externalModuleUrl] - URL for the id5 prebid external module + * @property {Multiplexing} [multiplexing] - Multiplexing options. Only supported when loading the external module. + * @property {Diagnostics} [diagnostics] - Diagnostics options. Supported only in multiplexing + * @property {Array} [segments] - A list of segments to push to partners. Supported only in multiplexing. + * @property {boolean} [disableUaHints] - When true, look up of high entropy values through user agent hints is disabled. + */ + /** @type {Submodule} */ export const id5IdSubmodule = { /** @@ -118,7 +184,8 @@ export const id5IdSubmodule = { } const resp = function (cbFunction) { - new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()).execute() + const fetchFlow = new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()); + fetchFlow.execute() .then(response => { cbFunction(response) }) @@ -169,7 +236,7 @@ export const id5IdSubmodule = { }, }; -class IdFetchFlow { +export class IdFetchFlow { constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData, gppData) { this.submoduleConfig = submoduleConfig this.gdprConsentData = gdprConsentData @@ -178,83 +245,97 @@ class IdFetchFlow { this.gppData = gppData } - execute() { - return this.#callForConfig(this.submoduleConfig) - .then(fetchFlowConfig => { - return this.#callForExtensions(fetchFlowConfig.extensionsCall) - .then(extensionsData => { - return this.#callId5Fetch(fetchFlowConfig.fetchCall, extensionsData) - }) - }) - .then(fetchCallResponse => { - try { - resetNb(this.submoduleConfig.params.partner); - if (fetchCallResponse.privacy) { - storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); - } - } catch (error) { - logError(LOG_PREFIX + error); - } - return fetchCallResponse; - }) + /** + * Calls the ID5 Servers to fetch an ID5 ID + * @returns {Promise} The result of calling the server side + */ + async execute() { + const configCallPromise = this.#callForConfig(); + if (this.#isExternalModule()) { + try { + return await this.#externalModuleFlow(configCallPromise); + } catch (error) { + logError(LOG_PREFIX + 'Error while performing ID5 external module flow. Continuing with regular flow.', error); + return this.#regularFlow(configCallPromise); + } + } else { + return this.#regularFlow(configCallPromise); + } + } + + #isExternalModule() { + return typeof this.submoduleConfig.params.externalModuleUrl === 'string'; + } + + // eslint-disable-next-line no-dupe-class-members + async #externalModuleFlow(configCallPromise) { + await loadExternalModule(this.submoduleConfig.params.externalModuleUrl); + const fetchFlowConfig = await configCallPromise; + + return this.#getExternalIntegration().fetchId5Id(fetchFlowConfig, this.submoduleConfig.params, getRefererInfo(), this.gdprConsentData, this.usPrivacyData, this.gppData); } - #ajaxPromise(url, data, options) { - return new Promise((resolve, reject) => { - ajax(url, - { - success: function (res) { - resolve(res) - }, - error: function (err) { - reject(err) - } - }, data, options) - }) + // eslint-disable-next-line no-dupe-class-members + #getExternalIntegration() { + return window.id5Prebid && window.id5Prebid.integration; } // eslint-disable-next-line no-dupe-class-members - #callForConfig(submoduleConfig) { - let url = submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only - return this.#ajaxPromise(url, JSON.stringify(submoduleConfig), {method: 'POST'}) - .then(response => { - let responseObj = JSON.parse(response); - logInfo(LOG_PREFIX + 'config response received from the server', responseObj); - return responseObj; - }); + async #regularFlow(configCallPromise) { + const fetchFlowConfig = await configCallPromise; + const extensionsData = await this.#callForExtensions(fetchFlowConfig.extensionsCall); + const fetchCallResponse = await this.#callId5Fetch(fetchFlowConfig.fetchCall, extensionsData); + return this.#processFetchCallResponse(fetchCallResponse); } // eslint-disable-next-line no-dupe-class-members - #callForExtensions(extensionsCallConfig) { + async #callForConfig() { + let url = this.submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(this.submoduleConfig) + }); + if (!response.ok) { + throw new Error('Error while calling config endpoint: ', response); + } + const dynamicConfig = await response.json(); + logInfo(LOG_PREFIX + 'config response received from the server', dynamicConfig); + return dynamicConfig; + } + + // eslint-disable-next-line no-dupe-class-members + async #callForExtensions(extensionsCallConfig) { if (extensionsCallConfig === undefined) { - return Promise.resolve(undefined) + return undefined; + } + const extensionsUrl = extensionsCallConfig.url; + const method = extensionsCallConfig.method || 'GET'; + const body = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {}); + const response = await fetch(extensionsUrl, { method, body }); + if (!response.ok) { + throw new Error('Error while calling extensions endpoint: ', response); } - let extensionsUrl = extensionsCallConfig.url - let method = extensionsCallConfig.method || 'GET' - let data = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {}) - return this.#ajaxPromise(extensionsUrl, data, {'method': method}) - .then(response => { - let responseObj = JSON.parse(response); - logInfo(LOG_PREFIX + 'extensions response received from the server', responseObj); - return responseObj; - }) + const extensions = await response.json(); + logInfo(LOG_PREFIX + 'extensions response received from the server', extensions); + return extensions; } // eslint-disable-next-line no-dupe-class-members - #callId5Fetch(fetchCallConfig, extensionsData) { - let url = fetchCallConfig.url; - let additionalData = fetchCallConfig.overrides || {}; - let data = { + async #callId5Fetch(fetchCallConfig, extensionsData) { + const fetchUrl = fetchCallConfig.url; + const additionalData = fetchCallConfig.overrides || {}; + const body = JSON.stringify({ ...this.#createFetchRequestData(), ...additionalData, extensions: extensionsData - }; - return this.#ajaxPromise(url, JSON.stringify(data), {method: 'POST', withCredentials: true}) - .then(response => { - let responseObj = JSON.parse(response); - logInfo(LOG_PREFIX + 'fetch response received from the server', responseObj); - return responseObj; - }); + }); + const response = await fetch(fetchUrl, { method: 'POST', body, credentials: 'include' }); + if (!response.ok) { + throw new Error('Error while calling fetch endpoint: ', response); + } + const fetchResponse = await response.json(); + logInfo(LOG_PREFIX + 'fetch response received from the server', fetchResponse); + return fetchResponse; } // eslint-disable-next-line no-dupe-class-members @@ -263,7 +344,7 @@ class IdFetchFlow { const hasGdpr = (this.gdprConsentData && typeof this.gdprConsentData.gdprApplies === 'boolean' && this.gdprConsentData.gdprApplies) ? 1 : 0; const referer = getRefererInfo(); const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); - const nbPage = incrementNb(params.partner); + const nbPage = incrementAndResetNb(params.partner); const data = { 'partner': params.partner, 'gdpr': hasGdpr, @@ -309,6 +390,33 @@ class IdFetchFlow { } return data; } + + // eslint-disable-next-line no-dupe-class-members + #processFetchCallResponse(fetchCallResponse) { + try { + if (fetchCallResponse.privacy) { + storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); + } + } catch (error) { + logError(LOG_PREFIX + 'Error while writing privacy info into local storage.', error); + } + return fetchCallResponse; + } +} + +async function loadExternalModule(url) { + return new GreedyPromise((resolve, reject) => { + if (window.id5Prebid) { + // Already loaded + resolve(); + } else { + try { + loadExternalScript(url, 'id5', resolve); + } catch (error) { + reject(error); + } + } + }); } function validateConfig(config) { @@ -371,8 +479,10 @@ function incrementNb(partnerId) { return nb; } -function resetNb(partnerId) { +function incrementAndResetNb(partnerId) { + const result = incrementNb(partnerId); storeNbInCache(partnerId, 0); + return result; } function getLegacyCookieSignature() { diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 927fa10f87b..592c69056fa 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -25,6 +25,7 @@ pbjs.setConfig({ name: 'id5Id', params: { partner: 173, // change to the Partner Number you received from ID5 + externalModuleUrl: "https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js" // optional but recommended pd: 'MT1iNTBjY...', // optional, see table below for a link to how to generate this abTesting: { // optional enabled: true, // false by default @@ -49,6 +50,7 @@ pbjs.setConfig({ | name | Required | String | The name of this module: `"id5Id"` | `"id5Id"` | | params | Required | Object | Details for the ID5 ID. | | | params.partner | Required | Number | This is the ID5 Partner Number obtained from registering with ID5. | `173` | +| params.externalModuleUrl | Optional | String | The URL for the id5-prebid external module. It is recommended to use the latest version at the URL in the example. Source code available [here](https://github.com/id5io/id5-api.js/blob/master/src/id5PrebidModule.js). | https://cdn.id5-sync.com/api/1.0/id5PrebidModule.js | params.pd | Optional | String | Partner-supplied data used for linking ID5 IDs across domains. See [our documentation](https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/passing-partner-data-to-id5) for details on generating the string. Omit the parameter or leave as an empty string if no data to supply | `"MT1iNTBjY..."` | | params.provider | Optional | String | An identifier provided by ID5 to technology partners who manage Prebid setups on behalf of publishers. Reach out to [ID5](mailto:prebid@id5.io) if you have questions about this parameter | `pubmatic-identity-hub` | | params.abTesting | Optional | Object | Allows publishers to easily run an A/B Test. If enabled and the user is in the Control Group, the ID5 ID will NOT be exposed to bid adapters for that request | Disabled by default | diff --git a/src/adloader.js b/src/adloader.js index 87683828aa8..f60955736bd 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -30,7 +30,8 @@ const _approvedLoadExternalJSList = [ 'mediafilter', 'qortex', 'dynamicAdBoost', - 'contxtful' + 'contxtful', + 'id5' ] /** diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index d3d91db010d..ecce98d0b8d 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,26 +1,18 @@ -import { - expDaysStr, - getFromLocalStorage, - getNbFromCache, - ID5_PRIVACY_STORAGE_NAME, - ID5_STORAGE_NAME, - id5IdSubmodule, - nbCacheName, - storage, - storeInLocalStorage, - storeNbInCache, -} from 'modules/id5IdSystem.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'; -import * as utils from 'src/utils.js'; -import {uspDataHandler, gppDataHandler} from 'src/adapterManager.js'; -import 'src/prebid.js'; +import * as id5System from '../../../modules/id5IdSystem.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'; +import * as utils from '../../../src/utils.js'; +import {uspDataHandler, gppDataHandler} from '../../../src/adapterManager.js'; +import '../../../src/prebid.js'; 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'; + +const IdFetchFlow = id5System.IdFetchFlow; describe('ID5 ID System', function () { const ID5_MODULE_NAME = 'id5Id'; @@ -35,7 +27,6 @@ describe('ID5 ID System', function () { url: ID5_ENDPOINT } }; - const ID5_NB_STORAGE_NAME = nbCacheName(ID5_TEST_PARTNER_ID); const ID5_STORED_ID = 'storedid5id'; const ID5_STORED_SIGNATURE = '123456'; const ID5_STORED_LINK_TYPE = 1; @@ -74,11 +65,11 @@ describe('ID5 ID System', function () { 'Content-Type': 'application/json' } - function getId5FetchConfig(storageName = ID5_STORAGE_NAME, storageType = 'html5') { + function getId5FetchConfig(partner = ID5_TEST_PARTNER_ID, storageName = id5System.ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, params: { - partner: ID5_TEST_PARTNER_ID + partner }, storage: { name: storageName, @@ -109,7 +100,7 @@ describe('ID5 ID System', function () { } function getFetchLocalStorageConfig() { - return getUserSyncConfig([getId5FetchConfig(ID5_STORAGE_NAME, 'html5')]); + return getUserSyncConfig([getId5FetchConfig()]); } function getValueConfig(value) { @@ -126,67 +117,66 @@ describe('ID5 ID System', function () { } function callSubmoduleGetId(config, consentData, cacheIdObj) { - return new Promise((resolve) => { - id5IdSubmodule.getId(config, consentData, cacheIdObj).callback((response) => { - resolve(response) - }) + return new GreedyPromise((resolve) => { + id5System.id5IdSubmodule.getId(config, consentData, cacheIdObj).callback((response) => { + resolve(response); + }); }); } class XhrServerMock { + currentRequestIdx = 0; + server; + constructor(server) { - this.currentRequestIdx = 0 - this.server = server + this.currentRequestIdx = 0; + this.server = server; } - expectFirstRequest() { - return this.#expectRequest(0); + async expectFirstRequest() { + return this.#waitOnRequest(0); } - expectNextRequest() { - return this.#expectRequest(++this.currentRequestIdx) + async expectNextRequest() { + return this.#waitOnRequest(++this.currentRequestIdx); } - expectConfigRequest() { - return this.expectFirstRequest() - .then(configRequest => { - expect(configRequest.url).is.eq(ID5_API_CONFIG_URL); - expect(configRequest.method).is.eq('POST'); - return configRequest; - }) + async expectConfigRequest() { + const configRequest = await this.expectFirstRequest(); + expect(configRequest.url).is.eq(ID5_API_CONFIG_URL); + expect(configRequest.method).is.eq('POST'); + return configRequest; } - respondWithConfigAndExpectNext(configRequest, config = ID5_API_CONFIG) { + async respondWithConfigAndExpectNext(configRequest, config = ID5_API_CONFIG) { configRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(config)); - return this.expectNextRequest() + return this.expectNextRequest(); } - expectFetchRequest() { - return this.expectConfigRequest() - .then(configRequest => { - return this.respondWithConfigAndExpectNext(configRequest, ID5_API_CONFIG); - }).then(request => { - expect(request.url).is.eq(ID5_API_CONFIG.fetchCall.url); - expect(request.method).is.eq('POST'); - return request; - }) + async expectFetchRequest() { + const configRequest = await this.expectFirstRequest(); + const fetchRequest = await this.respondWithConfigAndExpectNext(configRequest); + expect(fetchRequest.method).is.eq('POST'); + expect(fetchRequest.url).is.eq(ID5_API_CONFIG.fetchCall.url); + return fetchRequest; } - #expectRequest(index) { - let server = this.server - return new Promise(function (resolve) { - (function waitForCondition() { - if (server.requests && server.requests.length > index) return resolve(server.requests[index]); - setTimeout(waitForCondition, 30); - })(); - }) - .then(request => { - return request - }); + async #waitOnRequest(index) { + const server = this.server + return new GreedyPromise((resolve) => { + const waitForCondition = () => { + if (server.requests && server.requests.length > index) { + resolve(server.requests[index]); + } else { + setTimeout(waitForCondition, 30); + } + }; + waitForCondition(); + }); } hasReceivedAnyRequest() { - let requests = this.server.requests; + const requests = this.server.requests; return requests && requests.length > 0; } } @@ -198,29 +188,29 @@ describe('ID5 ID System', function () { describe('Check for valid publisher config', function () { it('should fail with invalid config', function () { // no config - expect(id5IdSubmodule.getId()).is.eq(undefined); - expect(id5IdSubmodule.getId({})).is.eq(undefined); - - // valid params, invalid storage - expect(id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); - - // valid storage, invalid params - expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); - expect(id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId()).is.eq(undefined); + expect(id5System.id5IdSubmodule.getId({})).is.eq(undefined); + + // valid params, invalid id5System.storage + expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); + + // valid id5System.storage, invalid params + expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); }); - it('should warn with non-recommended storage params', function () { - let logWarnStub = sinon.stub(utils, 'logWarn'); + it('should warn with non-recommended id5System.storage params', function () { + const logWarnStub = sinon.stub(utils, 'logWarn'); - id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); - id5IdSubmodule.getId({ storage: { name: ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({ storage: { name: id5System.ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); }); @@ -240,19 +230,19 @@ describe('ID5 ID System', function () { dataConsentVals.forEach(function([purposeConsent, vendorConsent, caseName]) { it('should fail with invalid consent because of ' + caseName, function() { - let dataConsent = { + const dataConsent = { gdprApplies: true, consentString: 'consentString', vendorData: { purposeConsent, vendorConsent } } - expect(id5IdSubmodule.getId(config)).is.eq(undefined); - expect(id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); + expect(id5System.id5IdSubmodule.getId(config)).is.eq(undefined); + expect(id5System.id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); - let cacheIdObject = 'cacheIdObject'; - expect(id5IdSubmodule.extendId(config)).is.eq(undefined); - expect(id5IdSubmodule.extendId(config, dataConsent, cacheIdObject)).is.eq(cacheIdObject); + const cacheIdObject = 'cacheIdObject'; + expect(id5System.id5IdSubmodule.extendId(config)).is.eq(undefined); + expect(id5System.id5IdSubmodule.extendId(config, dataConsent, cacheIdObject)).is.eq(cacheIdObject); }); }); }); @@ -269,125 +259,121 @@ describe('ID5 ID System', function () { gppStub?.restore() }); - it('should call the ID5 server and handle a valid response', function () { - let xhrServerMock = new XhrServerMock(server) - let config = getId5FetchConfig(); - let submoduleResponse = callSubmoduleGetId(config, undefined, undefined); + it('should call the ID5 server and handle a valid response', async function () { + const xhrServerMock = new XhrServerMock(server) + const config = getId5FetchConfig(); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(fetchRequest.url).to.contain(ID5_ENDPOINT); - expect(fetchRequest.withCredentials).is.true; - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.o).is.eq('pbjs'); - expect(requestBody.pd).is.undefined; - expect(requestBody.s).is.undefined; - expect(requestBody.provider).is.undefined - expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.gdpr).is.eq(0); - expect(requestBody.gdpr_consent).is.undefined; - expect(requestBody.us_privacy).is.undefined; - expect(requestBody.storage).is.deep.eq(config.storage) + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, undefined); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(submoduleResponse => { - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); - }); + const fetchRequest = await xhrServerMock.expectFetchRequest() + + expect(fetchRequest.url).to.contain(ID5_ENDPOINT); + expect(fetchRequest.withCredentials).is.true; + + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.pd).is.undefined; + expect(requestBody.s).is.undefined; + expect(requestBody.provider).is.undefined + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.gdpr).is.eq(0); + expect(requestBody.gdpr_consent).is.undefined; + expect(requestBody.us_privacy).is.undefined; + expect(requestBody.storage).is.deep.eq(config.storage) + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with gdpr data ', function () { - let xhrServerMock = new XhrServerMock(server) - let consentData = { + it('should call the ID5 server with gdpr data ', async function () { + const xhrServerMock = new XhrServerMock(server) + const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA } - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.gdpr).to.eq(1); - expect(requestBody.gdpr_consent).is.eq(consentData.consentString); + const fetchRequest = await xhrServerMock.expectFetchRequest() + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.gdpr).to.eq(1); + expect(requestBody.gdpr_consent).is.eq(consentData.consentString); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(submoduleResponse => { - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); - }); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server without gdpr data when gdpr not applies ', function () { - let xhrServerMock = new XhrServerMock(server) - let consentData = { + it('should call the ID5 server without gdpr data when gdpr not applies ', async function () { + const xhrServerMock = new XhrServerMock(server) + const consentData = { gdprApplies: false, consentString: 'consentString' } - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.gdpr).to.eq(0); - expect(requestBody.gdpr_consent).is.undefined + const fetchRequest = await xhrServerMock.expectFetchRequest() + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.gdpr).to.eq(0); + expect(requestBody.gdpr_consent).is.undefined - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(submoduleResponse => { - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); - }); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with us privacy consent', function () { - let usPrivacyString = '1YN-'; + it('should call the ID5 server with us privacy consent', async function () { + const usPrivacyString = '1YN-'; uspDataHandler.setConsentData(usPrivacyString) - let xhrServerMock = new XhrServerMock(server) - let consentData = { + const xhrServerMock = new XhrServerMock(server) + const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA } - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.us_privacy).to.eq(usPrivacyString); + const fetchRequest = await xhrServerMock.expectFetchRequest() + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.us_privacy).to.eq(usPrivacyString); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(submoduleResponse => { - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); - }); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); }); - it('should call the ID5 server with no signature field when no stored object', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + it('should call the ID5 server with no signature field when no stored object', async function () { + const xhrServerMock = new XhrServerMock(server) - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.s).is.undefined; - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + const fetchRequest = await xhrServerMock.expectFetchRequest() + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.s).is.undefined; + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server for config with submodule config object', function () { - let xhrServerMock = new XhrServerMock(server) - let id5FetchConfig = getId5FetchConfig(); + it('should call the ID5 server for config with submodule config object', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.extraParam = { x: 'X', y: { @@ -395,341 +381,391 @@ describe('ID5 ID System', function () { b: '3' } } - let submoduleResponse = callSubmoduleGetId(id5FetchConfig, undefined, undefined); - return xhrServerMock.expectConfigRequest() - .then(configRequest => { - let requestBody = JSON.parse(configRequest.requestBody) - expect(requestBody).is.deep.eq(id5FetchConfig) - return xhrServerMock.respondWithConfigAndExpectNext(configRequest) - }) - .then(fetchRequest => { - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + const requestBody = JSON.parse(configRequest.requestBody); + expect(requestBody).is.deep.eq(id5FetchConfig) + + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server for config with partner id being a string', function () { - let xhrServerMock = new XhrServerMock(server) - let id5FetchConfig = getId5FetchConfig(); + it('should call the ID5 server for config with partner id being a string', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.partner = '173'; - let submoduleResponse = callSubmoduleGetId(id5FetchConfig, undefined, undefined); - return xhrServerMock.expectConfigRequest() - .then(configRequest => { - let requestBody = JSON.parse(configRequest.requestBody) - expect(requestBody.params.partner).is.eq(173) - return xhrServerMock.respondWithConfigAndExpectNext(configRequest) - }) - .then(fetchRequest => { - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + const requestBody = JSON.parse(configRequest.requestBody) + expect(requestBody.params.partner).is.eq(173) + + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server for config with overridden url', function () { - let xhrServerMock = new XhrServerMock(server) - let id5FetchConfig = getId5FetchConfig(); + it('should call the ID5 server for config with overridden url', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.configUrl = 'http://localhost/x/y/z' - let submoduleResponse = callSubmoduleGetId(id5FetchConfig, undefined, undefined); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); - return xhrServerMock.expectFirstRequest() - .then(configRequest => { - expect(configRequest.url).is.eq('http://localhost/x/y/z') - return xhrServerMock.respondWithConfigAndExpectNext(configRequest) - }) - .then(fetchRequest => { - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + const configRequest = await xhrServerMock.expectFirstRequest(); + expect(configRequest.url).is.eq('http://localhost/x/y/z'); + + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with additional data when provided', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); - - return xhrServerMock.expectConfigRequest() - .then(configRequest => { - return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { - fetchCall: { - url: ID5_ENDPOINT, - overrides: { - arg1: '123', - arg2: { - x: '1', - y: 2 - } - } + it('should call the ID5 server with additional data when provided', async function () { + const xhrServerMock = new XhrServerMock(server) + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT, + overrides: { + arg1: '123', + arg2: { + x: '1', + y: 2 } - }); - }) - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.o).is.eq('pbjs'); - expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.arg1).is.eq('123') - expect(requestBody.arg2).is.deep.eq({ - x: '1', - y: 2 - }) - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + } + } + }); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.arg1).is.eq('123') + expect(requestBody.arg2).is.deep.eq({ + x: '1', + y: 2 + }) + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with extensions', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); - - return xhrServerMock.expectConfigRequest() - .then(configRequest => { - return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { - fetchCall: { - url: ID5_ENDPOINT - }, - extensionsCall: { - url: ID5_EXTENSIONS_ENDPOINT, - method: 'GET' - } - }); - }) - .then(extensionsRequest => { - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('GET') - extensionsRequest.respond(200, responseHeader, JSON.stringify({ - lb: 'ex' - })) - return xhrServerMock.expectNextRequest(); - }) - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.o).is.eq('pbjs'); - expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.extensions).is.deep.eq({ - lb: 'ex' - }) - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + it('should call the ID5 server with extensions', async function () { + const xhrServerMock = new XhrServerMock(server) + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + const extensionsRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT + }, + extensionsCall: { + url: ID5_EXTENSIONS_ENDPOINT, + method: 'GET' + } + }); + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) + expect(extensionsRequest.method).is.eq('GET') + + extensionsRequest.respond(200, responseHeader, JSON.stringify({ + lb: 'ex' + })); + const fetchRequest = await xhrServerMock.expectNextRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.extensions).is.deep.eq({ + lb: 'ex' + }) + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with extensions fetched with POST', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); - - return xhrServerMock.expectConfigRequest() - .then(configRequest => { - return xhrServerMock.respondWithConfigAndExpectNext(configRequest, { - fetchCall: { - url: ID5_ENDPOINT - }, - extensionsCall: { - url: ID5_EXTENSIONS_ENDPOINT, - method: 'POST', - body: { - x: '1', - y: 2 - } - } - }); - }) - .then(extensionsRequest => { - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('POST') - let requestBody = JSON.parse(extensionsRequest.requestBody) - expect(requestBody).is.deep.eq({ + it('should call the ID5 server with extensions fetched using method POST', async function () { + const xhrServerMock = new XhrServerMock(server) + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + const extensionsRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest, { + fetchCall: { + url: ID5_ENDPOINT + }, + extensionsCall: { + url: ID5_EXTENSIONS_ENDPOINT, + method: 'POST', + body: { x: '1', y: 2 - }) - extensionsRequest.respond(200, responseHeader, JSON.stringify({ - lb: 'post', - })) - return xhrServerMock.expectNextRequest(); - }) - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); - expect(requestBody.o).is.eq('pbjs'); - expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.extensions).is.deep.eq({ - lb: 'post' - }) - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + } + } + }); + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) + expect(extensionsRequest.method).is.eq('POST') + const extRequestBody = JSON.parse(extensionsRequest.requestBody) + expect(extRequestBody).is.deep.eq({ + x: '1', + y: 2 + }) + extensionsRequest.respond(200, responseHeader, JSON.stringify({ + lb: 'post', + })); + + const fetchRequest = await xhrServerMock.expectNextRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); + expect(requestBody.o).is.eq('pbjs'); + expect(requestBody.v).is.eq('$prebid.version$'); + expect(requestBody.extensions).is.deep.eq({ + lb: 'post' + }); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with signature field from stored object', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + it('should call the ID5 server with signature field from stored object', async function () { + const xhrServerMock = new XhrServerMock(server) - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + const fetchRequest = await xhrServerMock.expectFetchRequest() + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with pd field when pd config is set', function () { - let xhrServerMock = new XhrServerMock(server) + it('should call the ID5 server with pd field when pd config is set', async function () { + const xhrServerMock = new XhrServerMock(server) const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; - let id5Config = getId5FetchConfig(); + const id5Config = getId5FetchConfig(); id5Config.params.pd = pubData; - let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, undefined); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.pd).is.eq(pubData); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse; - }) + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.pd).is.eq(pubData); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with no pd field when pd config is not set', function () { - let xhrServerMock = new XhrServerMock(server) - let id5Config = getId5FetchConfig(); + it('should call the ID5 server with no pd field when pd config is not set', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; - let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.pd).is.undefined; - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse; - }) + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.pd).is.undefined; + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server with nb=1 when no stored value exists and reset after', function () { - let xhrServerMock = new XhrServerMock(server) - coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); + it('should call the ID5 server with nb=1 when no stored value exists and reset after', async function () { + const xhrServerMock = new XhrServerMock(server) + const TEST_PARTNER_ID = 189; + coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(TEST_PARTNER_ID)); - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.nbPage).is.eq(1); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(() => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); - }) + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.nbPage).is.eq(1); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; + + expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); }); - it('should call the ID5 server with incremented nb when stored value exists and reset after', function () { - let xhrServerMock = new XhrServerMock(server) - storeNbInCache(ID5_TEST_PARTNER_ID, 1); + it('should call the ID5 server with incremented nb when stored value exists and reset after', async function () { + const xhrServerMock = new XhrServerMock(server); + const TEST_PARTNER_ID = 189; + const config = getId5FetchConfig(TEST_PARTNER_ID); + id5System.storeNbInCache(TEST_PARTNER_ID, 1); - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.nbPage).is.eq(2); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(() => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); - }) + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.nbPage).is.eq(2); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; + + expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); }); - it('should call the ID5 server with ab_testing object when abTesting is turned on', function () { - let xhrServerMock = new XhrServerMock(server) - let id5Config = getId5FetchConfig(); + it('should call the ID5 server with ab_testing object when abTesting is turned on', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5Config = getId5FetchConfig(); id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234} - let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.ab_testing.enabled).is.eq(true); - expect(requestBody.ab_testing.control_group_pct).is.eq(0.234); - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse; - }); + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing.enabled).is.eq(true); + expect(requestBody.ab_testing.control_group_pct).is.eq(0.234); + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server without ab_testing object when abTesting is turned off', function () { - let xhrServerMock = new XhrServerMock(server) - let id5Config = getId5FetchConfig(); + it('should call the ID5 server without ab_testing object when abTesting is turned off', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5Config = getId5FetchConfig(); id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55} - let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.ab_testing).is.undefined; - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }); + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing).is.undefined; + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should call the ID5 server without ab_testing when when abTesting is not set', function () { - let xhrServerMock = new XhrServerMock(server) - let id5Config = getId5FetchConfig(); + it('should call the ID5 server without ab_testing when when abTesting is not set', async function () { + const xhrServerMock = new XhrServerMock(server) + const id5Config = getId5FetchConfig(); - let submoduleResponse = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.ab_testing).is.undefined; - fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }); + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.ab_testing).is.undefined; + + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + await submoduleResponsePromise; }); - it('should store the privacy object from the ID5 server response', function () { - let xhrServerMock = new XhrServerMock(server) - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + it('should store the privacy object from the ID5 server response', async function () { + const xhrServerMock = new XhrServerMock(server) + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); const privacy = { jurisdiction: 'gdpr', id5_consent: true }; - return xhrServerMock.expectFetchRequest() - .then(request => { - let responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = privacy; - request.respond(200, responseHeader, JSON.stringify(responseObject)); - return submoduleResponse - }) - .then(() => { - expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).is.eq(JSON.stringify(privacy)); - coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); - }) + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = privacy; + + fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); + await submoduleResponsePromise; + + expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.eq(JSON.stringify(privacy)); + coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); }); - it('should not store a privacy object if not part of ID5 server response', function () { - let xhrServerMock = new XhrServerMock(server) - coreStorage.removeDataFromLocalStorage(ID5_PRIVACY_STORAGE_NAME); - let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + it('should not store a privacy object if not part of ID5 server response', async function () { + const xhrServerMock = new XhrServerMock(server); + coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); - return xhrServerMock.expectFetchRequest() - .then(request => { - let responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = undefined; - request.respond(200, responseHeader, JSON.stringify(responseObject)); - return submoduleResponse - }) - .then(() => { - expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).is.null; - }); + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const responseObject = utils.deepClone(ID5_JSON_RESPONSE); + responseObject.privacy = undefined; + + fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); + await submoduleResponsePromise; + + expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.null; + }); + + describe('with successful external module call', function() { + const MOCK_RESPONSE = { + ...ID5_JSON_RESPONSE, + universal_uid: 'my_mock_reponse' + }; + let mockId5ExternalModule; + + beforeEach(() => { + window.id5Prebid = { + integration: { + fetchId5Id: function() {} + } + }; + mockId5ExternalModule = sinon.stub(window.id5Prebid.integration, 'fetchId5Id') + .resolves(MOCK_RESPONSE); + }); + + this.afterEach(() => { + mockId5ExternalModule.restore(); + delete window.id5Prebid; + }); + + it('should retrieve the response from the external module interface', async function() { + const xhrServerMock = new XhrServerMock(server); + const config = getId5FetchConfig(); + config.params.externalModuleUrl = 'https://test-me.test'; + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, undefined); + + const configRequest = await xhrServerMock.expectConfigRequest(); + configRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_API_CONFIG)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).to.deep.equal(MOCK_RESPONSE); + expect(mockId5ExternalModule.calledOnce); + }); + }); + + describe('with failing external module loading', function() { + it('should fallback to regular logic if external module fails to load', async function() { + const xhrServerMock = new XhrServerMock(server); + const config = getId5FetchConfig(); + config.params.externalModuleUrl = 'https://test-me.test'; // Fails by loading this fake URL + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, undefined); + + // Still we have a server-side request triggered as fallback + const fetchRequest = await xhrServerMock.expectFetchRequest(); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).to.deep.equal(ID5_JSON_RESPONSE); + }); }); it('should pass gpp_string and gpp_sid to ID5 server', function () { @@ -756,14 +792,14 @@ describe('ID5 ID System', function () { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getCookie'); + sandbox.stub(id5System.storage, 'getCookie'); }); afterEach(() => { sandbox.restore(); }); it('should not throw if malformed JSON is forced into cookies', () => { - storage.getCookie.callsFake(() => ' Not JSON '); - id5IdSubmodule.getId(getId5FetchConfig()); + id5System.storage.getCookie.callsFake(() => ' Not JSON '); + id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); }) }); @@ -772,7 +808,7 @@ describe('ID5 ID System', function () { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'localStorageIsEnabled'); + sandbox.stub(id5System.storage, 'localStorageIsEnabled'); }); afterEach(() => { sandbox.restore(); @@ -780,26 +816,23 @@ describe('ID5 ID System', function () { [ [true, 1], [false, 0] - ].forEach(function ([isEnabled, expectedValue]) { - it(`should check localStorage availability and log in request. Available=${isEnabled}`, () => { - let xhrServerMock = new XhrServerMock(server) - let config = getId5FetchConfig(); - let submoduleResponse = callSubmoduleGetId(config, undefined, undefined); - storage.localStorageIsEnabled.callsFake(() => isEnabled) - - return xhrServerMock.expectFetchRequest() - .then(fetchRequest => { - let requestBody = JSON.parse(fetchRequest.requestBody); - expect(requestBody.localStorage).is.eq(expectedValue); - - fetchRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse - }) - .then(submoduleResponse => { - expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); - }); - }) - }) + ].forEach(([isEnabled, expectedValue]) => { + it(`should check localStorage availability and log in request. Available=${isEnabled}`, async function() { + const xhrServerMock = new XhrServerMock(server) + id5System.storage.localStorageIsEnabled.callsFake(() => isEnabled) + + // Trigger the fetch but we await on it later + const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + const fetchRequest = await xhrServerMock.expectFetchRequest(); + const requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.localStorage).is.eq(expectedValue); + + fetchRequest.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); + const submoduleResponse = await submoduleResponsePromise; + expect(submoduleResponse).is.deep.equal(ID5_JSON_RESPONSE); + }); + }); }); describe('Request Bids Hook', function () { @@ -810,26 +843,26 @@ describe('ID5 ID System', function () { sandbox = sinon.sandbox.create(); mockGdprConsent(sandbox); sinon.stub(events, 'getEvents').returns([]); - coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); - coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); - coreStorage.setDataInLocalStorage(ID5_STORAGE_NAME + '_cst', getConsentHash()) + coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); + coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()) adUnits = [getAdUnitMock()]; }); afterEach(function () { events.getEvents.restore(); - coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME); - coreStorage.removeDataFromLocalStorage(`${ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); - coreStorage.removeDataFromLocalStorage(ID5_STORAGE_NAME + '_cst') + coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); + coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); + coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); + coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst') sandbox.restore(); }); it('should add stored ID from cache to bids', function (done) { - storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); init(config); - setSubmoduleRegistry([id5IdSubmodule]); + setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); requestBidsHook(function () { @@ -855,7 +888,7 @@ describe('ID5 ID System', function () { it('should add config value ID to bids', function (done) { init(config); - setSubmoduleRegistry([id5IdSubmodule]); + setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getValueConfig(ID5_STORED_ID)); requestBidsHook(function () { @@ -874,44 +907,44 @@ describe('ID5 ID System', function () { }); it('should set nb=1 in cache when no stored nb value exists and cached ID', function (done) { - storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - coreStorage.removeDataFromLocalStorage(ID5_NB_STORAGE_NAME); + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); init(config); - setSubmoduleRegistry([id5IdSubmodule]); + setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); requestBidsHook((adUnitConfig) => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); + expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); done() }, {adUnits}); }); it('should increment nb in cache when stored nb value exists and cached ID', function (done) { - storeInLocalStorage(ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - storeNbInCache(ID5_TEST_PARTNER_ID, 1); + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); init(config); - setSubmoduleRegistry([id5IdSubmodule]); + setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); requestBidsHook(() => { - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); + expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); done() }, {adUnits}); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - let xhrServerMock = new XhrServerMock(server) - let initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); - storeInLocalStorage(ID5_STORAGE_NAME, initialLocalStorageValue, 1); - storeInLocalStorage(`${ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); + const xhrServerMock = new XhrServerMock(server) + const initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); + id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); + id5System.storeInLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`, id5System.expDaysStr(-1), 1); - storeNbInCache(ID5_TEST_PARTNER_ID, 1); + id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; init(config); - setSubmoduleRegistry([id5IdSubmodule]); + setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(id5Config); return new Promise((resolve) => { @@ -921,23 +954,23 @@ describe('ID5 ID System', function () { }).then(() => { expect(xhrServerMock.hasReceivedAnyRequest()).is.false; events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); - return xhrServerMock.expectFetchRequest() + return xhrServerMock.expectFetchRequest(); }).then(request => { - let requestBody = JSON.parse(request.requestBody); + const requestBody = JSON.parse(request.requestBody); expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); expect(requestBody.nbPage).is.eq(2); - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); + expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); return new Promise(function (resolve) { (function waitForCondition() { - if (getFromLocalStorage(ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); + if (id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); setTimeout(waitForCondition, 30); })(); }) }).then(() => { - expect(decodeURIComponent(getFromLocalStorage(ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); + expect(decodeURIComponent(id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); + expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); }) }); }); @@ -946,10 +979,10 @@ describe('ID5 ID System', function () { const expectedDecodedObject = {id5id: {uid: ID5_STORED_ID, ext: {linkType: ID5_STORED_LINK_TYPE}}}; it('should properly decode from a stored object', function () { - expect(id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).is.deep.equal(expectedDecodedObject); + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ, getId5FetchConfig())).is.deep.equal(expectedDecodedObject); }); it('should return undefined if passed a string', function () { - expect(id5IdSubmodule.decode('somestring', getId5FetchConfig())).is.eq(undefined); + expect(id5System.id5IdSubmodule.decode('somestring', getId5FetchConfig())).is.eq(undefined); }); }); @@ -992,13 +1025,13 @@ describe('ID5 ID System', function () { }); it('should not set abTestingControlGroup extension when A/B testing is off', function () { - let decoded = id5IdSubmodule.decode(storedObject, testConfig); + const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); }); it('should set abTestingControlGroup to false when A/B testing is on but in normal group', function () { storedObject.ab_testing = {result: 'normal'}; - let decoded = id5IdSubmodule.decode(storedObject, testConfig); + const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOn); }); @@ -1008,13 +1041,13 @@ describe('ID5 ID System', function () { storedObject.ext = { 'linkType': 0 }; - let decoded = id5IdSubmodule.decode(storedObject, testConfig); + const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); expect(decoded).is.deep.equal(expectedDecodedObjectWithoutIdAbOn); }); it('should log A/B testing errors', function () { storedObject.ab_testing = {result: 'error'}; - let decoded = id5IdSubmodule.decode(storedObject, testConfig); + const decoded = id5System.id5IdSubmodule.decode(storedObject, testConfig); expect(decoded).is.deep.equal(expectedDecodedObjectWithIdAbOff); sinon.assert.calledOnce(logErrorSpy); }); From a189b322775784c0d101ffaf5b7022299e9d7352 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 23 Jan 2024 15:38:35 -0700 Subject: [PATCH 365/760] JSdoc Lint Fixes for Multiple ID Modules (#10972) * fixes jsdoc * fix id jsdocs * jsdoc id fixes * jsdoc id fixes * fix id module lint * id lint fixes * fix lint jsdoc * jslint fixes * id jsdoc lint fix * jsdoc lint fixes --- modules/33acrossIdSystem.js | 6 ++++++ modules/admixerIdSystem.js | 7 +++++++ modules/adqueryIdSystem.js | 6 ++++++ modules/adriverIdSystem.js | 7 +++++++ modules/adtelligentIdSystem.js | 7 +++++++ modules/britepoolIdSystem.js | 7 +++++++ modules/connectIdSystem.js | 7 +++++++ modules/criteoIdSystem.js | 6 ++++++ modules/czechAdIdSystem.js | 5 +++++ modules/deepintentDpesIdSystem.js | 6 ++++++ modules/dmdIdSystem.js | 7 +++++++ modules/euidIdSystem.js | 7 +++++++ modules/fabrickIdSystem.js | 7 +++++++ modules/ftrackIdSystem.js | 7 +++++++ modules/growthCodeIdSystem.js | 6 ++++++ modules/hadronIdSystem.js | 6 ++++++ modules/id5IdSystem.js | 9 ++++++++- modules/identityLinkIdSystem.js | 7 +++++++ modules/idxIdSystem.js | 5 +++++ modules/imuIdSystem.js | 5 +++++ modules/intentIqIdSystem.js | 6 ++++++ modules/justIdSystem.js | 7 +++++++ modules/kinessoIdSystem.js | 6 ++++++ modules/liveIntentIdSystem.js | 6 ++++++ modules/lotamePanoramaIdSystem.js | 7 +++++++ modules/merkleIdSystem.js | 7 +++++++ modules/mwOpenLinkIdSystem.js | 5 +++++ modules/mygaruIdSystem.js | 6 ++++++ modules/naveggIdSystem.js | 5 +++++ modules/netIdSystem.js | 7 +++++++ modules/novatiqIdSystem.js | 5 +++++ modules/oneKeyIdSystem.js | 7 +++++++ modules/operaadsIdSystem.js | 5 +++++ modules/pairIdSystem.js | 4 ++++ modules/parrableIdSystem.js | 6 ++++++ modules/pubProvidedIdSystem.js | 9 +++++++-- modules/publinkIdSystem.js | 7 +++++++ modules/quantcastIdSystem.js | 4 ++++ modules/sharedIdSystem.js | 8 ++++++++ modules/teadsIdSystem.js | 7 +++++++ modules/uid2IdSystem.js | 7 +++++++ modules/unifiedIdSystem.js | 6 ++++++ modules/verizonMediaIdSystem.js | 7 +++++++ modules/zeotapIdPlusIdSystem.js | 5 +++++ src/targeting.js | 8 ++++---- src/userSync.js | 13 ++++++------- 46 files changed, 286 insertions(+), 14 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 0f370237e21..0cb1b1f3382 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -10,6 +10,12 @@ import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.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 = '33acrossId'; const API_URL = 'https://lexicon.33across.com/v1/envelope'; const AJAX_TIMEOUT = 10000; diff --git a/modules/admixerIdSystem.js b/modules/admixerIdSystem.js index 0e3a56420a8..cb7248c9537 100644 --- a/modules/admixerIdSystem.js +++ b/modules/admixerIdSystem.js @@ -11,6 +11,13 @@ 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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const NAME = 'admixerId'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: NAME}); diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index c5d01d7fbed..eb00011593d 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -11,6 +11,12 @@ import {submodule} from '../src/hook.js'; import {isFn, isPlainObject, isStr, logError, logInfo} from '../src/utils.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 = 'qid'; const AU_GVLID = 902; diff --git a/modules/adriverIdSystem.js b/modules/adriverIdSystem.js index c04ebf48028..1da75f2063d 100644 --- a/modules/adriverIdSystem.js +++ b/modules/adriverIdSystem.js @@ -11,6 +11,13 @@ 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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'adriverId'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); diff --git a/modules/adtelligentIdSystem.js b/modules/adtelligentIdSystem.js index 440ed9ade75..76713f29775 100644 --- a/modules/adtelligentIdSystem.js +++ b/modules/adtelligentIdSystem.js @@ -8,6 +8,13 @@ import * as ajax from '../src/ajax.js'; import { submodule } from '../src/hook.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const gvlid = 410; const moduleName = 'adtelligent'; const syncUrl = 'https://idrs.adtelligent.com/get'; diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js index b75fe9424b1..37ace544dc7 100644 --- a/modules/britepoolIdSystem.js +++ b/modules/britepoolIdSystem.js @@ -10,6 +10,13 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; const PIXEL = 'https://px.britepool.com/new?partner_id=t'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').SubmoduleParams} SubmoduleParams + */ + /** @type {Submodule} */ export const britepoolIdSubmodule = { /** diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 35a77a9d72d..2ebc68baa84 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -14,6 +14,13 @@ import {formatQS, isNumber, isPlainObject, logError, parseUrl} from '../src/util import {uspDataHandler, gppDataHandler} from '../src/adapterManager.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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'connectId'; const STORAGE_EXPIRY_DAYS = 365; const STORAGE_DURATION = 60 * 60 * 24 * 1000 * STORAGE_EXPIRY_DAYS; diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js index 6a09ce2c973..0c42858a0fb 100644 --- a/modules/criteoIdSystem.js +++ b/modules/criteoIdSystem.js @@ -13,6 +13,12 @@ import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + */ + const gvlid = 91; const bidderCode = 'criteo'; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: bidderCode }); diff --git a/modules/czechAdIdSystem.js b/modules/czechAdIdSystem.js index ae958aae198..7fdf462183a 100644 --- a/modules/czechAdIdSystem.js +++ b/modules/czechAdIdSystem.js @@ -9,6 +9,11 @@ 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').IdResponse} IdResponse + */ + // Returns StorageManager export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: 'czechAdId' }) diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 4d685592c04..2d3eae980cd 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.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').ConsentData} ConsentData + */ + const MODULE_NAME = 'deepintentId'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); diff --git a/modules/dmdIdSystem.js b/modules/dmdIdSystem.js index 2f910a8bd92..3575e658a2a 100644 --- a/modules/dmdIdSystem.js +++ b/modules/dmdIdSystem.js @@ -9,6 +9,13 @@ import { logError, getWindowLocation } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'dmdId'; /** @type {Submodule} */ diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index a29da69d6c7..fa6113250a8 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -14,6 +14,13 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // eslint-disable-next-line prebid/validate-imports import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').euidId} euidId + */ + const MODULE_NAME = 'euid'; const MODULE_REVISION = Uid2CodeVersion; const PREBID_VERSION = '$prebid.version$'; diff --git a/modules/fabrickIdSystem.js b/modules/fabrickIdSystem.js index bc9c30cb479..f62bafcf637 100644 --- a/modules/fabrickIdSystem.js +++ b/modules/fabrickIdSystem.js @@ -10,6 +10,13 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + /** @type {Submodule} */ export const fabrickIdSubmodule = { /** diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 809f1311c42..1794c3f76f4 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -12,6 +12,13 @@ import {uspDataHandler} from '../src/adapterManager.js'; import {loadExternalScript} from '../src/adloader.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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'ftrackId'; const LOG_PREFIX = 'FTRACK - '; const LOCAL_STORAGE_EXP_DAYS = 30; diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index e50a4e73019..539a20a7302 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -12,6 +12,12 @@ import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.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 GC_DATA_KEY = '_gc_data'; const GCID_KEY = 'gcid'; diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 4141e9a01f8..01b0283d3d1 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -13,6 +13,12 @@ import { config } from '../src/config.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.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 LOG_PREFIX = '[hadronIdSystem]'; const HADRONID_LOCAL_NAME = 'auHadronId'; const MODULE_NAME = 'hadronId'; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 96ae5386de6..b10f3c5d2cf 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -24,6 +24,13 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import { GreedyPromise } from '../src/utils/promise.js'; import { loadExternalScript } from '../src/adloader.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'id5Id'; const GVLID = 131; const NB_EXP_DAYS = 30; @@ -521,7 +528,7 @@ export function getFromLocalStorage(key) { * by default it's not required * @param {string} key * @param {any} value - * @param {integer} expDays + * @param {number} expDays */ export function storeInLocalStorage(key, value, expDays) { storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index d70b5680b69..82aa2303e1c 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -12,6 +12,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import { gppDataHandler } from '../src/adapterManager.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'identityLink'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); diff --git a/modules/idxIdSystem.js b/modules/idxIdSystem.js index bf807f199a6..db545eecd8c 100644 --- a/modules/idxIdSystem.js +++ b/modules/idxIdSystem.js @@ -9,6 +9,11 @@ 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 + */ + const IDX_MODULE_NAME = 'idx'; const IDX_COOKIE_NAME = '_idx'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: IDX_MODULE_NAME}); diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 38870c9403b..1242ca183ea 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -11,6 +11,11 @@ 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 + */ + const MODULE_NAME = 'imuid'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 5164080c317..109ea5c39c6 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -11,6 +11,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 PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js index 26e9275bbab..a5698023020 100644 --- a/modules/justIdSystem.js +++ b/modules/justIdSystem.js @@ -10,6 +10,13 @@ import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js' import {includes} from '../src/polyfill.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'justId'; const EXTERNAL_SCRIPT_MODULE_CODE = 'justtag'; const LOG_PREFIX = 'User ID - JustId submodule: '; diff --git a/modules/kinessoIdSystem.js b/modules/kinessoIdSystem.js index c13ed3976d3..35b8dcc182d 100644 --- a/modules/kinessoIdSystem.js +++ b/modules/kinessoIdSystem.js @@ -10,6 +10,12 @@ import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {coppaDataHandler, uspDataHandler} from '../src/adapterManager.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + */ + const MODULE_NAME = 'kpuid'; const ID_SVC = 'https://id.knsso.com/id'; // These values should NEVER change. If diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 8dc7102091d..ad71a584e42 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -13,6 +13,12 @@ import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.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 DEFAULT_AJAX_TIMEOUT = 5000 const EVENTS_TOPIC = 'pre_lips' const MODULE_NAME = 'liveIntentId'; diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 808a67492b0..64d631c2469 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -20,6 +20,13 @@ import {getStorageManager} from '../src/storageManager.js'; import { uspDataHandler } from '../src/adapterManager.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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const KEY_ID = 'panoramaId'; const KEY_EXPIRY = `${KEY_ID}_expiry`; const KEY_PROFILE = '_cc_id'; diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js index 20c6f817dca..3f3a90c3c49 100644 --- a/modules/merkleIdSystem.js +++ b/modules/merkleIdSystem.js @@ -11,6 +11,13 @@ 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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'merkleId'; const ID_URL = 'https://prebid.sv.rkdms.com/identity/'; const DEFAULT_REFRESH = 7 * 3600; diff --git a/modules/mwOpenLinkIdSystem.js b/modules/mwOpenLinkIdSystem.js index 8cc5cd8bb4a..c06f61ff82f 100644 --- a/modules/mwOpenLinkIdSystem.js +++ b/modules/mwOpenLinkIdSystem.js @@ -11,6 +11,11 @@ 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').SubmoduleParams} SubmoduleParams + */ + const openLinkID = { name: 'mwol', cookie_expiration: (86400 * 1000 * 365 * 1) // 1 year diff --git a/modules/mygaruIdSystem.js b/modules/mygaruIdSystem.js index 4d50de16d48..9133480477b 100644 --- a/modules/mygaruIdSystem.js +++ b/modules/mygaruIdSystem.js @@ -8,6 +8,12 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + */ + const bidderCode = 'mygaruId'; const syncUrl = 'https://ident.mygaru.com/v2/id'; diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index f82bf4002ca..42c6b113566 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -10,6 +10,11 @@ import { ajax } from '../src/ajax.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 + */ + const MODULE_NAME = 'naveggId'; const OLD_NAVEGG_ID = 'nid'; const NAVEGG_ID = 'nvggid'; diff --git a/modules/netIdSystem.js b/modules/netIdSystem.js index 6f1ffe8b0e7..4765f892a97 100644 --- a/modules/netIdSystem.js +++ b/modules/netIdSystem.js @@ -7,6 +7,13 @@ import {submodule} from '../src/hook.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + /** @type {Submodule} */ export const netIdSubmodule = { /** diff --git a/modules/novatiqIdSystem.js b/modules/novatiqIdSystem.js index d0cc94b513b..b6eab776df2 100644 --- a/modules/novatiqIdSystem.js +++ b/modules/novatiqIdSystem.js @@ -11,6 +11,11 @@ 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 + */ + const MODULE_NAME = 'novatiq'; /** @type {Submodule} */ diff --git a/modules/oneKeyIdSystem.js b/modules/oneKeyIdSystem.js index 7aa9e7b48ac..8765a72a1af 100644 --- a/modules/oneKeyIdSystem.js +++ b/modules/oneKeyIdSystem.js @@ -8,6 +8,13 @@ import {submodule} from '../src/hook.js'; import { logError, logMessage } from '../src/utils.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + // Pre-init OneKey if it has not load yet. window.OneKey = window.OneKey || {}; window.OneKey.queue = window.OneKey.queue || []; diff --git a/modules/operaadsIdSystem.js b/modules/operaadsIdSystem.js index c16d5a9c009..7cf5e2ce5e1 100644 --- a/modules/operaadsIdSystem.js +++ b/modules/operaadsIdSystem.js @@ -8,6 +8,11 @@ import * as ajax from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { logMessage, logError } from '../src/utils.js'; +/** + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'operaId'; const ID_KEY = MODULE_NAME; const version = '1.0'; diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js index 1009846e06a..dbff4c6a402 100644 --- a/modules/pairIdSystem.js +++ b/modules/pairIdSystem.js @@ -10,6 +10,10 @@ import {getStorageManager} from '../src/storageManager.js' import { logInfo } from '../src/utils.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + */ + const MODULE_NAME = 'pairId'; const PAIR_ID_KEY = 'pairId'; const DEFAULT_LIVERAMP_PAIR_ID_KEY = '_lr_pairId'; diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js index 3e3488f72f3..5651bdf0434 100644 --- a/modules/parrableIdSystem.js +++ b/modules/parrableIdSystem.js @@ -26,6 +26,12 @@ import {uspDataHandler} from '../src/adapterManager.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').ConsentData} ConsentData + */ + const PARRABLE_URL = 'https://h.parrable.com/prebid'; const PARRABLE_COOKIE_NAME = '_parrable_id'; const PARRABLE_GVLID = 928; diff --git a/modules/pubProvidedIdSystem.js b/modules/pubProvidedIdSystem.js index baffd997443..d23d992e495 100644 --- a/modules/pubProvidedIdSystem.js +++ b/modules/pubProvidedIdSystem.js @@ -9,6 +9,11 @@ import {submodule} from '../src/hook.js'; import { logInfo, isArray } from '../src/utils.js'; import {VENDORLESS_GVLID} from '../src/consentHandler.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + */ + const MODULE_NAME = 'pubProvidedId'; /** @type {Submodule} */ @@ -25,7 +30,7 @@ export const pubProvidedIdSubmodule = { * decode the stored id value for passing to bid request * @function * @param {string} value - * @returns {{pubProvidedId: array}} or undefined if value doesn't exists + * @returns {{pubProvidedId: Array}} or undefined if value doesn't exists */ decode(value) { const res = value ? {pubProvidedId: value} : undefined; @@ -37,7 +42,7 @@ export const pubProvidedIdSubmodule = { * performs action to obtain id and return a value. * @function * @param {SubmoduleConfig} [config] - * @returns {{id: array}} + * @returns {{id: Array}} */ getId(config) { const configParams = (config && config.params) || {}; diff --git a/modules/publinkIdSystem.js b/modules/publinkIdSystem.js index 1a993c99b45..e8eb90cd02a 100644 --- a/modules/publinkIdSystem.js +++ b/modules/publinkIdSystem.js @@ -12,6 +12,13 @@ import { parseUrl, buildUrl, logError } from '../src/utils.js'; import {uspDataHandler} from '../src/adapterManager.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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'publinkId'; const GVLID = 24; const PUBLINK_COOKIE = '_publink'; diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js index 2faf638fc0b..d980f5316e5 100644 --- a/modules/quantcastIdSystem.js +++ b/modules/quantcastIdSystem.js @@ -11,6 +11,10 @@ import { triggerPixel, logInfo } from '../src/utils.js'; import { uspDataHandler, coppaDataHandler, gdprDataHandler } from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + */ + const QUANTCAST_FPA = '__qca'; const DEFAULT_COOKIE_EXP_DAYS = 392; // (13 months - 2 days) const DAY_MS = 86400000; diff --git a/modules/sharedIdSystem.js b/modules/sharedIdSystem.js index 9046d6a633d..fa8b5e3bfdb 100644 --- a/modules/sharedIdSystem.js +++ b/modules/sharedIdSystem.js @@ -13,6 +13,14 @@ import {VENDORLESS_GVLID} from '../src/consentHandler.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').SubmoduleParams} SubmoduleParams + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: 'sharedId'}); const COOKIE = 'cookie'; const LOCAL_STORAGE = 'html5'; diff --git a/modules/teadsIdSystem.js b/modules/teadsIdSystem.js index 4744fd5d37b..8026fe77f87 100644 --- a/modules/teadsIdSystem.js +++ b/modules/teadsIdSystem.js @@ -12,6 +12,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {uspDataHandler} from '../src/adapterManager.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').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'teadsId'; const GVL_ID = 132; const FP_TEADS_ID_COOKIE_NAME = '_tfpvi'; diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 13c0303e0b3..32d2322e9bd 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -16,6 +16,13 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').uid2Id} uid2Id + */ + const MODULE_NAME = 'uid2'; const MODULE_REVISION = Uid2CodeVersion; const PREBID_VERSION = '$prebid.version$'; diff --git a/modules/unifiedIdSystem.js b/modules/unifiedIdSystem.js index 5576c40d960..e88aec3a90f 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -9,6 +9,12 @@ import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.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 = 'unifiedId'; /** @type {Submodule} */ diff --git a/modules/verizonMediaIdSystem.js b/modules/verizonMediaIdSystem.js index 6b22538fdc7..26fa89cfe03 100644 --- a/modules/verizonMediaIdSystem.js +++ b/modules/verizonMediaIdSystem.js @@ -10,6 +10,13 @@ import {submodule} from '../src/hook.js'; import {formatQS, logError} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'verizonMediaId'; const VENDOR_ID = 25; const PLACEHOLDER = '__PIXEL_ID__'; diff --git a/modules/zeotapIdPlusIdSystem.js b/modules/zeotapIdPlusIdSystem.js index ab7bf7c237b..32b7a5b77ad 100644 --- a/modules/zeotapIdPlusIdSystem.js +++ b/modules/zeotapIdPlusIdSystem.js @@ -9,6 +9,11 @@ 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 + */ + const ZEOTAP_COOKIE_NAME = 'IDP'; const ZEOTAP_VENDOR_ID = 301; const ZEOTAP_MODULE_NAME = 'zeotapIdPlus'; diff --git a/src/targeting.js b/src/targeting.js index 2c2ddefa6c2..ddbc3cebaf3 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -497,7 +497,7 @@ export function newTargeting(auctionManager) { }; /** - * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes + * @param {(string|string[])} adUnitCodes adUnitCode or array of adUnitCodes * Sets targeting for AST */ targeting.setTargetingForAst = function(adUnitCodes) { @@ -530,7 +530,7 @@ export function newTargeting(auctionManager) { /** * Get targeting key value pairs for winning bid. - * @param {string[]} AdUnit code array + * @param {string[]} adUnitCodes code array * @return {targetingArray} winning bids targeting */ function getWinningBidTargeting(adUnitCodes, bidsReceived) { @@ -630,7 +630,7 @@ export function newTargeting(auctionManager) { /** * Get custom targeting key value pairs for bids. - * @param {string[]} AdUnit code array + * @param {string[]} adUnitCodes code array * @return {targetingArray} bids with custom targeting defined in bidderSettings */ function getCustomBidTargeting(adUnitCodes, bidsReceived) { @@ -644,7 +644,7 @@ export function newTargeting(auctionManager) { /** * Get targeting key value pairs for non-winning bids. - * @param {string[]} AdUnit code array + * @param {string[]} adUnitCodes code array * @return {targetingArray} all non-winning bids targeting */ function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { diff --git a/src/userSync.js b/src/userSync.js index 153b36816a4..1b684de6de0 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -182,8 +182,8 @@ export function newUserSync(deps) { * @function incrementAdapterBids * @summary Increment the count of user syncs queue for the adapter * @private - * @params {object} numAdapterBids The object contain counts for all adapters - * @params {string} bidder The name of the bidder adding a sync + * @param {object} numAdapterBids The object contain counts for all adapters + * @param {string} bidder The name of the bidder adding a sync * @returns {object} The updated version of numAdapterBids */ function incrementAdapterBids(numAdapterBids, bidder) { @@ -199,10 +199,9 @@ export function newUserSync(deps) { * @function registerSync * @summary Add sync for this bidder to a queue to be fired later * @public - * @params {string} type The type of the sync including image, iframe - * @params {string} bidder The name of the adapter. e.g. "rubicon" - * @params {string} url Either the pixel url or iframe url depending on the type - + * @param {string} type The type of the sync including image, iframe + * @param {string} bidder The name of the adapter. e.g. "rubicon" + * @param {string} url Either the pixel url or iframe url depending on the type * @example Using Image Sync * // registerSync(type, adapter, pixelUrl) * userSync.registerSync('image', 'rubicon', 'http://example.com/pixel') @@ -309,7 +308,7 @@ export function newUserSync(deps) { * @function syncUsers * @summary Trigger all the user syncs based on publisher-defined timeout * @public - * @params {number} timeout The delay in ms before syncing data - default 0 + * @param {number} timeout The delay in ms before syncing data - default 0 */ publicApi.syncUsers = (timeout = 0) => { if (timeout) { From aa24e103cefdd7508ad6488ad495fbcba5e14e62 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 23 Jan 2024 15:39:03 -0700 Subject: [PATCH 366/760] JSdoc Linting Fixes for Multiiple RTD Modules (#10973) * fix rtd jsdoc * jsdoc lint fixes * import jsdoc types * rtd jsdoc fixes --- modules/a1MediaRtdProvider.js | 4 ++++ modules/adnuntiusRtdProvider.js | 4 ++++ modules/airgridRtdProvider.js | 4 ++++ modules/arcspanRtdProvider.js | 4 ++++ modules/blueconicRtdProvider.js | 4 ++++ modules/brandmetricsRtdProvider.js | 4 ++++ modules/browsiRtdProvider.js | 5 +++++ modules/cleanioRtdProvider.js | 4 ++++ modules/dgkeywordRtdProvider.js | 4 ++++ modules/experianRtdProvider.js | 6 ++++++ modules/geoedgeRtdProvider.js | 4 ++++ modules/goldfishAdsRtdProvider.js | 4 ++++ modules/hadronRtdProvider.js | 4 ++++ modules/idWardRtdProvider.js | 3 +++ modules/imRtdProvider.js | 4 ++++ modules/jwplayerRtdProvider.js | 5 +++++ modules/mgidRtdProvider.js | 4 ++++ modules/oneKeyRtdProvider.js | 4 ++++ modules/optimeraRtdProvider.js | 4 ++++ modules/oxxionRtdProvider.js | 4 ++++ modules/permutiveRtdProvider.js | 4 ++++ modules/reconciliationRtdProvider.js | 4 ++++ modules/timeoutRtdProvider.js | 4 ++++ 23 files changed, 95 insertions(+) diff --git a/modules/a1MediaRtdProvider.js b/modules/a1MediaRtdProvider.js index 9fa6b307b6a..445ed47181d 100644 --- a/modules/a1MediaRtdProvider.js +++ b/modules/a1MediaRtdProvider.js @@ -4,6 +4,10 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { isEmptyStr, mergeDeep } from '../src/utils.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const REAL_TIME_MODULE = 'realTimeData'; const MODULE_NAME = 'a1Media'; const SCRIPT_URL = 'https://linkback.contentsfeed.com/src'; diff --git a/modules/adnuntiusRtdProvider.js b/modules/adnuntiusRtdProvider.js index 9234a30aa33..1d5d639aa55 100644 --- a/modules/adnuntiusRtdProvider.js +++ b/modules/adnuntiusRtdProvider.js @@ -5,6 +5,10 @@ import { ajax } from '../src/ajax.js'; import { config as sourceConfig } from '../src/config.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const GVLID = 855; function init(config, userConsent) { diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 7c6cf1f5de0..2184544807e 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -11,6 +11,10 @@ import {getStorageManager} from '../src/storageManager.js'; 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 = 'airgrid'; const AG_TCF_ID = 782; diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js index a7ffa059279..8ccf3d160b9 100644 --- a/modules/arcspanRtdProvider.js +++ b/modules/arcspanRtdProvider.js @@ -2,6 +2,10 @@ import { submodule } from '../src/hook.js'; import { mergeDeep } from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'arcspan'; diff --git a/modules/blueconicRtdProvider.js b/modules/blueconicRtdProvider.js index bf0c457bebd..6b10e79b94c 100644 --- a/modules/blueconicRtdProvider.js +++ b/modules/blueconicRtdProvider.js @@ -11,6 +11,10 @@ import {submodule} from '../src/hook.js'; import {mergeDeep, isPlainObject, logMessage, logError} from '../src/utils.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 = 'blueconic'; diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 20b1125cee9..17336baa76c 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -11,6 +11,10 @@ import {loadExternalScript} from '../src/adloader.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const MODULE_NAME = 'brandmetrics' const MODULE_CODE = MODULE_NAME const RECEIVED_EVENTS = [] diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 1f37c7c5bc0..5281274616a 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -25,6 +25,11 @@ import {getGlobal} from '../src/prebidGlobal.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const MODULE_NAME = 'browsi'; const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index 7d0f461108b..f9bed5357ee 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -12,6 +12,10 @@ import { logError, generateUUID, insertElement } from '../src/utils.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + // ============================ MODULE STATE =============================== /** diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js index 76f5f04ac03..14519ae2713 100644 --- a/modules/dgkeywordRtdProvider.js +++ b/modules/dgkeywordRtdProvider.js @@ -11,6 +11,10 @@ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getGlobal } from '../src/prebidGlobal.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + /** * get keywords from api server. and set keywords. * @param {Object} reqBidsConfigObj diff --git a/modules/experianRtdProvider.js b/modules/experianRtdProvider.js index e18296342de..cd415d4b32c 100644 --- a/modules/experianRtdProvider.js +++ b/modules/experianRtdProvider.js @@ -12,6 +12,12 @@ import { } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + export const SUBMODULE_NAME = 'experian_rtid'; export const EXPERIAN_RTID_DATA_KEY = 'experian_rtid_data'; export const EXPERIAN_RTID_EXPIRATION_KEY = 'experian_rtid_expiration'; diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 37db5860001..a2ed71a898c 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -24,6 +24,10 @@ import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + /** @type {string} */ const SUBMODULE_NAME = 'geoedge'; /** @type {string} */ diff --git a/modules/goldfishAdsRtdProvider.js b/modules/goldfishAdsRtdProvider.js index c90d3104b56..c595e361968 100755 --- a/modules/goldfishAdsRtdProvider.js +++ b/modules/goldfishAdsRtdProvider.js @@ -3,6 +3,10 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { deepAccess } from '../src/utils.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + export const MODULE_NAME = 'goldfishAdsRtd'; export const MODULE_TYPE = 'realTimeData'; export const ENDPOINT_URL = 'https://prebid.goldfishads.com/iab-segments'; diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 6fb982815c1..5c604709b4b 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -14,6 +14,10 @@ import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from import {loadExternalScript} from '../src/adloader.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index 4e51f87cc81..dd08a132b2d 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -9,6 +9,9 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.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 = 'idWard'; diff --git a/modules/imRtdProvider.js b/modules/imRtdProvider.js index 5aa1d4e4dbe..78681c2beda 100644 --- a/modules/imRtdProvider.js +++ b/modules/imRtdProvider.js @@ -20,6 +20,10 @@ import { import {submodule} from '../src/hook.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + export const imUidLocalName = '__im_uid'; export const imVidCookieName = '_im_vid'; export const imRtdLocalName = '__im_sids'; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 18f89c33ad2..573ff391dae 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -16,6 +16,11 @@ import {deepAccess, logError} from '../src/utils.js'; import {find} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').adUnit} adUnit + */ + const SUBMODULE_NAME = 'jwplayer'; const JWPLAYER_DOMAIN = SUBMODULE_NAME + '.com'; const segCache = {}; diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js index fd2c0bbe6fd..059be4e9103 100644 --- a/modules/mgidRtdProvider.js +++ b/modules/mgidRtdProvider.js @@ -5,6 +5,10 @@ import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.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 = 'mgid'; const MGID_RTD_API_URL = 'https://servicer.mgid.com/sda'; diff --git a/modules/oneKeyRtdProvider.js b/modules/oneKeyRtdProvider.js index 27511017676..19915609820 100644 --- a/modules/oneKeyRtdProvider.js +++ b/modules/oneKeyRtdProvider.js @@ -3,6 +3,10 @@ import { submodule } from '../src/hook.js'; import { mergeDeep, logError, logMessage, deepSetValue, generateUUID } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const SUBMODULE_NAME = 'oneKey'; const prefixLog = 'OneKey.RTD-module' diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index 074d8d39bbf..bd564e3a260 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -23,6 +23,10 @@ import { logInfo, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; import { ajaxBuilder } from '../src/ajax.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + /** @type {ModuleParams} */ let _moduleParams = {}; diff --git a/modules/oxxionRtdProvider.js b/modules/oxxionRtdProvider.js index f968fc10e4b..a0476d8ca0f 100644 --- a/modules/oxxionRtdProvider.js +++ b/modules/oxxionRtdProvider.js @@ -3,6 +3,10 @@ import { logInfo, logError } from '../src/utils.js' import { ajax } from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const LOG_PREFIX = 'oxxionRtdProvider submodule: '; const bidderAliasRegistry = adapterManager.aliasRegistry || {}; diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 697d7721205..5a63990f84f 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -12,6 +12,10 @@ import {deepAccess, deepSetValue, isFn, logError, mergeDeep, isPlainObject, safe import {includes} from '../src/polyfill.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const MODULE_NAME = 'permutive' const logger = prefixLog('[PermutiveRTD]') diff --git a/modules/reconciliationRtdProvider.js b/modules/reconciliationRtdProvider.js index 9b6a3d7aca3..5671b2021d8 100644 --- a/modules/reconciliationRtdProvider.js +++ b/modules/reconciliationRtdProvider.js @@ -21,6 +21,10 @@ import {ajaxBuilder} from '../src/ajax.js'; import {generateUUID, isGptPubadsDefined, logError, timestamp} from '../src/utils.js'; import {find} from '../src/polyfill.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + /** @type {Object} */ const MessageType = { IMPRESSION_REQUEST: 'rsdk:impression:req', diff --git a/modules/timeoutRtdProvider.js b/modules/timeoutRtdProvider.js index fe9597f6f4e..a46a39a2c2b 100644 --- a/modules/timeoutRtdProvider.js +++ b/modules/timeoutRtdProvider.js @@ -4,6 +4,10 @@ import * as ajax from '../src/ajax.js'; import { logInfo, deepAccess, logError } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + const SUBMODULE_NAME = 'timeout'; // this allows the stubbing of functions during testing From b7f60d86589288d3afc9c397127ee00e3bdbb627 Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:15:27 +0530 Subject: [PATCH 367/760] Insticator Bid Adaptor: add support for bidder video params (#10976) * add support for bidder video params * add comment for bidderspecific override * fix video validation empty condition * fix test case --- modules/insticatorBidAdapter.js | 18 ++++-- .../spec/modules/insticatorBidAdapter_spec.js | 64 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 32ec5b482c4..3ea95ef8443 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -120,6 +120,7 @@ function buildVideo(bidRequest) { } const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video'); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); let optionalParams = {}; for (const param in OPTIONAL_VIDEO_PARAMS) { if (bidRequestVideo[param]) { @@ -132,7 +133,8 @@ function buildVideo(bidRequest) { mimes, w, h, - ...optionalParams + ...optionalParams, + ...videoBidderParams // bidder specific overrides for video } if (plcmt) { @@ -384,10 +386,16 @@ function validateBanner(bid) { } function validateVideo(bid) { - const video = deepAccess(bid, 'mediaTypes.video'); - - if (video === undefined) { - return true; + const videoParams = deepAccess(bid, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bid, 'params.video', {}); + let video = { + ...videoParams, + ...videoBidderParams // bidder specific overrides for video + } + // Check if the merged video object is empty + if (Object.keys(video).length === 0 || video === undefined) { + logError('insticator: video object is empty'); + return false; // or handle the case where video parameters are undefined or empty } let w = deepAccess(bid, 'mediaTypes.video.w'); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index f9aa3b913c6..149983552f8 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -286,6 +286,36 @@ describe('InsticatorBidAdapter', function () { } })).to.be.false; }); + + it('should return true when video bidder params override bidRequest video params', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [250, 300], + placement: 1, + }, + } + }, + params: { + ...bidRequest.params, + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + 'video/x-flv', + 'video/webm', + ], + placement: 2, + }, + } + })).to.be.true; + }); }); describe('buildRequests', function () { @@ -462,6 +492,40 @@ describe('InsticatorBidAdapter', function () { it('should return empty array if no valid requests are passed', function () { expect(spec.buildRequests([], bidderRequest)).to.be.an('array').that.have.lengthOf(0); }); + + it('should have bidder params override bidRequest mediatypes', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + 'video/x-flv', + 'video/webm', + 'video/ogg', + ], + plcmt: 4, + w: 640, + h: 480, + } + } + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].video.mimes).to.deep.equal([ + 'video/mp4', + 'video/mpeg', + 'video/x-flv', + 'video/webm', + 'video/ogg', + ]) + expect(data.imp[0].video.placement).to.equal(2); + expect(data.imp[0].video.plcmt).to.equal(4); + expect(data.imp[0].video.w).to.equal(640); + expect(data.imp[0].video.h).to.equal(480); + }); }); describe('interpretResponse', function () { From 12430d72667c39f97c52d95d44ec8e094a303120 Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Wed, 24 Jan 2024 03:52:07 -0800 Subject: [PATCH 368/760] Conversant adapter: use ortbConverter to handle requests and responses (#10913) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 396 ++++--------- .../spec/modules/conversantBidAdapter_spec.js | 543 ++++++++---------- 2 files changed, 377 insertions(+), 562 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index bef65a43616..c338a6633b5 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -1,23 +1,23 @@ import { - logWarn, - isStr, + buildUrl, deepAccess, - isArray, deepSetValue, - isEmpty, - _each, - parseUrl, - mergeDeep, - buildUrl, - _map, - logError, + getBidIdParameter, + isArray, isFn, - isPlainObject, getBidIdParameter, + isPlainObject, + isStr, + logError, + logWarn, + mergeDeep, + parseUrl, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; // Maintainer: mediapsr@epsilon.com @@ -27,6 +27,92 @@ const BIDDER_CODE = 'conversant'; export const storage = getStorageManager({gvlid: GVLID, bidderCode: BIDDER_CODE}); const URL = 'https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'; +function setSiteId(bidRequest, request) { + if (bidRequest.params.site_id) { + if (request.site) { + request.site.id = bidRequest.params.site_id; + } + if (request.app) { + request.app.id = bidRequest.params.site_id; + } + } +} + +function setPubcid(bidRequest, request) { + // Add common id if available + const pubcid = getPubcid(bidRequest); + if (pubcid) { + deepSetValue(request, 'user.ext.fpc', pubcid); + } +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300 + }, + request: function (buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + request.at = 1; + if (context.bidRequests) { + const bidRequest = context.bidRequests[0]; + setSiteId(bidRequest, request); + setPubcid(bidRequest, request); + } + + return request; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const data = { + secure: 1, + bidfloor: getBidFloor(bidRequest) || 0, + displaymanager: 'Prebid.js', + displaymanagerver: '$prebid.version$' + }; + copyOptProperty(bidRequest.params.tag_id, data, 'tagid'); + mergeDeep(imp, data, imp); + return imp; + }, + bidResponse: function (buildBidResponse, bid, context) { + if (!bid.price) return; + + // ensure that context.mediaType is set to banner or video otherwise + if (!context.mediaType && context.bidRequest.mediaTypes) { + const [type] = Object.keys(context.bidRequest.mediaTypes); + if (Object.values(ORTB_MTYPES).includes(type)) { + context.mediaType = type; + } + } + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + }, + + overrides: { + imp: { + banner(fillBannerImp, imp, bidRequest, context) { + if (bidRequest.mediaTypes && !bidRequest.mediaTypes.banner) return; + if (bidRequest.params.position) { + // fillBannerImp looks for mediaTypes.banner.pos so put it under the right name here + mergeDeep(bidRequest, {mediaTypes: {banner: {pos: bidRequest.params.position}}}); + } + fillBannerImp(imp, bidRequest, context); + }, + video(fillVideoImp, imp, bidRequest, context) { + if (bidRequest.mediaTypes && !bidRequest.mediaTypes.video) return; + const videoData = {}; + copyOptProperty(bidRequest.params?.position, videoData, 'pos'); + copyOptProperty(bidRequest.params?.mimes, videoData, 'mimes'); + copyOptProperty(bidRequest.params?.maxduration, videoData, 'maxduration'); + copyOptProperty(bidRequest.params?.protocols, videoData, 'protocols'); + copyOptProperty(bidRequest.params?.api, videoData, 'api'); + imp.video = mergeDeep(videoData, imp.video); + fillVideoImp(imp, bidRequest, context); + } + }, + } +}); + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -64,148 +150,14 @@ export const spec = { return true; }, - /** - * Make a server request from the list of BidRequests. - * - * @param {BidRequest[]} validBidRequests - an array of bids - * @param bidderRequest - * @return {ServerRequest} Info describing the request to the server. - */ - buildRequests: function(validBidRequests, bidderRequest) { - const page = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.page : ''; - let siteId = ''; - let pubcid = null; - let pubcidName = '_pubcid'; - let bidurl = URL; - - const conversantImps = validBidRequests.map(function(bid) { - const bidfloor = getBidFloor(bid); - - siteId = getBidIdParameter('site_id', bid.params) || siteId; - pubcidName = getBidIdParameter('pubcid_name', bid.params) || pubcidName; - - const imp = { - id: bid.bidId, - secure: 1, - bidfloor: bidfloor || 0, - displaymanager: 'Prebid.js', - displaymanagerver: '$prebid.version$' - }; - if (bid.ortb2Imp) { - mergeDeep(imp, bid.ortb2Imp); - } - - copyOptProperty(bid.params.tag_id, imp, 'tagid'); - - if (isVideoRequest(bid)) { - const videoData = deepAccess(bid, 'mediaTypes.video') || {}; - const format = convertSizes(videoData.playerSize || bid.sizes); - const video = {}; - - if (format && format[0]) { - copyOptProperty(format[0].w, video, 'w'); - copyOptProperty(format[0].h, video, 'h'); - } - - copyOptProperty(bid.params.position || videoData.pos, video, 'pos'); - copyOptProperty(bid.params.mimes || videoData.mimes, video, 'mimes'); - copyOptProperty(bid.params.maxduration || videoData.maxduration, video, 'maxduration'); - copyOptProperty(bid.params.protocols || videoData.protocols, video, 'protocols'); - copyOptProperty(bid.params.api || videoData.api, video, 'api'); - - imp.video = video; - } else { - const bannerData = deepAccess(bid, 'mediaTypes.banner') || {}; - const format = convertSizes(bannerData.sizes || bid.sizes); - const banner = {format: format}; - - copyOptProperty(bid.params.position || bannerData.pos, banner, 'pos'); - - imp.banner = banner; - } - - if (bid.userId && bid.userId.pubcid) { - pubcid = bid.userId.pubcid; - } else if (bid.crumbs && bid.crumbs.pubcid) { - pubcid = bid.crumbs.pubcid; - } - if (bid.params.white_label_url) { - bidurl = bid.params.white_label_url; - } - - return imp; - }); - - const payload = { - id: bidderRequest.bidderRequestId, - imp: conversantImps, - source: { - tid: bidderRequest.ortb2?.source?.tid, - }, - site: { - id: siteId, - mobile: document.querySelector('meta[name="viewport"][content*="width=device-width"]') !== null ? 1 : 0, - page: page - }, - device: getDevice(), - at: 1 - }; - - let userExt = {}; - - // pass schain object if it is present - const schain = deepAccess(validBidRequests, '0.schain'); - if (schain) { - deepSetValue(payload, 'source.ext.schain', schain); - } - - if (bidderRequest) { - if (bidderRequest.timeout) { - deepSetValue(payload, 'tmax', bidderRequest.timeout); - } - - // Add GDPR flag and consent string - if (bidderRequest.gdprConsent) { - userExt.consent = bidderRequest.gdprConsent.consentString; - - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - } - } - - if (bidderRequest.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - } - - if (!pubcid) { - pubcid = readStoredValue(pubcidName); - } - - // Add common id if available - if (pubcid) { - userExt.fpc = pubcid; - } - - // Add Eids if available - const eids = collectEids(validBidRequests); - if (eids.length > 0) { - userExt.eids = eids; - } - - // Only add the user object if it's not empty - if (!isEmpty(userExt)) { - payload.user = {ext: userExt}; - } - - const firstPartyData = bidderRequest.ortb2 || {}; - mergeDeep(payload, firstPartyData); - - return { + buildRequests: function(bidRequests, bidderRequest) { + const payload = converter.toORTB({bidderRequest, bidRequests}); + const result = { method: 'POST', - url: bidurl, + url: makeBidUrl(bidRequests[0]), data: payload, }; + return result; }, /** * Unpack the response from the server into a list of bids. @@ -215,59 +167,7 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - const bidResponses = []; - const requestMap = {}; - serverResponse = serverResponse.body; - - if (bidRequest && bidRequest.data && bidRequest.data.imp) { - _each(bidRequest.data.imp, imp => requestMap[imp.id] = imp); - } - - if (serverResponse && isArray(serverResponse.seatbid)) { - _each(serverResponse.seatbid, function(bidList) { - _each(bidList.bid, function(conversantBid) { - const responseCPM = parseFloat(conversantBid.price); - if (responseCPM > 0.0 && conversantBid.impid) { - const responseAd = conversantBid.adm || ''; - const responseNurl = conversantBid.nurl || ''; - const request = requestMap[conversantBid.impid]; - - const bid = { - requestId: conversantBid.impid, - currency: serverResponse.cur || 'USD', - cpm: responseCPM, - creativeId: conversantBid.crid || '', - ttl: 300, - netRevenue: true - }; - bid.meta = {}; - if (conversantBid.adomain && conversantBid.adomain.length > 0) { - bid.meta.advertiserDomains = conversantBid.adomain; - } - - if (request.video) { - if (responseAd.charAt(0) === '<') { - bid.vastXml = responseAd; - } else { - bid.vastUrl = responseAd; - } - - bid.mediaType = 'video'; - bid.width = request.video.w; - bid.height = request.video.h; - } else { - bid.ad = responseAd + ''; - bid.width = conversantBid.w; - bid.height = conversantBid.h; - } - - bidResponses.push(bid); - } - }) - }); - } - - return bidResponses; + return converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); }, /** @@ -327,51 +227,18 @@ export const spec = { } }; -/** - * Determine do-not-track state - * - * @returns {boolean} - */ -function getDNT() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -/** - * Return openrtb device object that includes ua, width, and height. - * - * @returns {Device} Openrtb device object - */ -function getDevice() { - const language = navigator.language ? 'language' : 'userLanguage'; - return { - h: screen.height, - w: screen.width, - dnt: getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent - }; -} - -/** - * Convert arrays of widths and heights to an array of objects with w and h properties. - * - * [[300, 250], [300, 600]] => [{w: 300, h: 250}, {w: 300, h: 600}] - * - * @param {Array.>} bidSizes - arrays of widths and heights - * @returns {object[]} Array of objects with w and h - */ -function convertSizes(bidSizes) { - let format; - if (Array.isArray(bidSizes)) { - if (bidSizes.length === 2 && typeof bidSizes[0] === 'number' && typeof bidSizes[1] === 'number') { - format = [{w: bidSizes[0], h: bidSizes[1]}]; - } else { - format = _map(bidSizes, d => { return {w: d[0], h: d[1]}; }); - } +function getPubcid(bidRequest) { + let pubcid = null; + if (bidRequest.userId && bidRequest.userId.pubcid) { + pubcid = bidRequest.userId.pubcid; + } else if (bidRequest.crumbs && bidRequest.crumbs.pubcid) { + pubcid = bidRequest.crumbs.pubcid; } - - return format; + if (!pubcid) { + const pubcidName = getBidIdParameter('pubcid_name', bidRequest.params) || '_pubcid'; + pubcid = readStoredValue(pubcidName); + } + return pubcid; } /** @@ -397,33 +264,6 @@ function copyOptProperty(src, dst, dstName) { } } -/** - * Collect IDs from validBidRequests and store them as an extended id array - * @param bidRequests valid bid requests - */ -function collectEids(bidRequests) { - const request = bidRequests[0]; // bidRequests have the same userId object - const eids = []; - if (isArray(request.userIdAsEids) && request.userIdAsEids.length > 0) { - // later following white-list can be converted to block-list if needed - const requiredSourceValues = { - 'epsilon.com': 1, - 'adserver.org': 1, - 'liveramp.com': 1, - 'criteo.com': 1, - 'id5-sync.com': 1, - 'parrable.com': 1, - 'liveintent.com': 1 - }; - request.userIdAsEids.forEach(function(eid) { - if (requiredSourceValues.hasOwnProperty(eid.source)) { - eids.push(eid); - } - }); - } - return eids; -} - /** * Look for a stored value from both cookie and local storage and return the first value found. * @param key Key for the search @@ -479,4 +319,12 @@ function getBidFloor(bid) { return floor } +function makeBidUrl(bid) { + let bidurl = URL; + if (bid.params.white_label_url) { + bidurl = bid.params.white_label_url; + } + return bidurl; +} + registerBidder(spec); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 59ebefa2d60..73d1978a9d9 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -2,13 +2,20 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/conversantBidAdapter.js'; import * as utils from 'src/utils.js'; import {createEidsArray} from 'modules/userId/eids.js'; -import { config } from '../../../src/config.js'; import {deepAccess} from 'src/utils'; +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; // handles eids +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; // handles schain +import {hook} from '../../../src/hook.js' describe('Conversant adapter tests', function() { const siteId = '108060'; const versionPattern = /^\d+\.\d+\.\d+(.)*$/; - const bidRequests = [ // banner with single size { @@ -19,13 +26,18 @@ describe('Conversant adapter tests', function() { tag_id: 'tagid-1', bidfloor: 0.5 }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, placementCode: 'pcode000', transactionId: 'tx000', - sizes: [[300, 250]], bidId: 'bid000', bidderRequestId: '117d765b87bed38', auctionId: 'req000' }, + // banner with sizes in mediaTypes.banner.sizes { bidder: 'conversant', @@ -51,9 +63,13 @@ describe('Conversant adapter tests', function() { position: 2, tag_id: '' }, + mediaTypes: { + banner: { + sizes: [[300, 600], [160, 600]], + } + }, placementCode: 'pcode002', transactionId: 'tx002', - sizes: [[300, 600], [160, 600]], bidId: 'bid002', bidderRequestId: '117d765b87bed38', auctionId: 'req000' @@ -77,7 +93,6 @@ describe('Conversant adapter tests', function() { }, placementCode: 'pcode003', transactionId: 'tx003', - sizes: [640, 480], bidId: 'bid003', bidderRequestId: '117d765b87bed38', auctionId: 'req000' @@ -125,16 +140,15 @@ describe('Conversant adapter tests', function() { bidderRequestId: '117d765b87bed38', auctionId: 'req000' }, - // video with first party data + // banner with first party data { bidder: 'conversant', params: { site_id: siteId }, mediaTypes: { - video: { - context: 'instream', - mimes: ['video/mp4', 'video/x-flv'] + banner: { + sizes: [[300, 600], [160, 600]], } }, ortb2Imp: { @@ -150,23 +164,6 @@ describe('Conversant adapter tests', function() { bidId: 'bid006', bidderRequestId: '117d765b87bed38', auctionId: 'req000' - }, - { - bidder: 'conversant', - params: { - site_id: siteId - }, - mediaTypes: { - banner: { - sizes: [[728, 90], [468, 60]], - pos: 5 - } - }, - placementCode: 'pcode001', - transactionId: 'tx001', - bidId: 'bid007', - bidderRequestId: '117d765b87bed38', - auctionId: 'req000' } ]; @@ -217,7 +214,14 @@ describe('Conversant adapter tests', function() { }] }] }, - headers: {}}; + headers: {} + }; + + before(() => { + // ortbConverter depends on other modules to be setup to work as expected so run hook.ready to register some + // submodules so functions like setOrtbSourceExtSchain and setOrtbUserExtEids are available + hook.ready(); + }); it('Verify basic properties', function() { expect(spec.code).to.equal('conversant'); @@ -232,12 +236,9 @@ describe('Conversant adapter tests', function() { expect(spec.isBidRequestValid({})).to.be.false; expect(spec.isBidRequestValid({params: {}})).to.be.false; expect(spec.isBidRequestValid({params: {site_id: '123'}})).to.be.true; - expect(spec.isBidRequestValid(bidRequests[0])).to.be.true; - expect(spec.isBidRequestValid(bidRequests[1])).to.be.true; - expect(spec.isBidRequestValid(bidRequests[2])).to.be.true; - expect(spec.isBidRequestValid(bidRequests[3])).to.be.true; - expect(spec.isBidRequestValid(bidRequests[4])).to.be.true; - expect(spec.isBidRequestValid(bidRequests[5])).to.be.true; + bidRequests.forEach((bid) => { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); const simpleVideo = JSON.parse(JSON.stringify(bidRequests[3])); simpleVideo.params.site_id = 123; @@ -251,152 +252,171 @@ describe('Conversant adapter tests', function() { expect(spec.isBidRequestValid(simpleVideo)).to.be.true; }); - it('Verify buildRequest', function() { - const page = 'http://test.com?a=b&c=123'; - const bidderRequest = { - refererInfo: { - page: page - }, - ortb2: { - source: { - tid: 'tid000' + describe('Verify buildRequest', function() { + let page, bidderRequest, request, payload; + before(() => { + page = 'http://test.com?a=b&c=123'; + // ortbConverter uses the site/device information from the ortb2 object passed in the bidderRequest object + bidderRequest = { + refererInfo: { + page: page + }, + ortb2: { + source: { + tid: 'tid000' + }, + site: { + mobile: 0, + page: page, + }, + device: { + w: screen.width, + h: screen.height, + dnt: 0, + ua: navigator.userAgent + } } - } - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'); - const payload = request.data; + }; + request = spec.buildRequests(bidRequests, bidderRequest); + payload = request.data; + }); + + it('Verify common elements', function() { + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://web.hb.ad.cpe.dotomi.com/cvx/client/hb/ortb/25'); + + expect(payload).to.have.property('id'); + expect(payload.source).to.have.property('tid', 'tid000'); + expect(payload).to.have.property('at', 1); + expect(payload).to.have.property('imp'); + expect(payload.imp).to.be.an('array').with.lengthOf(bidRequests.length); + + expect(payload).to.have.property('site'); + expect(payload.site).to.have.property('id', siteId); + expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); + + expect(payload.site).to.have.property('page', page); + + expect(payload).to.have.property('device'); + expect(payload.device).to.have.property('w', screen.width); + expect(payload.device).to.have.property('h', screen.height); + expect(payload.device).to.have.property('dnt').that.is.oneOf([0, 1]); + expect(payload.device).to.have.property('ua', navigator.userAgent); + + expect(payload).to.not.have.property('user'); // there should be no user by default + expect(payload).to.not.have.property('tmax'); // there should be no user by default + }); - expect(payload).to.have.property('id'); - expect(payload.source).to.have.property('tid', 'tid000'); - expect(payload).to.have.property('at', 1); - expect(payload).to.have.property('imp'); - expect(payload.imp).to.be.an('array').with.lengthOf(8); - - expect(payload.imp[0]).to.have.property('id', 'bid000'); - expect(payload.imp[0]).to.have.property('secure', 1); - expect(payload.imp[0]).to.have.property('bidfloor', 0.5); - expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); - expect(payload.imp[0]).to.have.property('banner'); - expect(payload.imp[0].banner).to.have.property('pos', 1); - expect(payload.imp[0].banner).to.have.property('format'); - expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); - expect(payload.imp[0]).to.not.have.property('video'); - - expect(payload.imp[1]).to.have.property('id', 'bid001'); - expect(payload.imp[1]).to.have.property('secure', 1); - expect(payload.imp[1]).to.have.property('bidfloor', 0); - expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[1]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[1]).to.not.have.property('tagid'); - expect(payload.imp[1]).to.have.property('banner'); - expect(payload.imp[1].banner).to.not.have.property('pos'); - expect(payload.imp[1].banner).to.have.property('format'); - expect(payload.imp[1].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); - - expect(payload.imp[2]).to.have.property('id', 'bid002'); - expect(payload.imp[2]).to.have.property('secure', 1); - expect(payload.imp[2]).to.have.property('bidfloor', 0); - expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[2]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[2]).to.have.property('banner'); - expect(payload.imp[2].banner).to.have.property('pos', 2); - expect(payload.imp[2].banner).to.have.property('format'); - expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); - - expect(payload.imp[3]).to.have.property('id', 'bid003'); - expect(payload.imp[3]).to.have.property('secure', 1); - expect(payload.imp[3]).to.have.property('bidfloor', 0); - expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[3]).to.not.have.property('tagid'); - expect(payload.imp[3]).to.have.property('video'); - expect(payload.imp[3].video).to.have.property('pos', 3); - expect(payload.imp[3].video).to.have.property('w', 632); - expect(payload.imp[3].video).to.have.property('h', 499); - expect(payload.imp[3].video).to.have.property('mimes'); - expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(payload.imp[3].video).to.have.property('protocols'); - expect(payload.imp[3].video.protocols).to.deep.equal([1, 2]); - expect(payload.imp[3].video).to.have.property('api'); - expect(payload.imp[3].video.api).to.deep.equal([2]); - expect(payload.imp[3].video).to.have.property('maxduration', 30); - expect(payload.imp[3]).to.not.have.property('banner'); - - expect(payload.imp[4]).to.have.property('id', 'bid004'); - expect(payload.imp[4]).to.have.property('secure', 1); - expect(payload.imp[4]).to.have.property('bidfloor', 0); - expect(payload.imp[4]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[4]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[4]).to.not.have.property('tagid'); - expect(payload.imp[4]).to.have.property('video'); - expect(payload.imp[4].video).to.not.have.property('pos'); - expect(payload.imp[4].video).to.have.property('w', 1024); - expect(payload.imp[4].video).to.have.property('h', 768); - expect(payload.imp[4].video).to.have.property('mimes'); - expect(payload.imp[4].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(payload.imp[4].video).to.have.property('protocols'); - expect(payload.imp[4].video.protocols).to.deep.equal([1, 2, 3]); - expect(payload.imp[4].video).to.have.property('api'); - expect(payload.imp[4].video.api).to.deep.equal([2, 3]); - expect(payload.imp[4].video).to.have.property('maxduration', 30); - expect(payload.imp[4]).to.not.have.property('banner'); - - expect(payload.imp[5]).to.have.property('id', 'bid005'); - expect(payload.imp[5]).to.have.property('secure', 1); - expect(payload.imp[5]).to.have.property('bidfloor', 0); - expect(payload.imp[5]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[5]).to.not.have.property('tagid'); - expect(payload.imp[5]).to.have.property('video'); - expect(payload.imp[5].video).to.have.property('pos', 2); - expect(payload.imp[5].video).to.not.have.property('w'); - expect(payload.imp[5].video).to.not.have.property('h'); - expect(payload.imp[5].video).to.have.property('mimes'); - expect(payload.imp[5].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(payload.imp[5].video).to.not.have.property('protocols'); - expect(payload.imp[5].video).to.not.have.property('api'); - expect(payload.imp[5].video).to.not.have.property('maxduration'); - expect(payload.imp[5]).to.not.have.property('banner'); - - expect(payload.imp[6]).to.have.property('id', 'bid006'); - expect(payload.imp[6]).to.have.property('video'); - expect(payload.imp[6].video).to.have.property('mimes'); - expect(payload.imp[6].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(payload.imp[6]).to.not.have.property('banner'); - expect(payload.imp[6]).to.have.property('instl'); - expect(payload.imp[6]).to.have.property('ext'); - expect(payload.imp[6].ext).to.have.property('data'); - expect(payload.imp[6].ext.data).to.have.property('pbadslot'); - - expect(payload.imp[7]).to.have.property('id', 'bid007'); - expect(payload.imp[7]).to.have.property('secure', 1); - expect(payload.imp[7]).to.have.property('bidfloor', 0); - expect(payload.imp[7]).to.have.property('displaymanager', 'Prebid.js'); - expect(payload.imp[7]).to.have.property('displaymanagerver').that.matches(versionPattern); - expect(payload.imp[7]).to.not.have.property('tagid'); - expect(payload.imp[7]).to.have.property('banner'); - expect(payload.imp[7].banner).to.have.property('pos', 5); - expect(payload.imp[7].banner).to.have.property('format'); - expect(payload.imp[7].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); - - expect(payload).to.have.property('site'); - expect(payload.site).to.have.property('id', siteId); - expect(payload.site).to.have.property('mobile').that.is.oneOf([0, 1]); - - expect(payload.site).to.have.property('page', page); - - expect(payload).to.have.property('device'); - expect(payload.device).to.have.property('w', screen.width); - expect(payload.device).to.have.property('h', screen.height); - expect(payload.device).to.have.property('dnt').that.is.oneOf([0, 1]); - expect(payload.device).to.have.property('ua', navigator.userAgent); - - expect(payload).to.not.have.property('user'); // there should be no user by default - expect(payload).to.not.have.property('tmax'); // there should be no user by default + it('Simple banner', () => { + expect(payload.imp[0]).to.have.property('id', 'bid000'); + expect(payload.imp[0]).to.have.property('secure', 1); + expect(payload.imp[0]).to.have.property('bidfloor', 0.5); + expect(payload.imp[0]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[0]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[0]).to.have.property('tagid', 'tagid-1'); + expect(payload.imp[0]).to.have.property('banner'); + expect(payload.imp[0].banner).to.have.property('pos', 1); + expect(payload.imp[0].banner).to.have.property('format'); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + expect(payload.imp[0]).to.not.have.property('video'); + }); + + it('Banner multiple sizes', () => { + expect(payload.imp[1]).to.have.property('id', 'bid001'); + expect(payload.imp[1]).to.have.property('secure', 1); + expect(payload.imp[1]).to.have.property('bidfloor', 0); + expect(payload.imp[1]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[1]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[1]).to.not.have.property('tagid'); + expect(payload.imp[1]).to.have.property('banner'); + expect(payload.imp[1].banner).to.not.have.property('pos'); + expect(payload.imp[1].banner).to.have.property('format'); + expect(payload.imp[1].banner.format).to.deep.equal([{w: 728, h: 90}, {w: 468, h: 60}]); + }); + + it('Banner with tagid and position', () => { + expect(payload.imp[2]).to.have.property('id', 'bid002'); + expect(payload.imp[2]).to.have.property('secure', 1); + expect(payload.imp[2]).to.have.property('bidfloor', 0); + expect(payload.imp[2]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[2]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[2]).to.have.property('banner'); + expect(payload.imp[2].banner).to.have.property('pos', 2); + expect(payload.imp[2].banner).to.have.property('format'); + expect(payload.imp[2].banner.format).to.deep.equal([{w: 300, h: 600}, {w: 160, h: 600}]); + }); + + if (FEATURES.VIDEO) { + it('Simple video', () => { + expect(payload.imp[3]).to.have.property('id', 'bid003'); + expect(payload.imp[3]).to.have.property('secure', 1); + expect(payload.imp[3]).to.have.property('bidfloor', 0); + expect(payload.imp[3]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[3]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[3]).to.not.have.property('tagid'); + expect(payload.imp[3]).to.have.property('video'); + expect(payload.imp[3].video).to.have.property('pos', 3); + expect(payload.imp[3].video).to.have.property('w', 632); + expect(payload.imp[3].video).to.have.property('h', 499); + expect(payload.imp[3].video).to.have.property('mimes'); + expect(payload.imp[3].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[3].video).to.have.property('protocols'); + expect(payload.imp[3].video.protocols).to.deep.equal([1, 2]); + expect(payload.imp[3].video).to.have.property('api'); + expect(payload.imp[3].video.api).to.deep.equal([2]); + expect(payload.imp[3].video).to.have.property('maxduration', 30); + expect(payload.imp[3]).to.not.have.property('banner'); + }); + + it('Video with playerSize', () => { + expect(payload.imp[4]).to.have.property('id', 'bid004'); + expect(payload.imp[4]).to.have.property('secure', 1); + expect(payload.imp[4]).to.have.property('bidfloor', 0); + expect(payload.imp[4]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[4]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[4]).to.not.have.property('tagid'); + expect(payload.imp[4]).to.have.property('video'); + expect(payload.imp[4].video).to.not.have.property('pos'); + expect(payload.imp[4].video).to.have.property('w', 1024); + expect(payload.imp[4].video).to.have.property('h', 768); + expect(payload.imp[4].video).to.have.property('mimes'); + expect(payload.imp[4].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[4].video).to.have.property('protocols'); + expect(payload.imp[4].video.protocols).to.deep.equal([1, 2, 3]); + expect(payload.imp[4].video).to.have.property('api'); + expect(payload.imp[4].video.api).to.deep.equal([1, 2, 3]); + expect(payload.imp[4].video).to.have.property('maxduration', 30); + }); + + it('Video without sizes', () => { + expect(payload.imp[5]).to.have.property('id', 'bid005'); + expect(payload.imp[5]).to.have.property('secure', 1); + expect(payload.imp[5]).to.have.property('bidfloor', 0); + expect(payload.imp[5]).to.have.property('displaymanager', 'Prebid.js'); + expect(payload.imp[5]).to.have.property('displaymanagerver').that.matches(versionPattern); + expect(payload.imp[5]).to.not.have.property('tagid'); + expect(payload.imp[5]).to.have.property('video'); + expect(payload.imp[5].video).to.have.property('pos', 2); + expect(payload.imp[5].video).to.not.have.property('w'); + expect(payload.imp[5].video).to.not.have.property('h'); + expect(payload.imp[5].video).to.have.property('mimes'); + expect(payload.imp[5].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(payload.imp[5].video).to.not.have.property('protocols'); + expect(payload.imp[5].video).to.not.have.property('api'); + expect(payload.imp[5].video).to.not.have.property('maxduration'); + expect(payload.imp[5]).to.not.have.property('banner'); + }); + } + + it('With FPD', () => { + expect(payload.imp[6]).to.have.property('id', 'bid006'); + expect(payload.imp[6]).to.have.property('banner'); + expect(payload.imp[6]).to.not.have.property('video'); + expect(payload.imp[6]).to.have.property('instl'); + expect(payload.imp[6]).to.have.property('ext'); + expect(payload.imp[6].ext).to.have.property('data'); + expect(payload.imp[6].ext.data).to.have.property('pbadslot'); + }); }); it('Verify timeout', () => { @@ -440,59 +460,62 @@ describe('Conversant adapter tests', function() { expect(request.url).to.equal(testUrl); }); - it('Verify interpretResponse', function() { - const request = spec.buildRequests(bidRequests, {}); - const response = spec.interpretResponse(bidResponses, request); - expect(response).to.be.an('array').with.lengthOf(4); - - let bid = response[0]; - expect(bid).to.have.property('requestId', 'bid000'); - expect(bid).to.have.property('currency', 'USD'); - expect(bid).to.have.property('cpm', 0.99); - expect(bid).to.have.property('creativeId', '1000'); - expect(bid).to.have.property('width', 300); - expect(bid).to.have.property('height', 250); - expect(bid.meta.advertiserDomains).to.deep.equal(['https://example.com']); - expect(bid).to.have.property('ad', 'markup000'); - expect(bid).to.have.property('ttl', 300); - expect(bid).to.have.property('netRevenue', true); + describe('Verify interpretResponse', function() { + let bid, request, response; + + before(() => { + request = spec.buildRequests(bidRequests, {}); + response = spec.interpretResponse(bidResponses, request).bids; + }); + + it('Banner', function() { + expect(response).to.be.an('array').with.lengthOf(4); + bid = response[0]; + expect(bid).to.have.property('requestId', 'bid000'); + expect(bid).to.have.property('cpm', 0.99); + expect(bid).to.have.property('creativeId', '1000'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 250); + expect(bid.meta.advertiserDomains).to.deep.equal(['https://example.com']); + expect(bid).to.have.property('ad', 'markup000
'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + }); // There is no bid001 because cpm is $0 - bid = response[1]; - expect(bid).to.have.property('requestId', 'bid002'); - expect(bid).to.have.property('currency', 'USD'); - expect(bid).to.have.property('cpm', 2.99); - expect(bid).to.have.property('creativeId', '1002'); - expect(bid).to.have.property('width', 300); - expect(bid).to.have.property('height', 600); - expect(bid).to.have.property('ad', 'markup002'); - expect(bid).to.have.property('ttl', 300); - expect(bid).to.have.property('netRevenue', true); - - bid = response[2]; - expect(bid).to.have.property('requestId', 'bid003'); - expect(bid).to.have.property('currency', 'USD'); - expect(bid).to.have.property('cpm', 3.99); - expect(bid).to.have.property('creativeId', '1003'); - expect(bid).to.have.property('width', 632); - expect(bid).to.have.property('height', 499); - expect(bid).to.have.property('vastUrl', 'markup003'); - expect(bid).to.have.property('mediaType', 'video'); - expect(bid).to.have.property('ttl', 300); - expect(bid).to.have.property('netRevenue', true); - - bid = response[3]; - expect(bid).to.have.property('vastXml', ''); - }); + it('Banner multiple sizes', function() { + bid = response[1]; + expect(bid).to.have.property('requestId', 'bid002'); + expect(bid).to.have.property('cpm', 2.99); + expect(bid).to.have.property('creativeId', '1002'); + expect(bid).to.have.property('width', 300); + expect(bid).to.have.property('height', 600); + expect(bid).to.have.property('ad', 'markup002
'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + }); + + if (FEATURES.VIDEO) { + it('Video', function () { + bid = response[2]; + expect(bid).to.have.property('requestId', 'bid003'); + expect(bid).to.have.property('cpm', 3.99); + expect(bid).to.have.property('creativeId', '1003'); + expect(bid).to.have.property('playerWidth', 632); + expect(bid).to.have.property('playerHeight', 499); + expect(bid).to.have.property('vastUrl', 'notify003'); + expect(bid).to.have.property('vastXml', 'markup003'); + expect(bid).to.have.property('mediaType', 'video'); + expect(bid).to.have.property('ttl', 300); + expect(bid).to.have.property('netRevenue', true); + }); - it('Verify handling of bad responses', function() { - let response = spec.interpretResponse({}, {}); - expect(response).to.be.an('array').with.lengthOf(0); - response = spec.interpretResponse({id: '123'}, {}); - expect(response).to.be.an('array').with.lengthOf(0); - response = spec.interpretResponse({id: '123', seatbid: []}, {}); - expect(response).to.be.an('array').with.lengthOf(0); + it('Empty Video', function() { + bid = response[3]; + expect(bid).to.have.property('vastXml', ''); + }); + } }); it('Verify publisher commond id support', function() { @@ -524,79 +547,23 @@ describe('Conversant adapter tests', function() { expect(payload).to.not.have.nested.property('user.ext.eids'); }); - it('Verify GDPR bid request', function() { - // add gdpr info - const bidderRequest = { - gdprConsent: { - consentString: 'BOJObISOJObISAABAAENAA4AAAAAoAAA', - gdprApplies: true - } - }; - - const payload = spec.buildRequests(bidRequests, bidderRequest).data; - expect(payload).to.have.deep.nested.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); - expect(payload).to.have.deep.nested.property('regs.ext.gdpr', 1); - }); - - it('Verify GDPR bid request without gdprApplies', function() { - // add gdpr info - const bidderRequest = { - gdprConsent: { - consentString: '' - } - }; - - const payload = spec.buildRequests(bidRequests, bidderRequest).data; - expect(payload).to.have.deep.nested.property('user.ext.consent', ''); - expect(payload).to.not.have.deep.nested.property('regs.ext.gdpr'); - }); - - describe('CCPA', function() { - it('should have us_privacy', function() { - const bidderRequest = { - uspConsent: '1NYN' - }; - - const payload = spec.buildRequests(bidRequests, bidderRequest).data; - expect(payload).to.have.deep.nested.property('regs.ext.us_privacy', '1NYN'); - expect(payload).to.not.have.deep.nested.property('regs.ext.gdpr'); - }); - - it('should have no us_privacy', function() { - const payload = spec.buildRequests(bidRequests, {}).data; - expect(payload).to.not.have.deep.nested.property('regs.ext.us_privacy'); - }); - - it('should have both gdpr and us_privacy', function() { - const bidderRequest = { - gdprConsent: { - consentString: 'BOJObISOJObISAABAAENAA4AAAAAoAAA', - gdprApplies: true - }, - uspConsent: '1NYN' - }; - - const payload = spec.buildRequests(bidRequests, bidderRequest).data; - expect(payload).to.have.deep.nested.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); - expect(payload).to.have.deep.nested.property('regs.ext.gdpr', 1); - expect(payload).to.have.deep.nested.property('regs.ext.us_privacy', '1NYN'); - }); - }); - describe('Extended ID', function() { it('Verify unifiedid and liveramp', function() { // clone bidRequests let requests = utils.deepClone(bidRequests); + const uid = {pubcid: '112233', idl_env: '334455'}; + const eidArray = [{'source': 'pubcid.org', 'uids': [{'id': '112233', 'atype': 1}]}, {'source': 'liveramp.com', 'uids': [{'id': '334455', 'atype': 3}]}]; + // add pubcid to every entry requests.forEach((unit) => { - Object.assign(unit, {userId: {pubcid: '112233', tdid: '223344', idl_env: '334455'}}); - Object.assign(unit, {userIdAsEids: createEidsArray(unit.userId)}); + Object.assign(unit, {userId: uid}); + Object.assign(unit, {userIdAsEids: eidArray}); }); // construct http post payload const payload = spec.buildRequests(requests, {}).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ - {source: 'adserver.org', uids: [{id: '223344', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: '112233', atype: 1}]}, {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} ]); }); From a40061e42abb9e6fcd2f7858ce2dd591cf4d01d8 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Wed, 24 Jan 2024 22:11:02 +0200 Subject: [PATCH 369/760] Missena Bid Adapter: send cookieDeprecationLabel and prebid version (#10979) --- modules/missenaBidAdapter.js | 4 ++++ test/spec/modules/missenaBidAdapter_spec.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 5e68f56ed1c..080ed2a556f 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -99,7 +99,11 @@ export const spec = { if (bidRequest.params.isInternal) { payload.is_internal = bidRequest.params.isInternal; } + if (bidRequest.ortb2?.device?.ext?.cdep) { + payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; + } payload.userEids = bidRequest.userIdAsEids || []; + payload.version = '$prebid.version$'; const bidFloor = getFloor(bidRequest); payload.floor = bidFloor?.floor; diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index a51cb8bbac9..f84981352ab 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -4,6 +4,7 @@ import { BANNER } from '../../../src/mediaTypes.js'; const REFERRER = 'https://referer'; const REFERRER2 = 'https://referer2'; +const COOKIE_DEPRECATION_LABEL = 'test'; describe('Missena Adapter', function () { $$PREBID_GLOBAL$$.bidderSettings = { @@ -18,6 +19,11 @@ describe('Missena Adapter', function () { bidId: bidId, sizes: [[1, 1]], mediaTypes: { banner: { sizes: [[1, 1]] } }, + ortb2: { + device: { + ext: { cdep: COOKIE_DEPRECATION_LABEL }, + }, + }, params: { apiKey: 'PA-34745704', placement: 'sticky', @@ -183,6 +189,14 @@ describe('Missena Adapter', function () { it('should participate if capped on a different page', function () { expect(cappedRequestsOtherPage.length).to.equal(2); }); + + it('should send the prebid version', function () { + expect(payload.version).to.equal('$prebid.version$'); + }); + + it('should send cookie deprecation', function () { + expect(payload.cdep).to.equal(COOKIE_DEPRECATION_LABEL); + }); }); describe('interpretResponse', function () { From ec5880b0f4c6094f2e8f8519976ca5c15fb4ccb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=90=91=E5=86=9B?= <401154047@qq.com> Date: Fri, 26 Jan 2024 01:49:41 +0800 Subject: [PATCH 370/760] zMaticoo Bid Adapter : Initial Release (#10881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:zMaticoo Bid Adapter for Prebid.js * feat:zMaticoo Bid Adapter for Prebid.js * feat:fix some code style * feat:fix some code style * feat:update package-lock.json --------- Co-authored-by: adam --- modules/zmaticooBidAdapter.js | 141 +++++++++++++++++++ modules/zmaticooBidAdapter.md | 45 ++++++ test/spec/modules/zmaticooBidAdapter_spec.js | 83 +++++++++++ 3 files changed, 269 insertions(+) create mode 100644 modules/zmaticooBidAdapter.js create mode 100644 modules/zmaticooBidAdapter.md create mode 100644 test/spec/modules/zmaticooBidAdapter_spec.js diff --git a/modules/zmaticooBidAdapter.js b/modules/zmaticooBidAdapter.js new file mode 100644 index 00000000000..8566d58c6de --- /dev/null +++ b/modules/zmaticooBidAdapter.js @@ -0,0 +1,141 @@ +import {deepAccess, logWarn} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'zmaticoo'; +const ENDPOINT_URL = 'https://bid.zmaticoo.com/prebid/bid'; +const DEFAULT_CUR = 'USD'; +const TTL = 200; +const NET_REV = true; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + // check for all required bid fields + if (!(bid && bid.bidId && bid.params)) { + logWarn('Invalid bid request - missing required bid data'); + return false; + } + + if (!(bid.params.pubId)) { + logWarn('Invalid bid request - missing required field pubId'); + return false; + } + + if (!(bid.params.device && bid.params.device.ip)) { + logWarn('Invalid bid request - missing required device data'); + return false; + } + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {Bids[]} validBidRequests - an array of bidRequest objects + * @param {BidderRequest} bidderRequest - master bidRequest object + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const secure = 1; + const request = validBidRequests[0]; + const params = request.params; + let impData = { + id: request.bidId, + secure: secure, + banner: buildBanner(request), + ext: { + bidder: { + pubId: params.pubId + } + } + }; + let payload = { + id: bidderRequest.bidderRequestId, + imp: [impData], + site: params.site ? params.site : {}, + app: params.app ? params.app : {}, + device: params.device ? params.device : {}, + user: params.user ? params.user : {}, + at: params.at, + tmax: params.tmax, + wseat: params.wseat, + bseat: params.bseat, + allimps: params.allimps, + cur: [DEFAULT_CUR], + wlang: params.wlang, + bcat: deepAccess(bidderRequest.ortb2Imp, 'bcat') || params.bcat, + badv: params.badv, + bapp: params.bapp, + source: params.source ? params.source : {}, + regs: params.regs ? params.regs : {}, + ext: params.ext ? params.ext : {} + }; + + payload.device.ua = navigator.userAgent; + payload.device.ip = navigator.ip; + payload.site.page = bidderRequest.refererInfo.page; + payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; + if (params.test) { + payload.test = params.test; + } + if (request.gdprConsent) { + payload.regs.ext = Object.assign(payload.regs.ext, {gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0}); + } + if (request.gdprConsent && request.gdprConsent.gdprApplies) { + payload.user.ext = Object.assign(payload.user.ext, {consent: request.gdprConsent.consentString}); + } + const postUrl = ENDPOINT_URL; + return { + method: 'POST', url: postUrl, data: JSON.stringify(payload), + }; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param bidRequest The payload from the server's response. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + let bidResponse = []; + if (Object.keys(serverResponse.body).length !== 0) { + let zresponse = serverResponse.body; + let zbid = zresponse.seatbid[0].bid[0]; + let bid = { + requestId: zbid.impid, + cpm: zbid.price, + currency: zbid.cur, + width: zbid.w, + height: zbid.h, + ad: zbid.adm, + ttl: TTL, + creativeId: zbid.crid, + netRevenue: NET_REV + }; + bidResponse.push(bid); + } + return bidResponse; + } +} + +function buildBanner(request) { + let sizes = request.sizes; + if (request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + return { + w: sizes[0][0], h: sizes[0][1] + }; +} + +registerBidder(spec); diff --git a/modules/zmaticooBidAdapter.md b/modules/zmaticooBidAdapter.md new file mode 100644 index 00000000000..95959ef218c --- /dev/null +++ b/modules/zmaticooBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: zMaticoo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adam.li@eclicktech.com.cn +``` + +# Description + +zMaticoo Bidder Adapter for Prebid.js. + +# Test Parameters + +``` + var adUnits = [ + { + mediaTypes: { + banner: { + sizes: [[320, 50]], // a display size + } + }, + bids: [ + { + bidder: 'zmaticoo', + bidId: '12345', + params: { + user: { + uid: '12345', + buyeruid: '12345' + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + pubId: 'prebid-fgh', + test: 1 + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/zmaticooBidAdapter_spec.js b/test/spec/modules/zmaticooBidAdapter_spec.js new file mode 100644 index 00000000000..898dccf1fde --- /dev/null +++ b/test/spec/modules/zmaticooBidAdapter_spec.js @@ -0,0 +1,83 @@ +import {spec} from '../../../modules/zmaticooBidAdapter.js' + +describe('zMaticoo Bidder Adapter', function () { + const bannerRequest = [{ + bidId: '1234511', + auctionId: '223', + mediaTypes: { + banner: { + sizes: [[320, 50]], + } + }, + refererInfo: { + page: 'testprebid.com' + }, + params: { + user: { + uid: '12345', + buyeruid: '12345' + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + pubId: 'prebid-test', + test: 1 + } + }]; + + it('Test the bid validation function', function () { + const validBid = spec.isBidRequestValid(bannerRequest[0]); + const invalidBid = spec.isBidRequestValid(null); + + expect(validBid).to.be.true; + expect(invalidBid).to.be.false; + }); + + it('Test the request processing function', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + expect(request).to.not.be.empty; + + const payload = request.data; + expect(payload).to.not.be.empty; + }); + + const responseBody = { + id: '12345', + seatbid: [ + { + bid: [ + { + id: 'auctionId', + impid: 'impId', + price: 0.0, + adm: 'adMarkup', + crid: 'creativeId', + h: 50, + w: 320 + } + ] + } + ], + cur: 'USD' + }; + + it('Test the response parsing function', function () { + const receivedBid = responseBody.seatbid[0].bid[0]; + const response = {}; + response.body = responseBody; + + const bidResponse = spec.interpretResponse(response, null); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal(receivedBid.adm); + expect(bid.cpm).to.equal(receivedBid.price); + expect(bid.height).to.equal(receivedBid.h); + expect(bid.width).to.equal(receivedBid.w); + expect(bid.requestId).to.equal(receivedBid.impid); + }); +}); From 57c18bd43e6db1291a28f03e1df4393e3f17f149 Mon Sep 17 00:00:00 2001 From: Yimilh <34877463+Yimilh@users.noreply.github.com> Date: Fri, 26 Jan 2024 02:17:11 +0800 Subject: [PATCH 371/760] MediaGo Bid Adapter : add pmguid, title, description, keywords and synchronize mguid from third party cookie to first party cookie. (#10923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MediaGo Bid Adapter : update buyerId and add pmguid,title, description and keywords. --- modules/mediagoBidAdapter.js | 149 ++++++-- test/spec/modules/mediagoBidAdapter_spec.js | 375 +++++++++++++++++++- 2 files changed, 497 insertions(+), 27 deletions(-) diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index a9c670f35d8..8513fba94c4 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -11,40 +11,105 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'mediago'; // const PROTOCOL = window.document.location.protocol; const ENDPOINT_URL = 'https://gbid.mediago.io/api/bid?tn='; -const COOKY_SYNC_URL = 'https://gtrace.mediago.io/ju/cs/eplist'; +// const COOKY_SYNC_URL = 'https://gtrace.mediago.io/ju/cs/eplist'; const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; +export const THIRD_PARTY_COOKIE_ORIGIN = 'https://cdn.mediago.io'; const TIME_TO_LIVE = 500; const GVLID = 1020; // const ENDPOINT_URL = '/api/bid?tn='; -const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); let globals = {}; let itemMaps = {}; /* ----- mguid:start ------ */ -const COOKIE_KEY_MGUID = '__mguid_'; -const STORE_MAX_AGE = 1000 * 60 * 60 * 24 * 365; +export const COOKIE_KEY_MGUID = '__mguid_'; +const COOKIE_KEY_PMGUID = '__pmguid_'; +const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year let reqTimes = 0; +/** + * get page title + * @returns {string} + */ + +export function getPageTitle(win = window) { + try { + const ogTitle = win.top.document.querySelector('meta[property="og:title"]') + return win.top.document.title || (ogTitle && ogTitle.content) || ''; + } catch (e) { + const ogTitle = document.querySelector('meta[property="og:title"]') + return document.title || (ogTitle && ogTitle.content) || ''; + } +} /** - * 获取用户id - * @return {string} + * get page description + * + * @returns {string} + */ +export function getPageDescription(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="description"]') || + win.top.document.querySelector('meta[property="og:description"]') + } catch (e) { + element = document.querySelector('meta[name="description"]') || + document.querySelector('meta[property="og:description"]') + } + + return (element && element.content) || ''; +} + +/** + * get page keywords + * @returns {string} + */ +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return (element && element.content) || ''; +} + +/** + * get connection downlink + * @returns {number} */ -const getUserID = () => { - const i = storage.getCookie(COOKIE_KEY_MGUID); +export function getConnectionDownLink(win = window) { + const nav = win.navigator || {}; + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; +} - if (i === null) { - const uuid = utils.generateUUID(); - storage.setCookie(COOKIE_KEY_MGUID, uuid, STORE_MAX_AGE); - return uuid; +/** + * get pmg uid + * 获取并生成用户的id + * + * @return {string} + */ +export const getPmgUID = () => { + if (!storage.cookiesAreEnabled()) return; + + let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); + if (!pmgUid) { + pmgUid = utils.generateUUID(); + try { + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); + } catch (e) {} } - return i; + return pmgUid; }; -/* ----- mguid:end ------ */ +/* ----- pmguid:end ------ */ /** * 获取一个对象的某个值,如果没有则返回空字符串 + * * @param {Object} obj 对象 * @param {...string} keys 键名 * @return {any} @@ -283,6 +348,16 @@ function getReferrer(bidRequest = {}, bidderRequest = {}) { return pageUrl; } +/** + * get current time to UTC string + * @returns utc string + */ +export function getCurrentTimeToUTCString() { + const date = new Date(); + date.setTime(date.getTime() + COOKIE_RETENTION_TIME); + return date.toUTCString(); +} + /** * 获取rtb请求参数 * @@ -317,6 +392,10 @@ function getParam(validBidRequests, bidderRequest) { const timeout = bidderRequest.timeout || 2000; const firstPartyData = bidderRequest.ortb2; + const topWindow = window.top; + const title = getPageTitle(); + const desc = getPageDescription(); + const keywords = getPageKeywords(); if (items && items.length) { let c = { @@ -344,11 +423,23 @@ function getParam(validBidRequests, bidderRequest) { firstPartyData, content, cat, - reqTimes + reqTimes, + pmguid: getPmgUID(), + page: { + title: title ? title.slice(0, 100) : undefined, + desc: desc ? desc.slice(0, 300) : undefined, + keywords: keywords ? keywords.slice(0, 100) : undefined, + hLen: topWindow.history?.length || undefined, + }, + device: { + nbw: getConnectionDownLink(), + hc: topWindow.navigator?.hardwareConcurrency || undefined, + dm: topWindow.navigator?.deviceMemory || undefined, + } }, user: { - buyeruid: getUserID(), - id: sharedid || pubcid + buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, + id: sharedid || pubcid, }, eids, site: { @@ -407,13 +498,12 @@ export const spec = { return { method: 'POST', url: ENDPOINT_URL + globals['token'], - data: payloadString + data: payloadString, }; }, /** * Unpack the response from the server into a list of bids. - * * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ @@ -471,19 +561,26 @@ export const spec = { } if (syncOptions.iframeEnabled) { + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin != THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + this.removeEventListener('message', handler); + + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + }, true); return [ { type: 'iframe', url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` } ]; - } else { - return [ - { - type: 'image', - url: `${COOKY_SYNC_URL}?${syncParamUrl}` - } - ]; } }, diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index 601bcff527a..6e58217b3d3 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -1,5 +1,17 @@ import { expect } from 'chai'; -import { spec } from 'modules/mediagoBidAdapter.js'; +import { + spec, + getPmgUID, + storage, + getPageTitle, + getPageDescription, + getPageKeywords, + getConnectionDownLink, + THIRD_PARTY_COOKIE_ORIGIN, + COOKIE_KEY_MGUID, + getCurrentTimeToUTCString +} from 'modules/mediagoBidAdapter.js'; +import * as utils from 'src/utils.js'; describe('mediago:BidAdapterTests', function () { let bidRequestData = { @@ -161,6 +173,47 @@ describe('mediago:BidAdapterTests', function () { expect(req_data.imp).to.have.lengthOf(1); }); + describe('mediago: buildRequests', function() { + describe('getPmgUID function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(utils, 'generateUUID').returns('new-uuid'); + sandbox.stub(storage, 'cookiesAreEnabled'); + }) + + afterEach(() => { + sandbox.restore(); + }); + + it('should generate new UUID and set cookie if not exists', () => { + storage.cookiesAreEnabled.callsFake(() => true); + storage.getCookie.callsFake(() => null); + const uid = getPmgUID(); + expect(uid).to.equal('new-uuid'); + expect(storage.setCookie.calledOnce).to.be.true; + }); + + it('should return existing UUID from cookie', () => { + storage.cookiesAreEnabled.callsFake(() => true); + storage.getCookie.callsFake(() => 'existing-uuid'); + const uid = getPmgUID(); + expect(uid).to.equal('existing-uuid'); + expect(storage.setCookie.called).to.be.false; + }); + + it('should not set new UUID when cookies are not enabled', () => { + storage.cookiesAreEnabled.callsFake(() => false); + storage.getCookie.callsFake(() => null); + getPmgUID(); + expect(storage.setCookie.calledOnce).to.be.false; + }); + }) + }); + it('mediago:validate_response_params', function () { let adm = ''; @@ -207,4 +260,324 @@ describe('mediago:BidAdapterTests', function () { expect(bid.height).to.equal(250); expect(bid.currency).to.equal('USD'); }); + + describe('mediago: getUserSyncs', function() { + const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; + const IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }; + const IFRAME_DISABLED = { + iframeEnabled: false, + pixelEnabled: false, + }; + const GDPR_CONSENT = { + consentString: 'gdprConsentString', + gdprApplies: true + }; + const USP_CONSENT = { + consentString: 'uspConsentString' + } + + let syncParamUrl = `dm=${encodeURIComponent(location.origin || `https://${location.host}`)}`; + syncParamUrl += '&gdpr=1&gdpr_consent=gdprConsentString&ccpa_consent=uspConsentString'; + const expectedIframeSyncs = [ + { + type: 'iframe', + url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` + } + ]; + + it('should return nothing if iframe is disabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_DISABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.be.undefined; + }); + + it('should do userSyncs if iframe is enabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_ENABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.deep.equal(expectedIframeSyncs); + }); + }); +}); + +describe('mediago Bid Adapter Tests', function () { + describe('buildRequests', () => { + describe('getPageTitle function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document title if available', function() { + const fakeTopDocument = { + title: 'Top Document Title', + querySelector: () => ({ content: 'Top Document Title test' }) + }; + const fakeTopWindow = { + document: fakeTopDocument + }; + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal('Top Document Title'); + }); + + it('should return the content of top og:title meta tag if title is empty', function() { + const ogTitleContent = 'Top OG Title Content'; + const fakeTopWindow = { + document: { + title: '', + querySelector: sandbox.stub().withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }) + } + }; + + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return the document title if no og:title meta tag is present', function() { + document.title = 'Test Page Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + + const result = getPageTitle({ top: undefined }); + expect(result).to.equal('Test Page Title'); + }); + + it('should return the content of og:title meta tag if present', function() { + document.title = ''; + const ogTitleContent = 'Top OG Title Content'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return an empty string if no title or og:title meta tag is found', function() { + document.title = ''; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(''); + }); + + it('should handle exceptions when accessing top.document and fallback to current document', function() { + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const ogTitleContent = 'Current OG Title Content'; + document.title = 'Current Document Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle(fakeWindow); + expect(result).to.equal('Current Document Title'); + }); + }); + + describe('getPageDescription function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document description if available', function() { + const descriptionContent = 'Top Document Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[name="description"]').returns({ content: descriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(descriptionContent); + }); + + it('should return the top document og:description if description is not present', function() { + const ogDescriptionContent = 'Top OG Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(ogDescriptionContent); + }); + + it('should return the current document description if top document is not accessible', function() { + const descriptionContent = 'Current Document Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="description"]').returns({ content: descriptionContent }) + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(descriptionContent); + }); + + it('should return the current document og:description if description is not present and top document is not accessible', function() { + const ogDescriptionContent = 'Current OG Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }); + + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(ogDescriptionContent); + }); + }); + + describe('getPageKeywords function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document keywords if available', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + const fakeTopDocument = { + querySelector: sandbox.stub() + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + + const result = getPageKeywords({ top: fakeTopWindow }); + expect(result).to.equal(keywordsContent); + }); + + it('should return the current document keywords if top document is not accessible', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }); + + // 模拟顶层窗口访问异常 + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + + const result = getPageKeywords(fakeWindow); + expect(result).to.equal(keywordsContent); + }); + + it('should return an empty string if no keywords meta tag is found', function() { + sandbox.stub(document, 'querySelector').withArgs('meta[name="keywords"]').returns(null); + + const result = getPageKeywords(); + expect(result).to.equal(''); + }); + }); + describe('getConnectionDownLink function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the downlink value as a string if available', function() { + const downlinkValue = 2.5; + const fakeNavigator = { + connection: { + downlink: downlinkValue + } + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.equal(downlinkValue.toString()); + }); + + it('should return undefined if downlink is not available', function() { + const fakeNavigator = { + connection: {} + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should return undefined if connection is not available', function() { + const fakeNavigator = {}; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should handle cases where navigator is not defined', function() { + const result = getConnectionDownLink({}); + expect(result).to.be.undefined; + }); + }); + + describe('getUserSyncs with message event listener', function() { + function messageHandler(event) { + if (!event.data || event.origin !== THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + window.removeEventListener('message', messageHandler, true); + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + } + + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(window, 'removeEventListener'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should set a cookie when a valid message is received', () => { + const fakeEvent = { + data: { optout: '', mguid: '12345' }, + origin: THIRD_PARTY_COOKIE_ORIGIN, + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.calledOnce).to.be.true; + expect(window.removeEventListener.calledWith('message', messageHandler, true)).to.be.true; + expect(storage.setCookie.calledWith(COOKIE_KEY_MGUID, '12345', sinon.match.string)).to.be.true; + }); + it('should not do anything when an invalid message is received', () => { + const fakeEvent = { + data: null, + origin: 'http://invalid-origin.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true; + expect(window.removeEventListener.notCalled).to.be.true; + expect(storage.setCookie.notCalled).to.be.true; + }); + }); + }); }); From 379b687f49d37a79850aa5e5bc9251950d2e2219 Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Fri, 26 Jan 2024 04:37:07 +0800 Subject: [PATCH 372/760] Discovery Bid Adapter : not filter params (#10965) * feat(isBidRequestValid): just filter token once. not filter publisher and tagid * feat(isBidRequestValid): add unit test * feat(spec): fix eslint * feat(spec): fix unit test * feat(spec): fix unit test --------- Co-authored-by: yubei01 --- modules/discoveryBidAdapter.js | 4 +- test/spec/modules/discoveryBidAdapter_spec.js | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 551c78c1e24..1576a8dcb08 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -511,7 +511,7 @@ export const spec = { if (bid.params.badv) { globals['badv'] = Array.isArray(bid.params.badv) ? bid.params.badv : []; } - return !!(bid.params.token && bid.params.publisher && bid.params.tagid); + return true; }, /** @@ -522,6 +522,8 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { + if (!globals['token']) return; + let payload = getParam(validBidRequests, bidderRequest); const payloadString = JSON.stringify(payload); diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 05216ff126c..65fbf33022f 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -80,6 +80,77 @@ describe('discovery:BidAdapterTests', function () { }; let request = []; + let bidRequestDataNoParams = { + bidderCode: 'discovery', + auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', + bidderRequestId: '4fec04e87ad785', + bids: [ + { + bidder: 'discovery', + params: { + referrer: 'https://discovery.popin.cc', + }, + refererInfo: { + page: 'https://discovery.popin.cc', + stack: [ + 'a.com', + 'b.com' + ] + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + pos: 'left', + }, + }, + ortb2: { + user: { + ext: { + data: { + CxSegments: [] + } + } + }, + site: { + domain: 'discovery.popin.cc', + publisher: { + domain: 'discovery.popin.cc' + }, + page: 'https://discovery.popin.cc', + cat: ['IAB-19', 'IAB-20'], + }, + }, + ortb2Imp: { + ext: { + gpid: 'adslot_gpid', + tid: 'tid_01', + data: { + browsi: { + browsiViewability: 'NA', + }, + adserver: { + name: 'adserver_name', + adslot: 'adslot_name', + }, + keywords: ['travel', 'sport'], + pbadslot: '202309999' + } + } + }, + adUnitCode: 'regular_iframe', + transactionId: 'd163f9e2-7ecd-4c2c-a3bd-28ceb52a60ee', + sizes: [[300, 250]], + bidId: '276092a19e05eb', + bidderRequestId: '1fadae168708b', + auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + }, + ], + }; + it('discovery:validate_pub_params', function () { expect( spec.isBidRequestValid({ @@ -93,6 +164,15 @@ describe('discovery:BidAdapterTests', function () { ).to.equal(true); }); + it('isBidRequestValid:no_params', function () { + expect( + spec.isBidRequestValid({ + bidder: 'discovery', + params: {}, + }) + ).to.equal(true); + }); + it('discovery:validate_generated_params', function () { request = spec.buildRequests(bidRequestData.bids, bidRequestData); let req_data = JSON.parse(request.data); From 4cd9d5009dc72bcd21fe174c723b0ec49fbeaa9f Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Fri, 26 Jan 2024 07:13:04 -0500 Subject: [PATCH 373/760] appnexus Bid Adapter - DSA support (#10971) --- modules/appnexusBidAdapter.js | 34 ++++++++++++ test/spec/modules/appnexusBidAdapter_spec.js | 58 ++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index c6230d9f1e4..6a0b088795f 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -346,6 +346,36 @@ export const spec = { } } + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + const pubDsaObj = bidderRequest.ortb2.regs.ext.dsa; + const dsaObj = {}; + ['required', 'pubrender', 'datatopub'].forEach((dsaKey) => { + if (isNumber(pubDsaObj[dsaKey])) { + if (dsaKey === 'required') { + // our client-side endpoint has a different name for the 'required' field + // ...this is the only exception to the openrtb spec for these fields + dsaObj.dsainfo = pubDsaObj[dsaKey]; + } else { + dsaObj[dsaKey] = pubDsaObj[dsaKey]; + } + } + }); + + if (isArray(pubDsaObj.transparency) && pubDsaObj.transparency.every((v) => isPlainObject(v))) { + const tpData = []; + pubDsaObj.transparency.forEach((tpObj) => { + if (isStr(tpObj.domain) && tpObj.domain != '' && isArray(tpObj.params) && tpObj.params.every((v) => isNumber(v))) { + tpData.push(tpObj); + } + }); + if (tpData.length > 0) { + dsaObj.transparency = tpData; + } + } + + if (!isEmpty(dsaObj)) payload.dsa = dsaObj; + } + if (tags[0].publisher_id) { payload.publisher_id = tags[0].publisher_id; } @@ -585,6 +615,10 @@ function newBid(serverBid, rtbBid, bidderRequest) { bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); } + if (rtbBid.dsa) { + bid.meta = Object.assign({}, bid.meta, { dsa: rtbBid.dsa }); + } + // temporary function; may remove at later date if/when adserver fully supports dchain function setupDChain(rtbBid) { let dchain = { diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 193e8aa64f8..c44ef475290 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1204,6 +1204,46 @@ describe('AppNexusAdapter', function () { expect(payload.privacy.gpp_sid).to.deep.equal([7]); }); + it('should add dsa information to the request via bidderRequest.ortb2.regs.ext.dsa', function () { + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'ortb2': { + 'regs': { + 'ext': { + 'dsa': { + 'required': 1, + 'pubrender': 0, + 'datatopub': 1, + 'transparency': [{ + 'domain': 'good-domain', + 'params': [1, 2] + }, { + 'domain': 'bad-setup', + 'params': ['1', 3] + }] + } + } + } + } + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.dsa).to.exist; + expect(payload.dsa.dsainfo).to.equal(1); + expect(payload.dsa.pubrender).to.equal(0); + expect(payload.dsa.datatopub).to.equal(1); + expect(payload.dsa.transparency).to.deep.equal([{ + 'domain': 'good-domain', + 'params': [1, 2] + }]); + }); + it('supports sending hybrid mobile app parameters', function () { let appRequest = Object.assign({}, bidRequests[0], @@ -1575,6 +1615,15 @@ describe('AppNexusAdapter', function () { 'viewability': { 'config': '' }, + 'dsa': { + 'behalf': 'test-behalf', + 'paid': 'test-paid', + 'transparency': [{ + 'domain': 'good-domain', + 'params': [1, 2, 3] + }], + 'adrender': 1 + }, 'rtb': { 'banner': { 'content': '', @@ -1623,6 +1672,15 @@ describe('AppNexusAdapter', function () { 'nodes': [{ 'bsid': '958' }] + }, + 'dsa': { + 'behalf': 'test-behalf', + 'paid': 'test-paid', + 'transparency': [{ + 'domain': 'good-domain', + 'params': [1, 2, 3] + }], + 'adrender': 1 } } } From 73310f170975cbb61f82920e35e8262a981d0566 Mon Sep 17 00:00:00 2001 From: Malkov Mikhail Date: Fri, 26 Jan 2024 15:21:25 +0300 Subject: [PATCH 374/760] NextMillennium Bid Adapter : currency support in request (#10871) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * currency * currency --- modules/nextMillenniumBidAdapter.js | 55 ++++++++++++++++--- .../modules/nextMillenniumBidAdapter_spec.js | 22 +++++++- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 5dea4f9b651..0af2129de61 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -31,6 +31,7 @@ const TEST_ENDPOINT = 'https://test.pbs.nextmillmedia.com/openrtb2/auction'; const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&type={{.TYPE_PIXEL}}'; const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; +const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS = [ 'api', @@ -83,9 +84,11 @@ export const spec = { const site = getSiteObj(); const device = getDeviceObj(); + const {cur, mediaTypes} = getCurrency(bid); const postBody = { id: bidderRequest?.bidderRequestId, + cur, ext: { prebid: { storedrequest: { @@ -107,7 +110,7 @@ export const spec = { imp: [], }; - postBody.imp.push(getImp(bid, id)); + postBody.imp.push(getImp(bid, id, mediaTypes)); setConsentStrings(postBody, bidderRequest); setOrtb2Parameters(postBody, bidderRequest?.ortb2); setEids(postBody, bid); @@ -157,7 +160,7 @@ export const spec = { width: bid.w, height: bid.h, creativeId: bid.adid, - currency: response.cur, + currency: response.cur || DEFAULT_CURRENCY, netRevenue: true, ttl: TIME_TO_LIVE, meta: { @@ -244,7 +247,8 @@ export const spec = { }, }; -export function getImp(bid, id) { +export function getImp(bid, id, mediaTypes) { + const {banner, video} = mediaTypes; const imp = { id: bid.adUnitCode, ext: { @@ -256,18 +260,22 @@ export function getImp(bid, id) { }, }; - const banner = deepAccess(bid, 'mediaTypes.banner'); if (banner) { + if (banner.bidfloorcur) imp.bidfloorcur = banner.bidfloorcur; + if (banner.bidfloor) imp.bidfloor = banner.bidfloor; + imp.banner = { - format: (banner?.sizes || []).map(s => { return {w: s[0], h: s[1]} }), + format: (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }), }; }; - const video = deepAccess(bid, 'mediaTypes.video'); if (video) { + if (video.bidfloorcur) imp.bidfloorcur = video.bidfloorcur; + if (video.bidfloor) imp.bidfloor = video.bidfloor; + imp.video = getDefinedParams(video, VIDEO_PARAMS); - if (video.playerSize) { - imp.video = Object.assign(imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize) || {}); + 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; @@ -334,7 +342,36 @@ export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gp .replace('{{.TYPE_PIXEL}}', type); return url; -}; +} + +function getCurrency(bid = {}) { + const currency = config?.getConfig('currency')?.adServerCurrency || DEFAULT_CURRENCY; + const cur = []; + const types = ['banner', 'video']; + const mediaTypes = {}; + for (const mediaType of types) { + const mediaTypeData = deepAccess(bid, `mediaTypes.${mediaType}`); + if (mediaTypeData) { + mediaTypes[mediaType] = {data: mediaTypeData}; + } else { + continue; + }; + + if (typeof bid.getFloor === 'function') { + let floorInfo = bid.getFloor({currency, mediaType, size: '*'}); + mediaTypes[mediaType].bidfloorcur = floorInfo.currency; + mediaTypes[mediaType].bidfloor = floorInfo.floor; + } else { + mediaTypes[mediaType].bidfloorcur = currency; + }; + + if (cur.includes(mediaTypes[mediaType].bidfloorcur)) cur.push(mediaTypes[mediaType].bidfloorcur); + }; + + if (!cur.length) cur.push(DEFAULT_CURRENCY); + + return {cur, mediaTypes}; +} function getAdEl(bid) { // best way I could think of to get El, is by matching adUnitCode to google slots... diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index f57db82aedc..169ab7b01e4 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -19,10 +19,20 @@ describe('nextMillenniumBidAdapterTests', () => { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-banner-1', }, + + mediaTypes: { + banner: { + data: {sizes: [[300, 250], [320, 250]]}, + bidfloorcur: 'EUR', + bidfloor: 1.11, + }, + }, }, expected: { id: 'test-banner-1', + bidfloorcur: 'EUR', + bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, banner: {format: [{w: 300, h: 250}, {w: 320, h: 250}]}, }, @@ -36,10 +46,18 @@ describe('nextMillenniumBidAdapterTests', () => { mediaTypes: {video: {playerSize: [400, 300]}}, adUnitCode: 'test-video-1', }, + + mediaTypes: { + video: { + data: {playerSize: [400, 300]}, + bidfloorcur: 'USD', + }, + }, }, expected: { id: 'test-video-1', + bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, video: {w: 400, h: 300}, }, @@ -48,8 +66,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (let {title, data, expected} of dataTests) { it(title, () => { - const {bid, id} = data; - const imp = getImp(bid, id); + const {bid, id, mediaTypes} = data; + const imp = getImp(bid, id, mediaTypes); expect(imp).to.deep.equal(expected); }); } From f78996d5de015ec11ae1444b32e485f4abf1ba97 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 26 Jan 2024 04:46:55 -0800 Subject: [PATCH 375/760] Core & PBS adapter: fix race condition between network timeout and auction timeout (#10779) * Core: fix race condition between fetch timeout and auction timeout * PBS adapter: fix race condition between ajax timeout and auction timeout --- modules/prebidServerBidAdapter/index.js | 4 +- src/adapterManager.js | 8 +-- src/adapters/bidderFactory.js | 4 +- src/ajax.js | 8 ++- .../modules/prebidServerBidAdapter_spec.js | 26 +++++++++ test/spec/unit/core/adapterManager_spec.js | 53 ++++++++++++------- test/spec/unit/core/ajax_spec.js | 24 +++++++-- test/spec/unit/core/bidderFactory_spec.js | 39 +++++++++++--- 8 files changed, 127 insertions(+), 39 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 5ae9a9fc27a..6e4aec8ad92 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -478,13 +478,13 @@ export function PrebidServer() { adapterMetrics }); } - done(); + done(false); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError(msg, error) { logError(`Prebid server call failed: '${msg}'`, error); bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, {error, bidderRequest})); - done(); + done(error.timedOut); }, onBid: function ({adUnit, bid}) { const metrics = bid.metrics = s2sBidRequest.metrics.fork().renameWith(); diff --git a/src/adapterManager.js b/src/adapterManager.js index 575d28b35fa..189fbf31f2e 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -413,8 +413,10 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); - return function () { - onTimelyResponse(bidRequest.bidderRequestId); + return function (timedOut) { + if (!timedOut) { + onTimelyResponse(bidRequest.bidderRequestId); + } doneCb.apply(bidRequest, arguments); } }); @@ -433,7 +435,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request s2sBidRequest, serverBidderRequests, addBidResponse, - () => doneCbs.forEach(done => done()), + (timedOut) => doneCbs.forEach(done => done(timedOut)), s2sAjax ); } diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index df7b2fb0eea..3d55f2c06af 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -305,7 +305,9 @@ export function newBidder(spec) { }, // If the server responds with an error, there's not much we can do beside logging. onError: (errorMessage, error) => { - onTimelyResponse(spec.code); + if (!error.timedOut) { + onTimelyResponse(spec.code); + } adapterManager.callBidderError(spec.code, error, bidderRequest) events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, { error, bidderRequest }); logError(`Server call for ${spec.code} failed: ${errorMessage} ${error.status}. Continuing without bids.`); diff --git a/src/ajax.js b/src/ajax.js index 6f2f45e935e..ef4c2e4bcb4 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -114,7 +114,8 @@ function toXHR({status, statusText = '', headers, url}, responseText) { getResponseHeader: (header) => headers?.has(header) ? headers.get(header) : null, toJSON() { return Object.assign({responseXML: getXML()}, this) - } + }, + timedOut: false } } @@ -130,7 +131,10 @@ export function attachCallbacks(fetchPm, callback) { .then(([response, responseText]) => { const xhr = toXHR(response, responseText); response.ok || response.status === 304 ? success(responseText, xhr) : error(response.statusText, xhr); - }, () => error('', toXHR({status: 0}, ''))); + }, (reason) => error('', Object.assign( + toXHR({status: 0}, ''), + {reason, timedOut: reason?.name === 'AbortError'})) + ); } export function ajaxBuilder(timeout = 3000, {request, done} = {}) { diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 5b591804b95..d88d4411e33 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -2882,6 +2882,32 @@ describe('S2S Adapter', function () { }) }) + describe('calls done', () => { + let success, error; + beforeEach(() => { + const mockAjax = function (_, callback) { + ({success, error} = callback); + } + config.setConfig({ s2sConfig: CONFIG }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, mockAjax); + }) + + it('passing timedOut = false on succcess', () => { + success({}); + sinon.assert.calledWith(done, false); + }); + + Object.entries({ + 'timeouts': true, + 'other errors': false + }).forEach(([t, timedOut]) => { + it(`passing timedOut = ${timedOut} on ${t}`, () => { + error('', {timedOut}); + sinon.assert.calledWith(done, timedOut); + }) + }) + }) + // TODO: test dependent on pbjs_api_spec. Needs to be isolated it('does not call addBidResponse and calls done when ad unit not set', function () { config.setConfig({ s2sConfig: CONFIG }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 98d841d9c7c..dac70696b4b 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -964,25 +964,42 @@ describe('adapterManager tests', function () { 'start': 1462918897460 }]; - it('invokes callBids on the S2S adapter', function () { - const done = sinon.stub(); - const onTimelyResponse = sinon.stub(); - prebidServerAdapterMock.callBids.callsFake((_1, _2, _3, done) => { - done(); + describe('invokes callBids on the S2S adapter', () => { + let onTimelyResponse, timedOut, done; + beforeEach(() => { + done = sinon.stub(); + onTimelyResponse = sinon.stub(); + prebidServerAdapterMock.callBids.callsFake((_1, _2, _3, done) => { + done(timedOut); + }); + }) + + function runTest() { + adapterManager.callBids( + getAdUnits(), + bidRequests, + () => {}, + done, + undefined, + undefined, + onTimelyResponse + ); + sinon.assert.calledTwice(prebidServerAdapterMock.callBids); + sinon.assert.calledTwice(done); + } + + it('and marks requests as timely if the adapter says timedOut = false', function () { + timedOut = false; + runTest(); + bidRequests.forEach(br => sinon.assert.calledWith(onTimelyResponse, br.bidderRequestId)); }); - adapterManager.callBids( - getAdUnits(), - bidRequests, - () => {}, - done, - undefined, - undefined, - onTimelyResponse - ); - sinon.assert.calledTwice(prebidServerAdapterMock.callBids); - sinon.assert.calledTwice(done); - bidRequests.forEach(br => sinon.assert.calledWith(onTimelyResponse, br.bidderRequestId)); - }); + + it('and does NOT mark them as timely if it says timedOut = true', () => { + timedOut = true; + runTest(); + sinon.assert.notCalled(onTimelyResponse); + }) + }) // Enable this test when prebidServer adapter is made 1.0 compliant it('invokes callBids with only s2s bids', function () { diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js index a3a0459b980..dd03ad1a761 100644 --- a/test/spec/unit/core/ajax_spec.js +++ b/test/spec/unit/core/ajax_spec.js @@ -232,7 +232,7 @@ describe('attachCallbacks', () => { }; } - function expectNullXHR(response) { + function expectNullXHR(response, reason) { return new Promise((resolve, reject) => { attachCallbacks(Promise.resolve(response), { success: () => { @@ -246,7 +246,8 @@ describe('attachCallbacks', () => { statusText: '', responseText: '', response: '', - responseXML: null + responseXML: null, + reason }); expect(xhr.getResponseHeader('any')).to.be.null; resolve(); @@ -256,9 +257,21 @@ describe('attachCallbacks', () => { } it('runs error callback on rejections', () => { - return expectNullXHR(Promise.reject(new Error())); + const err = new Error(); + return expectNullXHR(Promise.reject(err), err); }); + it('sets timedOut = true on fetch timeout', (done) => { + const ctl = new AbortController(); + ctl.abort(); + attachCallbacks(fetch('/', {signal: ctl.signal}), { + error(_, xhr) { + expect(xhr.timedOut).to.be.true; + done(); + } + }); + }) + Object.entries({ '2xx response': { success: true, @@ -368,8 +381,9 @@ describe('attachCallbacks', () => { }); it(`runs error callback if body cannot be retrieved`, () => { - response.text = () => Promise.reject(new Error()); - return expectNullXHR(response); + const err = new Error(); + response.text = () => Promise.reject(err); + return expectNullXHR(response, err); }); if (success) { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 9dcdb627698..16042ca52dd 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -38,10 +38,6 @@ const MOCK_BIDS_REQUEST = { ] } -function onTimelyResponseStub() { - -} - before(() => { hook.ready(); }); @@ -49,6 +45,10 @@ before(() => { let wrappedCallback = config.callbackWithBidder(CODE); describe('bidderFactory', () => { + let onTimelyResponseStub; + beforeEach(() => { + onTimelyResponseStub = sinon.stub(); + }) describe('bidders created by newBidder', function () { let spec; let bidder; @@ -582,6 +582,14 @@ describe('bidderFactory', () => { utils.logError.restore(); }); + it('should call onTimelyResponse', () => { + const bidder = newBidder(spec); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({method: 'POST', url: 'test', data: {}}); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.called(onTimelyResponseStub); + }) + it('should call spec.interpretResponse() with the response content', function () { const bidder = newBidder(spec); @@ -800,12 +808,13 @@ describe('bidderFactory', () => { let ajaxStub; let callBidderErrorStub; let eventEmitterStub; - let xhrErrorMock = { - status: 500, - statusText: 'Internal Server Error' - }; + let xhrErrorMock; beforeEach(function () { + xhrErrorMock = { + status: 500, + statusText: 'Internal Server Error' + }; ajaxStub = sinon.stub(ajax, 'ajax').callsFake(function(url, callbacks) { callbacks.error('ajax call failed.', xhrErrorMock); }); @@ -821,6 +830,20 @@ describe('bidderFactory', () => { eventEmitterStub.restore(); }); + Object.entries({ + 'timeouts': true, + 'other errors': false + }).forEach(([t, timedOut]) => { + it(`should ${timedOut ? 'NOT ' : ''}call onTimelyResponse on ${t}`, () => { + Object.assign(xhrErrorMock, {timedOut}); + const bidder = newBidder(spec); + spec.isBidRequestValid.returns(true); + spec.buildRequests.returns({method: 'POST', url: 'test', data: {}}); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert[timedOut ? 'notCalled' : 'called'](onTimelyResponseStub); + }) + }) + it('should not spec.interpretResponse()', function () { const bidder = newBidder(spec); From 3ab9a57749f172df25c40294e08e13315bc24b99 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 26 Jan 2024 14:00:28 +0000 Subject: [PATCH 376/760] Prebid 8.33.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bac5bc0c929..d575b074b0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.33.0-pre", + "version": "8.33.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 70b0bd4a144..8694938e05e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.33.0-pre", + "version": "8.33.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 904359e1542a08c4b436e9007cec1b9fdb39389d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 26 Jan 2024 14:00:28 +0000 Subject: [PATCH 377/760] Increment version to 8.34.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d575b074b0d..87110627d26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.33.0", + "version": "8.34.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 8694938e05e..38697197d0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.33.0", + "version": "8.34.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 0dc3d5972d4239814aae2b256d1fdbdfca858c9f Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:05:26 +0800 Subject: [PATCH 378/760] Discovery Bid Adapter : synchronize mguid from third party cookie to first party cookie (#10927) * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test resolve conflict * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test * Discovery Bid Adapter : synchronize mguid from third party cookie to first party cookie --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 31 +++++++-- test/spec/modules/discoveryBidAdapter_spec.js | 68 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 1576a8dcb08..b9ba6087d30 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -13,10 +13,11 @@ const MEDIATYPE = [BANNER, NATIVE]; /* ----- _ss_pp_id:start ------ */ const COOKIE_KEY_SSPPID = '_ss_pp_id'; -const COOKIE_KEY_MGUID = '__mguid_'; +export const COOKIE_KEY_MGUID = '__mguid_'; const COOKIE_KEY_PMGUID = '__pmguid_'; const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; +export const THIRD_PARTY_COOKIE_ORIGIN = 'https://asset.popin.cc'; const NATIVERET = { id: 'id', @@ -126,10 +127,8 @@ export const getPmgUID = () => { let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); if (!pmgUid) { pmgUid = utils.generateUUID(); - const date = new Date(); - date.setTime(date.getTime() + COOKIE_RETENTION_TIME); try { - storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, date.toUTCString()); + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); } catch (e) {} } return pmgUid; @@ -295,6 +294,16 @@ function getReferrer(bidRequest = {}, bidderRequest = {}) { return pageUrl; } +/** + * get current time to UTC string + * @returns utc string + */ +export function getCurrentTimeToUTCString() { + const date = new Date(); + date.setTime(date.getTime() + COOKIE_RETENTION_TIME); + return date.toUTCString(); +} + /** * format imp ad test ext params * @@ -642,6 +651,20 @@ export const spec = { } if (syncOptions.iframeEnabled) { + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin != THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + this.removeEventListener('message', handler); + + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + }, true); return [ { type: 'iframe', diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 65fbf33022f..961ccb33c4f 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,5 +1,16 @@ import { expect } from 'chai'; -import { spec, getPmgUID, storage, getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from 'modules/discoveryBidAdapter.js'; +import { + spec, + getPmgUID, + storage, + getPageTitle, + getPageDescription, + getPageKeywords, + getConnectionDownLink, + THIRD_PARTY_COOKIE_ORIGIN, + COOKIE_KEY_MGUID, + getCurrentTimeToUTCString +} from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { @@ -523,5 +534,60 @@ describe('discovery Bid Adapter Tests', function () { expect(result).to.be.undefined; }); }); + + describe('getUserSyncs with message event listener', function() { + function messageHandler(event) { + if (!event.data || event.origin !== THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + window.removeEventListener('message', messageHandler, true); + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + } + + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(window, 'removeEventListener'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should set a cookie when a valid message is received', () => { + const fakeEvent = { + data: { optout: '', mguid: '12345' }, + origin: THIRD_PARTY_COOKIE_ORIGIN, + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.calledOnce).to.be.true; + expect(window.removeEventListener.calledWith('message', messageHandler, true)).to.be.true; + expect(storage.setCookie.calledWith(COOKIE_KEY_MGUID, '12345', sinon.match.string)).to.be.true; + }); + it('should not do anything when an invalid message is received', () => { + const fakeEvent = { + data: null, + origin: 'http://invalid-origin.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true; + expect(window.removeEventListener.notCalled).to.be.true; + expect(storage.setCookie.notCalled).to.be.true; + }); + }); }); }); From e37ba4e70fed642b3f57121566073b6e11f23fd4 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Fri, 26 Jan 2024 13:52:00 -0700 Subject: [PATCH 379/760] JSdoc Lint Fixes for Multiple Adapters (#10967) * fix native * update ablida adgeneration * fix adkernel admatic * update type imports * js lint fixes * jslint appnexus * jslint jsdoc fixes * fix jsdoc warnings * lint fixes * lint fix * jsdoc updates * Update adgenerationBidAdapter.js * fix js doc * jsdoc type imports * jsdoc type updates * add types for jsdoc * jsdoc type updates * add types * jsdoc types added * import types * jsdoc updates * jsdoc type imports * type jsdoc import fixes * jsdoc type imports * jsdoc type import fix * lint fixes * fix jsdoc types * type imports * lint fixes jsdoc * jsdoc fixes * type imports for jsdoc * jsdoc type fixes * fxes for jsdoc types * jsdoc fixes --- modules/ablidaBidAdapter.js | 6 ++++++ modules/adgenerationBidAdapter.js | 8 ++++++++ modules/adkernelBidAdapter.js | 8 ++++++-- modules/admaticBidAdapter.js | 7 +++++++ modules/admediaBidAdapter.js | 6 ++++++ modules/adnowBidAdapter.js | 4 ++++ modules/adqueryBidAdapter.js | 8 ++++++++ modules/adrelevantisBidAdapter.js | 5 +++++ modules/adtelligentBidAdapter.js | 4 ++++ modules/aduptechBidAdapter.js | 5 +++++ modules/adyoulikeBidAdapter.js | 5 +++++ modules/ajaBidAdapter.js | 5 +++++ modules/appierBidAdapter.js | 5 +++++ modules/appnexusBidAdapter.js | 5 +++++ modules/astraoneBidAdapter.js | 6 ++++++ modules/audiencerunBidAdapter.js | 9 +++++++++ modules/betweenBidAdapter.js | 8 ++++++++ modules/biddoBidAdapter.js | 5 +++++ modules/bidglassBidAdapter.js | 7 ++++++- modules/big-richmediaBidAdapter.js | 5 +++++ modules/braveBidAdapter.js | 5 +++++ modules/bridBidAdapter.js | 6 ++++++ modules/bridgewellBidAdapter.js | 5 +++++ modules/browsiBidAdapter.js | 5 +++++ modules/bucksenseBidAdapter.js | 5 +++++ modules/buzzoolaBidAdapter.js | 6 ++++++ modules/c1xBidAdapter.js | 4 ++++ modules/clickforceBidAdapter.js | 6 ++++++ modules/codefuelBidAdapter.js | 8 ++++++++ modules/cointrafficBidAdapter.js | 7 +++++++ modules/coinzillaBidAdapter.js | 6 ++++++ modules/colossussspBidAdapter.js | 5 +++++ modules/concertBidAdapter.js | 7 +++++++ modules/consumableBidAdapter.js | 6 ++++++ modules/conversantBidAdapter.js | 7 +++++++ modules/criteoBidAdapter.js | 8 ++++++++ modules/cwireBidAdapter.js | 6 ++++++ modules/datawrkzBidAdapter.js | 5 +++++ modules/discoveryBidAdapter.js | 6 ++++++ modules/dspxBidAdapter.js | 4 ++++ modules/dxkultureBidAdapter.js | 5 +++++ modules/eskimiBidAdapter.js | 4 ++++ modules/feedadBidAdapter.js | 9 +++++++++ modules/flippBidAdapter.js | 8 ++++++++ modules/fluctBidAdapter.js | 5 +++++ modules/freewheel-sspBidAdapter.js | 5 +++++ modules/gammaBidAdapter.js | 5 +++++ modules/getintentBidAdapter.js | 5 +++++ modules/gjirafaBidAdapter.js | 7 +++++++ modules/gmosspBidAdapter.js | 9 +++++++++ modules/gnetBidAdapter.js | 6 ++++++ modules/goldbachBidAdapter.js | 5 +++++ modules/gothamadsBidAdapter.js | 5 +++++ modules/gridBidAdapter.js | 6 ++++++ modules/gumgumBidAdapter.js | 9 +++++++++ modules/hybridBidAdapter.js | 7 +++++++ modules/impactifyBidAdapter.js | 8 ++++++++ modules/improvedigitalBidAdapter.js | 8 ++++++++ modules/incrxBidAdapter.js | 6 ++++++ modules/instreamTracking.js | 6 ++++++ modules/integr8BidAdapter.js | 7 +++++++ modules/invamiaBidAdapter.js | 5 +++++ modules/invibesBidAdapter.js | 5 +++++ modules/iqmBidAdapter.js | 4 ++++ modules/ivsBidAdapter.js | 7 +++++++ modules/lemmaDigitalBidAdapter.js | 9 +++++++++ modules/lifestreetBidAdapter.js | 4 ++++ modules/limelightDigitalBidAdapter.js | 6 ++++++ modules/livewrappedBidAdapter.js | 5 +++++ modules/lm_kiviadsBidAdapter.js | 8 ++++++++ modules/madvertiseBidAdapter.js | 5 +++++ modules/malltvBidAdapter.js | 7 +++++++ modules/mediaforceBidAdapter.js | 6 ++++++ modules/mediafuseBidAdapter.js | 5 +++++ modules/mediagoBidAdapter.js | 7 +++++++ modules/mediakeysBidAdapter.js | 2 +- modules/medianetBidAdapter.js | 6 ++++++ modules/mediasniperBidAdapter.js | 2 +- modules/mediasquareBidAdapter.js | 9 +++++++++ modules/mgidBidAdapter.js | 6 ++++++ modules/missenaBidAdapter.js | 7 +++++++ modules/nextrollBidAdapter.js | 6 ++++++ modules/nexx360BidAdapter.js | 9 +++++++++ modules/nobidBidAdapter.js | 9 +++++++++ modules/onetagBidAdapter.js | 5 +++++ modules/operaadsBidAdapter.js | 7 +++++++ modules/optidigitalBidAdapter.js | 9 +++++++++ modules/orbidderBidAdapter.js | 4 ++++ modules/pilotxBidAdapter.js | 8 ++++++++ modules/prismaBidAdapter.js | 9 +++++++++ modules/pubmaticBidAdapter.js | 6 ++++++ modules/pubwiseBidAdapter.js | 7 +++++++ modules/pxyzBidAdapter.js | 5 +++++ modules/quantcastBidAdapter.js | 5 +++++ modules/radsBidAdapter.js | 4 ++++ modules/retailspotBidAdapter.js | 7 ++++++- modules/rtbsapeBidAdapter.js | 8 ++++++++ modules/rubiconBidAdapter.js | 4 ++++ modules/seedtagBidAdapter.js | 9 +++++++++ modules/slimcutBidAdapter.js | 7 +++++++ modules/smaatoBidAdapter.js | 8 ++++++++ modules/smartadserverBidAdapter.js | 6 ++++++ modules/smartxBidAdapter.js | 6 ++++++ modules/smilewantedBidAdapter.js | 5 +++++ modules/sonobiBidAdapter.js | 6 ++++++ modules/sovrnBidAdapter.js | 4 ++++ modules/sparteoBidAdapter.js | 4 ++++ modules/spotxBidAdapter.js | 6 ++++++ modules/stvBidAdapter.js | 4 ++++ modules/sublimeBidAdapter.js | 6 ++++++ modules/tappxBidAdapter.js | 4 ++++ modules/targetVideoBidAdapter.js | 5 +++++ modules/teadsBidAdapter.js | 5 +++++ modules/temedyaBidAdapter.js | 8 ++++++++ modules/theAdxBidAdapter.js | 9 +++++++++ modules/tpmnBidAdapter.js | 4 ++++ modules/ttdBidAdapter.js | 9 +++++++++ modules/ucfunnelBidAdapter.js | 6 ++++++ modules/unicornBidAdapter.js | 5 +++++ modules/vdoaiBidAdapter.js | 6 ++++++ modules/vibrantmediaBidAdapter.js | 7 +++++++ modules/videobyteBidAdapter.js | 8 ++++++++ modules/videoheroesBidAdapter.js | 5 +++++ modules/videonowBidAdapter.js | 6 ++++++ modules/viqeoBidAdapter.js | 8 ++++++++ modules/voxBidAdapter.js | 7 +++++++ modules/welectBidAdapter.js | 7 +++++++ modules/winrBidAdapter.js | 5 +++++ modules/xeBidAdapter.js | 8 ++++++++ modules/yieldlabBidAdapter.js | 9 +++++++++ modules/yieldmoBidAdapter.js | 7 +++++++ modules/zetaBidAdapter.js | 8 ++++++++ modules/zeta_global_sspBidAdapter.js | 6 ++++++ src/native.js | 5 +++++ 134 files changed, 822 insertions(+), 6 deletions(-) diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js index 805a2020fb4..175d5ff7c72 100644 --- a/modules/ablidaBidAdapter.js +++ b/modules/ablidaBidAdapter.js @@ -3,6 +3,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'ablida'; const ENDPOINT_URL = 'https://bidder.ablida.net/prebid'; diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js index b40378c8e35..e0538fe2815 100644 --- a/modules/adgenerationBidAdapter.js +++ b/modules/adgenerationBidAdapter.js @@ -6,6 +6,14 @@ import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; import {escapeUnsafeChars} from '../libraries/htmlEscape/htmlEscape.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const ADG_BIDDER_CODE = 'adgeneration'; export const spec = { diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index add24772463..d6a4030057a 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -23,12 +23,16 @@ import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; -/* +/** * In case you're AdKernel whitelable platform's client who needs branded adapter to * work with Adkernel platform - DO NOT COPY THIS ADAPTER UNDER NEW NAME * - * Please contact prebid@adkernel.com and we'll add your adapter as an alias. + * Please contact prebid@adkernel.com and we'll add your adapter as an alias + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ + const VIDEO_PARAMS = ['pos', 'context', 'placement', 'plcmt', 'api', 'mimes', 'protocols', 'playbackmethod', 'minduration', 'maxduration', 'startdelay', 'linearity', 'skip', 'skipmin', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackend', 'boxingallowed']; const VIDEO_FPD = ['battr', 'pos']; diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 5afe7781d7d..3f87476def7 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -2,6 +2,13 @@ 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'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + export const OPENRTB = { NATIVE: { IMAGE_TYPE: { diff --git a/modules/admediaBidAdapter.js b/modules/admediaBidAdapter.js index 42593a36159..5ea3e27b0d9 100644 --- a/modules/admediaBidAdapter.js +++ b/modules/admediaBidAdapter.js @@ -1,6 +1,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; 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 + */ + const BIDDER_CODE = 'admedia'; const ENDPOINT_URL = 'https://prebid.admedia.com/bidder/'; diff --git a/modules/adnowBidAdapter.js b/modules/adnowBidAdapter.js index f83dbf68a1f..99f56df58b2 100644 --- a/modules/adnowBidAdapter.js +++ b/modules/adnowBidAdapter.js @@ -9,6 +9,10 @@ const ENDPOINT = 'https://n.ads3-adnow.com/a'; /** * @typedef {object} CommonBidData + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec * * @property {string} requestId The specific BidRequest which this bid is aimed at. * This should match the BidRequest.bidId which this Bid targets. diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index 6b0d642468b..d5a8ba6da84 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -2,6 +2,14 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {buildUrl, logInfo, parseSizesInput, triggerPixel} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + const ADQUERY_GVLID = 902; const ADQUERY_BIDDER_CODE = 'adquery'; const ADQUERY_BIDDER_DOMAIN_PROTOCOL = 'https'; diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 3c9c661b09c..68cd859e24e 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -23,6 +23,11 @@ import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'adrelevantis'; const URL = 'https://ssp.adrelevantis.com/prebid'; const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 8e6aeecdd75..cadba499b5c 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -7,6 +7,10 @@ import {find} from '../src/polyfill.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const subdomainSuffixes = ['', 1, 2]; const AUCTION_PATH = '/v2/auction/'; const PROTOCOL = 'https://'; diff --git a/modules/aduptechBidAdapter.js b/modules/aduptechBidAdapter.js index 49187da2fe2..fdc1249ded4 100644 --- a/modules/aduptechBidAdapter.js +++ b/modules/aduptechBidAdapter.js @@ -4,6 +4,11 @@ import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + */ + export const BIDDER_CODE = 'aduptech'; export const GVLID = 647; export const ENDPOINT_URL_PUBLISHER_PLACEHOLDER = '{PUBLISHER}'; diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 8952c3ae2b9..9bc24b11ac3 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -5,6 +5,11 @@ import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const VERSION = '1.0'; const BIDDER_CODE = 'adyoulike'; const DEFAULT_DC = 'hb-api'; diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index 9e2d4efb5ff..fcbe0c12c6e 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -3,6 +3,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + const BidderCode = 'aja'; const URL = 'https://ad.as.amanad.adtdp.com/v2/prebid'; const SDKType = 5; diff --git a/modules/appierBidAdapter.js b/modules/appierBidAdapter.js index 12346d15130..fa314f0bd5f 100644 --- a/modules/appierBidAdapter.js +++ b/modules/appierBidAdapter.js @@ -2,6 +2,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + export const ADAPTER_VERSION = '1.0.0'; const SUPPORTED_AD_TYPES = [BANNER]; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 6a0b088795f..e62824cb2aa 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -38,6 +38,11 @@ import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js index d7f92bb5fac..9645b8e68bd 100644 --- a/modules/astraoneBidAdapter.js +++ b/modules/astraoneBidAdapter.js @@ -2,6 +2,12 @@ import { _map } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' 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 + */ + const BIDDER_CODE = 'astraone'; const SSP_ENDPOINT = 'https://ssp.astraone.io/auction/prebid'; const TTL = 60; diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index e716fe94c8b..92a4343b3ed 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -12,6 +12,15 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; 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').ServerRequest} ServerRequest + * @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 = 'audiencerun'; const BASE_URL = 'https://d.audiencerun.com'; const AUCTION_URL = `${BASE_URL}/prebid`; diff --git a/modules/betweenBidAdapter.js b/modules/betweenBidAdapter.js index 6883b7cce2c..4a953875d99 100644 --- a/modules/betweenBidAdapter.js +++ b/modules/betweenBidAdapter.js @@ -3,6 +3,14 @@ import {parseSizesInput} from '../src/utils.js'; import {includes} from '../src/polyfill.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'between'; let ENDPOINT = 'https://ads.betweendigital.com/adjson?t=prebid'; const CODE_TYPES = ['inpage', 'preroll', 'midroll', 'postroll']; diff --git a/modules/biddoBidAdapter.js b/modules/biddoBidAdapter.js index 5512ca60f8e..cf39c572629 100644 --- a/modules/biddoBidAdapter.js +++ b/modules/biddoBidAdapter.js @@ -1,6 +1,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'biddo'; const ENDPOINT_URL = 'https://ad.adopx.net/delivery/impress'; diff --git a/modules/bidglassBidAdapter.js b/modules/bidglassBidAdapter.js index a29976cfcb7..6074d1a0e4c 100644 --- a/modules/bidglassBidAdapter.js +++ b/modules/bidglassBidAdapter.js @@ -1,7 +1,12 @@ import {_each, isArray, deepClone, getUniqueIdentifierStr, getBidIdParameter} from '../src/utils.js'; -// import {config} from 'src/config.js'; 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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'bidglass'; export const spec = { diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index 8a03aac1ace..ecb1724c2a1 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -3,6 +3,11 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {spec as baseAdapter} from './appnexusBidAdapter.js'; // eslint-disable-line prebid/validate-imports +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'big-richmedia'; const metadataByRequestId = {}; diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index d954522ae24..4c5448482db 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -4,6 +4,11 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'brave'; const DEFAULT_CUR = 'USD'; const ENDPOINT_URL = `https://point.bravegroup.tv/?t=2&partner=hash`; diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index 8e7c2f166ef..e784ea517ac 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -3,6 +3,12 @@ import {VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.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 SOURCE = 'pbjs'; const BIDDER_CODE = 'brid'; const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; diff --git a/modules/bridgewellBidAdapter.js b/modules/bridgewellBidAdapter.js index 6088cefaa55..578acf8a358 100644 --- a/modules/bridgewellBidAdapter.js +++ b/modules/bridgewellBidAdapter.js @@ -4,6 +4,11 @@ import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'bridgewell'; const REQUEST_ENDPOINT = 'https://prebid.scupio.com/recweb/prebid.aspx?cb='; const BIDDER_VERSION = '1.1.0'; diff --git a/modules/browsiBidAdapter.js b/modules/browsiBidAdapter.js index 03b6b2a8f3d..fa1cacaa568 100644 --- a/modules/browsiBidAdapter.js +++ b/modules/browsiBidAdapter.js @@ -3,6 +3,11 @@ import {config} from '../src/config.js'; import {VIDEO} from '../src/mediaTypes.js'; import {logError, logInfo, isArray, isStr} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'browsi'; const DATA = 'brwvidtag'; const ADAPTER = '__bad'; diff --git a/modules/bucksenseBidAdapter.js b/modules/bucksenseBidAdapter.js index ecf27de997d..5aa14f2a53b 100644 --- a/modules/bucksenseBidAdapter.js +++ b/modules/bucksenseBidAdapter.js @@ -2,6 +2,11 @@ import { logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const WHO = 'BKSHBID-005'; const BIDDER_CODE = 'bucksense'; const URL = 'https://directo.prebidserving.com/prebidjs/'; diff --git a/modules/buzzoolaBidAdapter.js b/modules/buzzoolaBidAdapter.js index b5ea6227f58..14a26203484 100644 --- a/modules/buzzoolaBidAdapter.js +++ b/modules/buzzoolaBidAdapter.js @@ -5,6 +5,12 @@ import {Renderer} from '../src/Renderer.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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'buzzoola'; const ENDPOINT = 'https://exchange.buzzoola.com/ssp/prebidjs'; const RENDERER_SRC = 'https://tube.buzzoola.com/new/build/buzzlibrary.js'; diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js index 8c9407825ba..79ba8cf499d 100644 --- a/modules/c1xBidAdapter.js +++ b/modules/c1xBidAdapter.js @@ -2,6 +2,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { logInfo, logError } from '../src/utils.js'; import { BANNER } from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'c1x'; const URL = 'https://hb-stg.c1exchange.com/ht'; // const PIXEL_ENDPOINT = '//px.c1exchange.com/pubpixel/'; diff --git a/modules/clickforceBidAdapter.js b/modules/clickforceBidAdapter.js index 92bc9b1bad2..be81ff1885c 100644 --- a/modules/clickforceBidAdapter.js +++ b/modules/clickforceBidAdapter.js @@ -2,6 +2,12 @@ import { _each } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'clickforce'; const ENDPOINT_URL = 'https://ad.holmesmind.com/adserver/prebid.json?cb=' + new Date().getTime() + '&hb=1&ver=1.21'; diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index 4fef3fb2494..a289e29bd19 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -2,6 +2,14 @@ import {deepAccess, isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; 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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'codefuel'; const CURRENCY = 'USD'; diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index 380e1f5fc77..3b90529b6cc 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -3,6 +3,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.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').BidderSpec} BidderSpec + */ + const BIDDER_CODE = 'cointraffic'; const ENDPOINT_URL = 'https://apps-pbd.ctraffic.io/pb/tmp'; const DEFAULT_CURRENCY = 'EUR'; diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index c7d8fa5797c..9ae2c74547d 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -1,6 +1,12 @@ import { parseSizesInput } from '../src/utils.js'; 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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'coinzilla'; const ENDPOINT_URL = 'https://request.czilladx.com/serve/request.php'; diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index b1ee8875422..cc3e452f20c 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -5,6 +5,11 @@ import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'colossusssp'; const G_URL = 'https://colossusssp.com/?c=o&m=multi'; const G_URL_SYNC = 'https://sync.colossusssp.com'; diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index bc2c4555c54..bd738a39bba 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -3,6 +3,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.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 = 'concert'; const CONCERT_ENDPOINT = 'https://bids.concert.io'; diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 696549a67dc..30b081e53d3 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -3,6 +3,12 @@ import {config} from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; 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').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'consumable'; const BASE_URI = 'https://e.serverbid.com/api/v2'; diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index c338a6633b5..dffc1a9f33b 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -21,6 +21,13 @@ import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; // Maintainer: mediapsr@epsilon.com +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').Device} Device + */ + const GVLID = 24; const BIDDER_CODE = 'conversant'; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 2fbf3d9ab9e..a8a1416e1f3 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -11,6 +11,14 @@ import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; 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').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + const GVLID = 91; export const ADAPTER_VERSION = 36; const BIDDER_CODE = 'criteo'; diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index f158e16a64e..d36948d162d 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -4,6 +4,12 @@ import {BANNER} from '../src/mediaTypes.js'; import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.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 + */ + // ------------------------------------ const BIDDER_CODE = 'cwire'; const CWID_KEY = 'cw_cwid'; diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index 127e7893ec5..db795c89155 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -15,6 +15,11 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import CONSTANTS from '../src/constants.json'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'datawrkz'; const ALIASES = []; const ENDPOINT_URL = 'https://at.datawrkz.com/exchange/openrtb23/'; diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index b9ba6087d30..ad8f5616d44 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -3,6 +3,12 @@ import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } 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 + */ + const BIDDER_CODE = 'discovery'; const ENDPOINT_URL = 'https://rtb-jp.mediago.io/api/bid?tn='; const TIME_TO_LIVE = 500; diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index b8e812f581a..b4490095894 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -4,6 +4,10 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {includes} from '../src/polyfill.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; diff --git a/modules/dxkultureBidAdapter.js b/modules/dxkultureBidAdapter.js index 282b54e0823..c167baef6ea 100644 --- a/modules/dxkultureBidAdapter.js +++ b/modules/dxkultureBidAdapter.js @@ -12,6 +12,11 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'dxkulture'; const DEFAULT_BID_TTL = 300; const DEFAULT_NET_REVENUE = true; diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index 81b8c5d8058..ce01abb9e71 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -4,6 +4,10 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; import {getBidIdParameter} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'eskimi'; // const ENDPOINT = 'https://hb.eskimi.com/bids' const ENDPOINT = 'https://sspback.eskimi.com/bid-request' diff --git a/modules/feedadBidAdapter.js b/modules/feedadBidAdapter.js index 7b41f0fcc03..e68a932b726 100644 --- a/modules/feedadBidAdapter.js +++ b/modules/feedadBidAdapter.js @@ -3,6 +3,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; 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').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 + */ + /** * Version of the FeedAd bid adapter * @type {string} diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index ea5e71ad81d..0708c90ac0d 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -3,6 +3,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const NETWORK_ID = 10922; const AD_TYPES = [4309, 641]; const DTX_TYPES = [5061]; diff --git a/modules/fluctBidAdapter.js b/modules/fluctBidAdapter.js index b566769c00e..f6d97fa7cd8 100644 --- a/modules/fluctBidAdapter.js +++ b/modules/fluctBidAdapter.js @@ -2,6 +2,11 @@ import { _each, deepSetValue, isEmpty } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'fluct'; const END_POINT = 'https://hb.adingo.jp/prebid'; const VERSION = '1.2'; diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index 73c56160b00..c4653181fd0 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -3,6 +3,11 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'freewheel-ssp'; const GVL_ID = 285; diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 279eb78812e..40abdd81930 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -1,5 +1,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const ENDPOINT = 'https://hb.gammaplatform.com'; const ENDPOINT_USERSYNC = 'https://cm-supply-web.gammaplatform.com'; const BIDDER_CODE = 'gamma'; diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index 8cffccb9f30..7353a1c1f67 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -1,6 +1,11 @@ import {getBidIdParameter, isFn, isInteger} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'getintent'; const IS_NET_REVENUE = true; const BID_HOST = 'px.adhigh.net'; diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js index 86cbb1d89de..9259010ac78 100644 --- a/modules/gjirafaBidAdapter.js +++ b/modules/gjirafaBidAdapter.js @@ -2,6 +2,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'gjirafa'; const ENDPOINT_URL = 'https://central.gjirafa.com/bid'; const DIMENSION_SEPARATOR = 'x'; diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index 559f9f77aaf..c4b8cd819e0 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -12,6 +12,15 @@ import {config} from '../src/config.js'; import {BANNER} from '../src/mediaTypes.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'gmossp'; const ENDPOINT = 'https://sp.gmossp-sp.jp/hb/prebid/query.ad'; diff --git a/modules/gnetBidAdapter.js b/modules/gnetBidAdapter.js index 38e96c183b9..4718438b9bb 100644 --- a/modules/gnetBidAdapter.js +++ b/modules/gnetBidAdapter.js @@ -4,6 +4,12 @@ import { BANNER } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; 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').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'gnet'; const ENDPOINT = 'https://service.gnetrtb.com/api'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 8892df130df..ff394e69e54 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -30,6 +30,11 @@ import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'goldbach'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const PRICING_URL = 'https://templates.da-services.ch/01_universal/burda_prebid/1.0/json/sizeCPMMapping.json'; diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index 9f44a54460f..ab59c6febec 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -4,6 +4,11 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'gothamads'; const ACCOUNTID_MACROS = '[account_id]'; const URL_ENDPOINT = `https://us-e-node1.gothamads.com/bid?pass=${ACCOUNTID_MACROS}&integration=prebidjs`; diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 9673633a0fe..b2350c0a61c 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -16,6 +16,12 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; 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').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'grid'; const ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson'; const USP_DELETE_DATA_HANDLER = 'https://media.grid.bidswitch.net/uspapi_delete_c2s' diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index deee906298e..15af94494a3 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -6,6 +6,15 @@ import {getStorageManager} from '../src/storageManager.js'; import {includes} from '../src/polyfill.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'gumgum'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); const ALIAS_BIDDER_CODE = ['gg']; diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index f746e69cbba..00e109516e5 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -5,6 +5,13 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.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 = 'hybrid'; const DSP_ENDPOINT = 'https://hbe198.hybrid.ai/prebidhb'; const TRAFFIC_TYPE_WEB = 1; diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 4c0b9801a36..ea446bd150d 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -6,6 +6,14 @@ import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'impactify'; const BIDDER_ALIAS = ['imp']; const DEFAULT_CURRENCY = 'USD'; diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index b563faf52ac..3a258dfa327 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -7,6 +7,14 @@ import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {loadExternalScript} from '../src/adloader.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'improvedigital'; const CREATIVE_TTL = 300; diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 46be7fb75d6..9b939aff11b 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -2,6 +2,12 @@ import { parseSizesInput, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; 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 + */ + const BIDDER_CODE = 'incrementx'; const ENDPOINT_URL = 'https://hb.incrementxserv.com/vzhbidder/bid'; const DEFAULT_CURRENCY = 'USD'; diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index ff8305c7fed..ece556d0fd2 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -5,6 +5,12 @@ import { INSTREAM } from '../src/video.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json' +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').AdUnit} AdUnit + */ + const {CACHE_ID, UUID} = CONSTANTS.TARGETING_KEYS; const {BID_WON, AUCTION_END} = CONSTANTS.EVENTS; const {RENDERED} = CONSTANTS.BID_STATUS; diff --git a/modules/integr8BidAdapter.js b/modules/integr8BidAdapter.js index 79c6533d122..949483ea7bf 100644 --- a/modules/integr8BidAdapter.js +++ b/modules/integr8BidAdapter.js @@ -3,6 +3,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'integr8'; const DEFAULT_ENDPOINT_URL = 'https://central.sea.integr8.digital/bid'; const DIMENSION_SEPARATOR = 'x'; diff --git a/modules/invamiaBidAdapter.js b/modules/invamiaBidAdapter.js index 2d36fb77e16..96af163ca4f 100644 --- a/modules/invamiaBidAdapter.js +++ b/modules/invamiaBidAdapter.js @@ -1,6 +1,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'invamia'; const ENDPOINT_URL = 'https://ad.invamia.com/delivery/impress'; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 1d608d7136b..2c37c0edad9 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -2,6 +2,11 @@ import {logInfo} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const CONSTANTS = { BIDDER_CODE: 'invibes', BID_ENDPOINT: '.videostep.com/Bid/VideoAdContent', diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js index c3808afd225..c94a88748a7 100644 --- a/modules/iqmBidAdapter.js +++ b/modules/iqmBidAdapter.js @@ -3,6 +3,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM} from '../src/video.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'iqm'; const VERSION = 'v.1.0.0'; const VIDEO_ORTB_PARAMS = [ diff --git a/modules/ivsBidAdapter.js b/modules/ivsBidAdapter.js index 6f4c024f09f..3deebf9bff3 100644 --- a/modules/ivsBidAdapter.js +++ b/modules/ivsBidAdapter.js @@ -4,6 +4,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { INSTREAM } from '../src/video.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 = 'ivs'; const ENDPOINT_URL = 'https://a.ivstracker.net/prod/openrtb/2.5'; diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index 8a3b05b7ed3..dde7c25d9b9 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -3,6 +3,15 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + var BIDDER_CODE = 'lemmadigital'; var LOG_WARN_PREFIX = 'LEMMADIGITAL: '; var ENDPOINT = 'https://bid.lemmadigital.com/lemma/servad'; diff --git a/modules/lifestreetBidAdapter.js b/modules/lifestreetBidAdapter.js index 6a8b783ce21..5b5eb639fcf 100644 --- a/modules/lifestreetBidAdapter.js +++ b/modules/lifestreetBidAdapter.js @@ -2,6 +2,10 @@ import { isInteger } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'lifestreet'; const ADAPTER_VERSION = '$prebid.version$'; diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 0eb9e900160..acc76014abe 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -3,6 +3,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; 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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'limelightDigital'; /** diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index 82affe40e03..cf1e690d862 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -6,6 +6,11 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'livewrapped'; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const URL = 'https://lwadm.com/ad'; diff --git a/modules/lm_kiviadsBidAdapter.js b/modules/lm_kiviadsBidAdapter.js index 9ba26052727..7c3085047c4 100644 --- a/modules/lm_kiviadsBidAdapter.js +++ b/modules/lm_kiviadsBidAdapter.js @@ -4,6 +4,14 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const CUR = 'USD'; const BIDDER_CODE = 'lm_kiviads'; const ENDPOINT = 'https://pbjs.kiviads.live'; diff --git a/modules/madvertiseBidAdapter.js b/modules/madvertiseBidAdapter.js index 457ff2409b8..3b031623aef 100644 --- a/modules/madvertiseBidAdapter.js +++ b/modules/madvertiseBidAdapter.js @@ -2,6 +2,11 @@ import { parseSizesInput, _each } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + // use protocol relative urls for http or https const MADVERTISE_ENDPOINT = 'https://mobile.mng-ads.com/'; diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js index 1d50ad4e4c1..67c8a4aec07 100644 --- a/modules/malltvBidAdapter.js +++ b/modules/malltvBidAdapter.js @@ -2,6 +2,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'malltv'; const ENDPOINT_URL = 'https://central.mall.tv/bid'; const DIMENSION_SEPARATOR = 'x'; diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index 3d33bbf8c12..9f899974721 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -3,6 +3,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'mediaforce'; const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid'; const TEST_ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid?debug_key=abcdefghijklmnop'; diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 1fdd3530fae..5e7221583a8 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -37,6 +37,11 @@ import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'mediafuse'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 8513fba94c4..8f687d30ff3 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -8,6 +8,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; // import { config } from '../src/config.js'; // import { isPubcidEnabled } from './pubCommonId.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').mediaType} mediaType + */ + const BIDDER_CODE = 'mediago'; // const PROTOCOL = window.document.location.protocol; const ENDPOINT_URL = 'https://gbid.mediago.io/api/bid?tn='; diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 7af43a3c549..f4967fed170 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -119,7 +119,7 @@ function getOS() { * * @param {*} bid a Prebid.js bid (request) object * @param {string} mediaType the mediaType or the wildcard '*' - * @param {string|array} size the size array or the wildcard '*' + * @param {string|Array} size the size array or the wildcard '*' * @returns {number|boolean} */ function getFloor(bid, mediaType, size = '*') { diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 041db71cd34..6a8a35dbfd4 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -19,6 +19,12 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + const BIDDER_CODE = 'medianet'; const TRUSTEDSTACK_CODE = 'trustedstack'; const BID_URL = 'https://prebid.media.net/rtb/prebid'; diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js index aee5f6230b2..5cf0ceaba18 100644 --- a/modules/mediasniperBidAdapter.js +++ b/modules/mediasniperBidAdapter.js @@ -241,7 +241,7 @@ function createImp(bid) { * * @param {*} bid a Prebid.js bid (request) object * @param {string} mediaType the mediaType or the wildcard '*' - * @param {string|array} size the size array or the wildcard '*' + * @param {string|Array} size the size array or the wildcard '*' * @returns {number|boolean} */ function getFloor(bid, mediaType, size = '*') { diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index fb580d81b94..550c715e741 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -6,6 +6,15 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {Renderer} from '../src/Renderer.js'; import { getRefererInfo } from '../src/refererDetection.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'mediasquare'; const BIDDER_URL_PROD = 'https://pbs-front.mediasquare.fr/' const BIDDER_URL_TEST = 'https://bidder-test.mediasquare.fr/' diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 1e158236deb..fb3990e97f1 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -21,6 +21,12 @@ import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.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 + */ + const GVLID = 358; const DEFAULT_CUR = 'USD'; const BIDDER_CODE = 'mgid'; diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 080ed2a556f..527b6704146 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -11,6 +11,13 @@ import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'missena'; const ENDPOINT_URL = 'https://bid.missena.io/'; const EVENTS_DOMAIN = 'events.missena.io'; diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index eab174d22dd..8a41efe4dcc 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -14,6 +14,12 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {find} from '../src/polyfill.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 = 'nextroll'; const BIDDER_ENDPOINT = 'https://d.adroll.com/bid/prebid/'; const ADAPTER_VERSION = 5; diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index c65544936fa..baadaa272e6 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -8,6 +8,15 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' import { INSTREAM, OUTSTREAM } from '../src/video.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; const BIDDER_CODE = 'nexx360'; diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 77de3c6d97b..28fb38e14e5 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -5,6 +5,15 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { hasPurpose1Consent } from '../src/utils/gpdr.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index ee6d2980385..eb9fa2eb536 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -8,6 +8,11 @@ import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { deepClone, logError, deepAccess } from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const ENDPOINT = 'https://onetag-sys.com/prebid-request'; const USER_SYNC_ENDPOINT = 'https://onetag-sys.com/usync/'; const BIDDER_CODE = 'onetag'; diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 131ba0bc1f2..957192d1bec 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -18,6 +18,13 @@ import {Renderer} from '../src/Renderer.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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ const BIDDER_CODE = 'operaads'; const ENDPOINT = 'https://s.adx.opera.com/ortb/v2/'; diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 152876b8d5d..27b858c84fe 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -3,6 +3,15 @@ import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, parseSizesInput} from '../src/utils.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'optidigital'; const GVL_ID = 915; const ENDPOINT_URL = 'https://pbs.optidigital.com/bidder'; diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index efc2effdd62..0f912384db7 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -5,6 +5,10 @@ import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getGlobal } from '../src/prebidGlobal.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ const storageManager = getStorageManager({ bidderCode: 'orbidder' }); /** diff --git a/modules/pilotxBidAdapter.js b/modules/pilotxBidAdapter.js index bd3612d6429..0fb39e19076 100644 --- a/modules/pilotxBidAdapter.js +++ b/modules/pilotxBidAdapter.js @@ -1,4 +1,12 @@ 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'pilotx'; const ENDPOINT_URL = '//adn.pilotx.tv/hb' export const spec = { diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index 931c5e7d7e6..4b4b5677cb3 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -4,6 +4,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'prisma'; const BIDDER_URL = 'https://prisma.nexx360.io/prebid'; const CACHE_URL = 'https://prisma.nexx360.io/cache'; diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 192c657d976..d25627a7b90 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -7,6 +7,12 @@ import { bidderSettings } from '../src/bidderSettings.js'; import CONSTANTS from '../src/constants.json'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.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 + */ + const BIDDER_CODE = 'pubmatic'; const LOG_WARN_PREFIX = 'PubMatic: '; const ENDPOINT = 'https://hbopenbid.pubmatic.com/translator?source=prebid-client'; diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index 6a5d866c76d..507df4a2bb0 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -6,6 +6,13 @@ import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { OUTSTREAM, INSTREAM } from '../src/video.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 VERSION = '0.3.0'; const GVLID = 842; const NET_REVENUE = true; diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js index 1ab432496a3..8b9dbea339b 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -2,6 +2,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {isArray, logError, logInfo} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'pxyz'; const URL = 'https://ads.playground.xyz/host-config/prebid?v=2'; const DEFAULT_CURRENCY = 'USD'; diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 2c721a61616..1ba23302367 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -6,6 +6,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; import {parseDomain} from '../src/refererDetection.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'quantcast'; const DEFAULT_BID_FLOOR = 0.0000000001; diff --git a/modules/radsBidAdapter.js b/modules/radsBidAdapter.js index ae16bcf9d83..faa35ee51f7 100644 --- a/modules/radsBidAdapter.js +++ b/modules/radsBidAdapter.js @@ -2,6 +2,10 @@ import {deepAccess} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'rads'; const ENDPOINT_URL = 'https://rads.recognified.net/md.request.php'; const ENDPOINT_URL_DEV = 'https://dcradn1.online-solution.biz/md.request.php'; diff --git a/modules/retailspotBidAdapter.js b/modules/retailspotBidAdapter.js index 616b638e840..557dd617274 100644 --- a/modules/retailspotBidAdapter.js +++ b/modules/retailspotBidAdapter.js @@ -2,6 +2,11 @@ import {buildUrl, deepAccess, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'retailspot'; const DEFAULT_SUBDOMAIN = 'ssp'; const PREPROD_SUBDOMAIN = 'ssp-preprod'; @@ -28,7 +33,7 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {bidRequests} - bidRequests.bids[] is an array of AdUnits and bids + * @param {BidRequests} - bidRequests.bids[] is an array of AdUnits and bids * @return ServerRequest Info describing the request to the server. */ buildRequests: function (bidRequests, bidderRequest) { diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js index 5b1a92b02a0..502b62c8799 100644 --- a/modules/rtbsapeBidAdapter.js +++ b/modules/rtbsapeBidAdapter.js @@ -4,6 +4,14 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.js'; import {Renderer} from '../src/Renderer.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'rtbsape'; const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid'; const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js'; diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9882eb23ac3..daaf9a14b9f 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -23,6 +23,10 @@ import { } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.2.1.js'; diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 51c326c2954..6f36c8a191e 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -3,6 +3,15 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'seedtag'; const SEEDTAG_ALIAS = 'st'; const SEEDTAG_SSP_ENDPOINT = 'https://s.seedtag.com/c/hb/bid'; diff --git a/modules/slimcutBidAdapter.js b/modules/slimcutBidAdapter.js index 9cdb85d4f25..250c1ebb19e 100644 --- a/modules/slimcutBidAdapter.js +++ b/modules/slimcutBidAdapter.js @@ -5,6 +5,13 @@ import { 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').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'slimcut'; const ENDPOINT_URL = 'https://sb.freeskreen.com/pbr'; export const spec = { diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index b735953d099..ac0422842d5 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -8,6 +8,14 @@ import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; import {fill} from '../libraries/appnexusUtils/anUtils.js'; import {chunk} from '../libraries/chunk/chunk.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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 313d466bd2e..9146bba6514 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -3,6 +3,12 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; 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').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'smartadserver'; const GVL_ID = 45; const DEFAULT_FLOOR = 0.0; diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 45cc45192ef..8394814365c 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -21,6 +21,12 @@ import { import { VIDEO } from '../src/mediaTypes.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'smartx'; const URL = 'https://bid.sxp.smartclip.net/bid/1000'; const GVLID = 115; diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 09c39e52825..46584e54373 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -4,6 +4,11 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const GVL_ID = 639; export const spec = { diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 2c84854e507..1ce7665ddfc 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -7,6 +7,12 @@ import { userSync } from '../src/userSync.js'; import { bidderSettings } from '../src/bidderSettings.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'sonobi'; const STR_ENDPOINT = 'https://apex.go.sonobi.com/trinity.json'; const PAGEVIEW_ID = generateUUID(); diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 0ff5d842135..e786095874e 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -15,6 +15,10 @@ import { VIDEO } from '../src/mediaTypes.js' +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const ORTB_VIDEO_PARAMS = { 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), 'minduration': (value) => isInteger(value), diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index bfb527d46f2..0bccc1ec140 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -3,6 +3,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js' +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'sparteo'; const GVLID = 1028; const TTL = 60; diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 017544cc596..c1f1c5159fc 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -21,6 +21,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { loadExternalScript } from '../src/adloader.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'spotx'; const URL = 'https://search.spotxchange.com/openrtb/2.3/dados/'; const ORTB_VERSION = '2.3'; diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 0dacf389955..5cffc5853b5 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -3,6 +3,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'stv'; const ENDPOINT_URL = 'https://ads.smartstream.tv/r/'; const ENDPOINT_URL_DEV = 'https://ads.smartstream.tv/r/'; diff --git a/modules/sublimeBidAdapter.js b/modules/sublimeBidAdapter.js index db2b02aaef1..a29265ce9cd 100644 --- a/modules/sublimeBidAdapter.js +++ b/modules/sublimeBidAdapter.js @@ -2,6 +2,12 @@ import { logInfo, generateUUID, formatQS, triggerPixel, deepAccess } from '../sr import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'sublime'; const BIDDER_GVLID = 114; const DEFAULT_BID_HOST = 'pbjs.sskzlabs.com'; diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 898f6a4185d..f0c275acfb6 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -8,6 +8,10 @@ import { Renderer } from '../src/Renderer.js'; import {parseDomain} from '../src/refererDetection.js'; import {getGlobal} from '../src/prebidGlobal.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + */ + const BIDDER_CODE = 'tappx'; const GVLID_CODE = 628; const TTL = 360; diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 1977686dd23..282f322c36a 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -3,6 +3,11 @@ import {getBidRequest} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const SOURCE = 'pbjs'; const BIDDER_CODE = 'targetVideo'; const ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index ba16c6ddf82..1c12b0e3968 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -2,6 +2,11 @@ import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParame import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'teads'; const GVL_ID = 132; const ENDPOINT_URL = 'https://a.teads.tv/hb/bid-request'; diff --git a/modules/temedyaBidAdapter.js b/modules/temedyaBidAdapter.js index cb9fe46d21a..0e48768b605 100644 --- a/modules/temedyaBidAdapter.js +++ b/modules/temedyaBidAdapter.js @@ -3,6 +3,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'temedya'; const ENDPOINT_URL = 'https://adm.vidyome.com/'; const ENDPOINT_METHOD = 'GET'; diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index def2446635b..f19f7cfe515 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -9,6 +9,15 @@ import { } from '../src/adapters/bidderFactory.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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'theadx'; const ENDPOINT_URL = 'https://ssp.theadx.com/request'; diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index e3adb80fb2f..3edc89c90ae 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -7,6 +7,10 @@ import { Renderer } from '../src/Renderer.js'; import { config } from '../src/config.js'; import * as utils from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'tpmn'; const DEFAULT_BID_TTL = 500; const DEFAULT_CURRENCY = 'USD'; diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 17a3cd652e8..d7705f2f5df 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -4,6 +4,15 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {isNumber} from '../src/utils.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').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDADAPTERVERSION = 'TTD-PREBID-2023.09.05'; const BIDDER_CODE = 'ttd'; const BIDDER_CODE_LONG = 'thetradedesk'; diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 1ef72f293fc..19b933a8666 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -5,6 +5,12 @@ import { getStorageManager } from '../src/storageManager.js'; import { config } from '../src/config.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').ServerRequest} ServerRequest + */ + const COOKIE_NAME = 'ucf_uid'; const VER = 'ADGENT_PREBID-2018011501'; const BIDDER_CODE = 'ucfunnel'; diff --git a/modules/unicornBidAdapter.js b/modules/unicornBidAdapter.js index 429ba8f60ba..43eb943f6d5 100644 --- a/modules/unicornBidAdapter.js +++ b/modules/unicornBidAdapter.js @@ -3,6 +3,11 @@ import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'unicorn'; const UNICORN_ENDPOINT = 'https://ds.uncn.jp/pb/0/bid.json'; const UNICORN_DEFAULT_CURRENCY = 'JPY'; diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index 05960378d23..ada843a6e45 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -2,6 +2,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; 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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'vdoai'; const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index 7368967ff3f..8809aae32bd 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -12,6 +12,13 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {OUTSTREAM} from '../src/video.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').BidderSpec} BidderSpec + */ + const BIDDER_CODE = 'vibrantmedia'; const VIBRANT_MEDIA_PREBID_URL = 'https://prebid.intellitxt.com/prebid'; const VALID_PIXEL_URL_REGEX = /^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+([/?].*)?$/; diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js index c4dae78e862..8cedf9ac16a 100644 --- a/modules/videobyteBidAdapter.js +++ b/modules/videobyteBidAdapter.js @@ -2,6 +2,14 @@ import { logMessage, logError, deepAccess, isFn, isPlainObject, isStr, isNumber, import {registerBidder} from '../src/adapters/bidderFactory.js'; import {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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'videobyte'; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js index 2f02734a31f..ee2c2deef8b 100644 --- a/modules/videoheroesBidAdapter.js +++ b/modules/videoheroesBidAdapter.js @@ -4,6 +4,11 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'videoheroes'; const DEFAULT_CUR = 'USD'; const ENDPOINT_URL = `https://point.contextualadv.com/?t=2&partner=hash`; diff --git a/modules/videonowBidAdapter.js b/modules/videonowBidAdapter.js index bfbc07fdff1..563f692693a 100644 --- a/modules/videonowBidAdapter.js +++ b/modules/videonowBidAdapter.js @@ -2,6 +2,12 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {_each, getBidIdParameter, getValue, logError, logInfo} from '../src/utils.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 + */ + const BIDDER_CODE = 'videonow'; const RTB_URL = 'https://adx.videonow.ru/yhb' const DEFAULT_CURRENCY = 'RUB' diff --git a/modules/viqeoBidAdapter.js b/modules/viqeoBidAdapter.js index 5762a794c8e..28f4de1fd52 100644 --- a/modules/viqeoBidAdapter.js +++ b/modules/viqeoBidAdapter.js @@ -3,6 +3,14 @@ import {logError, logInfo, _each, mergeDeep, isFn, isNumber, isPlainObject} from import {VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.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').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + */ + const BIDDER_CODE = 'viqeo'; const DEFAULT_MIMES = ['application/javascript']; const VIQEO_ENDPOINT = 'https://ads.betweendigital.com/openrtb_bid'; diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index 431d0887334..f1670de76d0 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -6,6 +6,13 @@ import {auctionManager} from '../src/auctionManager.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.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 { getConfig } = config; const BIDDER_CODE = 'vox'; diff --git a/modules/welectBidAdapter.js b/modules/welectBidAdapter.js index d88a3f4c3e2..533e6401cd5 100644 --- a/modules/welectBidAdapter.js +++ b/modules/welectBidAdapter.js @@ -1,6 +1,13 @@ import { deepAccess } from '../src/utils.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + */ + const BIDDER_CODE = 'welect'; const DEFAULT_DOMAIN = 'www.welect.de'; diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 41efc432e11..cf1158474b4 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -18,6 +18,11 @@ import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appn import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + const BIDDER_CODE = 'winr'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js index bff76aae172..a813b9aa2a3 100644 --- a/modules/xeBidAdapter.js +++ b/modules/xeBidAdapter.js @@ -4,6 +4,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import {parseSizesInput, isFn, deepAccess, logError, isArray, getBidIdParameter} from '../src/utils.js'; 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const CUR = 'USD'; const BIDDER_CODE = 'xe'; const ENDPOINT = 'https://pbjs.xe.works/bid'; diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index b0136cd21ea..e4fba5beb62 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -5,6 +5,15 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const ENDPOINT = 'https://ad.yieldlab.net'; const BIDDER_CODE = 'yieldlab'; const BID_RESPONSE_TTL_SEC = 300; diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 9109c6e2a80..78c2e7430e7 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -18,6 +18,13 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {find, includes} from '../src/polyfill.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').ServerRequest} ServerRequest + */ + const BIDDER_CODE = 'yieldmo'; const GVLID = 173; const CURRENCY = 'USD'; diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index 9a58e391a17..cc77d53d30d 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -2,6 +2,14 @@ import {deepAccess, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; 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').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + const BIDDER_CODE = 'zeta_global'; const PREBID_DEFINER_ID = '44253' const ENDPOINT_URL = 'https://prebid.rfihub.com/prebid'; diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 0c5db541286..68cf7c4e4cd 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -5,6 +5,12 @@ import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; 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').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'zeta_global_ssp'; const ENDPOINT_URL = 'https://ssp.disqus.com/bid/prebid'; const TIMEOUT_URL = 'https://ssp.disqus.com/timeout/prebid'; diff --git a/src/native.js b/src/native.js index c4709dd77e2..affdc855353 100644 --- a/src/native.js +++ b/src/native.js @@ -16,6 +16,11 @@ import {auctionManager} from './auctionManager.js'; import CONSTANTS from './constants.json'; import {NATIVE} from './mediaTypes.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + export const nativeAdapters = []; export const NATIVE_TARGETING_KEYS = Object.keys(CONSTANTS.NATIVE_KEYS).map( From 1c0f80f2e89aa96555295183871c66c167c60c24 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Sun, 28 Jan 2024 03:31:50 +0400 Subject: [PATCH 380/760] update mail (#10992) --- modules/sovrnAnalyticsAdapter.md | 2 +- modules/sovrnBidAdapter.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sovrnAnalyticsAdapter.md b/modules/sovrnAnalyticsAdapter.md index 80bc6d7f6b1..b4fe7c971a2 100644 --- a/modules/sovrnAnalyticsAdapter.md +++ b/modules/sovrnAnalyticsAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Sovrn Analytics Adapter Module Type: Analytics Adapter -Maintainer: jrosendahl@sovrn.com +Maintainer: exchange@sovrn.com ``` # Description diff --git a/modules/sovrnBidAdapter.md b/modules/sovrnBidAdapter.md index 53e3158024d..ce131269eee 100644 --- a/modules/sovrnBidAdapter.md +++ b/modules/sovrnBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Sovrn Bid Adapter Module Type: Bidder Adapter -Maintainer: jrosendahl@sovrn.com +Maintainer: exchange@sovrn.com ``` # Description From ba42d90e3af8a5c6b79da98aa5d3b392d8dc8d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Mon, 29 Jan 2024 15:50:55 +0100 Subject: [PATCH 381/760] Grid bid adapter : do not send topics along requests to the backend (#10995) --- modules/gridBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index b2350c0a61c..7147864a2e6 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -466,6 +466,7 @@ export const spec = { }, ajaxCall: function(url, cb, data, options) { + options.browsingTopics = false; return ajax(url, cb, data, options); }, From 17f346834449b1b4e02852793e6a92cbe3541532 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Mon, 29 Jan 2024 07:19:23 -0800 Subject: [PATCH 382/760] dfpAdServerVideo: add several parameters do DFP URLs (#10977) --- modules/dfpAdServerVideo.js | 103 ++++- test/spec/modules/dfpAdServerVideo_spec.js | 485 ++++++++++++++------- 2 files changed, 405 insertions(+), 183 deletions(-) diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index a3e26dc7202..7f275992210 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,17 +2,28 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import { registerVideoSupport } from '../src/adServerManager.js'; -import { targeting } from '../src/targeting.js'; -import { deepAccess, isEmpty, logError, parseSizesInput, formatQS, parseUrl, buildUrl } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { getHook, submodule } from '../src/hook.js'; -import { auctionManager } from '../src/auctionManager.js'; -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; +import {registerVideoSupport} from '../src/adServerManager.js'; +import {targeting} from '../src/targeting.js'; +import { + isNumber, + buildUrl, + deepAccess, + formatQS, + isEmpty, + logError, + parseSizesInput, + parseUrl, + uniques +} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {getHook, submodule} from '../src/hook.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; import CONSTANTS from '../src/constants.json'; import {getPPID} from '../src/adserver.js'; import {getRefererInfo} from '../src/refererDetection.js'; +import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; /** * @typedef {Object} DfpVideoParams @@ -113,7 +124,6 @@ export function buildDfpVideoUrl(options) { const descriptionUrl = getDescriptionUrl(bid, options, 'params'); if (descriptionUrl) { queryParams.description_url = descriptionUrl; } - const gdprConsent = gdprDataHandler.getConsentData(); if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { queryParams.gdpr = Number(gdprConsent.gdprApplies); } @@ -121,14 +131,6 @@ export function buildDfpVideoUrl(options) { if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } } - const uspConsent = uspDataHandler.getConsentData(); - if (uspConsent) { queryParams.us_privacy = uspConsent; } - - const gppConsent = gppDataHandler.getConsentData(); - if (gppConsent) { - // TODO - need to know what to set here for queryParams... - } - if (!queryParams.ppid) { const ppid = getPPID(); if (ppid != null) { @@ -136,6 +138,70 @@ export function buildDfpVideoUrl(options) { } } + const video = options.adUnit?.mediaTypes?.video; + Object.entries({ + plcmt: () => video?.plcmt, + min_ad_duration: () => isNumber(video?.minduration) ? video.minduration * 1000 : null, + max_ad_duration: () => isNumber(video?.maxduration) ? video.maxduration * 1000 : null, + vpos() { + const startdelay = video?.startdelay; + if (isNumber(startdelay)) { + if (startdelay === -2) return 'postroll'; + if (startdelay === -1 || startdelay > 0) return 'midroll'; + return 'preroll'; + } + }, + vconp: () => Array.isArray(video?.playbackmethod) && video.playbackmethod.every(m => m === 7) ? '2' : undefined, + vpa() { + // playbackmethod = 3 is play on click; 1, 2, 4, 5, 6 are autoplay + if (Array.isArray(video?.playbackmethod)) { + const click = video.playbackmethod.some(m => m === 3); + const auto = video.playbackmethod.some(m => [1, 2, 4, 5, 6].includes(m)); + if (click && !auto) return 'click'; + if (auto && !click) return 'auto'; + } + }, + vpmute() { + // playbackmethod = 2, 6 are muted; 1, 3, 4, 5 are not + if (Array.isArray(video?.playbackmethod)) { + const muted = video.playbackmethod.some(m => [2, 6].includes(m)); + const talkie = video.playbackmethod.some(m => [1, 3, 4, 5].includes(m)); + if (muted && !talkie) return '1'; + if (talkie && !muted) return '0'; + } + } + }).forEach(([param, getter]) => { + if (!queryParams.hasOwnProperty(param)) { + const val = getter(); + if (val != null) { + queryParams[param] = val; + } + } + }); + const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ?? + auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global; + + function getSegments(sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) + } + + const signals = Object.entries({ + IAB_AUDIENCE_1_1: getSegments(['user.data'], 4), + IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + if (signals.length) { + queryParams.ppsj = btoa(JSON.stringify({ + PublisherProvidedTaxonomySignals: signals + })) + } + return buildUrl(Object.assign({ protocol: 'https', host: 'securepubads.g.doubleclick.net', @@ -164,6 +230,8 @@ if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('reg * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP */ export function buildAdpodVideoUrl({code, params, callback} = {}) { + // TODO: the public API for this does not take in enough info to fill all DFP params (adUnit/bid), + // and is marked "alpha": https://docs.prebid.org/dev-docs/publisher-api-reference/adServers.dfp.buildAdpodVideoUrl.html if (!params || !callback) { logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); return; @@ -225,9 +293,6 @@ export function buildAdpodVideoUrl({code, params, callback} = {}) { if (gdprConsent.addtlConsent) { queryParams.addtl_consent = gdprConsent.addtlConsent; } } - const uspConsent = uspDataHandler.getConsentData(); - if (uspConsent) { queryParams.us_privacy = uspConsent; } - const masterTag = buildUrl({ protocol: 'https', host: 'securepubads.g.doubleclick.net', diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 4c12e9fa211..39713c2b51a 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,43 +1,61 @@ -import { expect } from 'chai'; +import {expect} from 'chai'; import parse from 'url-parse'; -import {buildDfpVideoUrl, buildAdpodVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; -import adUnit from 'test/fixtures/video/adUnit.json'; +import {buildAdpodVideoUrl, buildDfpVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; +import AD_UNIT from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; -import { config } from 'src/config.js'; -import { targeting } from 'src/targeting.js'; -import { auctionManager } from 'src/auctionManager.js'; -import { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; +import {deepClone} from 'src/utils.js'; +import {config} from 'src/config.js'; +import {targeting} from 'src/targeting.js'; +import {auctionManager} from 'src/auctionManager.js'; +import {gdprDataHandler, uspDataHandler} from 'src/adapterManager.js'; import * as adpod from 'modules/adpod.js'; -import { server } from 'test/mocks/xhr.js'; +import {server} from 'test/mocks/xhr.js'; import * as adServer from 'src/adserver.js'; -import {deepClone} from 'src/utils.js'; import {hook} from '../../../src/hook.js'; -import {getRefererInfo} from '../../../src/refererDetection.js'; - -const bid = { - videoCacheKey: 'abc', - adserverTargeting: { - hb_uuid: 'abc', - hb_cache_id: 'abc', - }, -}; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; +import {AuctionIndex} from '../../../src/auctionIndex.js'; describe('The DFP video support module', function () { before(() => { hook.ready(); }); - let sandbox; + let sandbox, bid, adUnit; beforeEach(() => { sandbox = sinon.sandbox.create(); + bid = { + videoCacheKey: 'abc', + adserverTargeting: { + hb_uuid: 'abc', + hb_cache_id: 'abc', + }, + }; + adUnit = deepClone(AD_UNIT); }); afterEach(() => { sandbox.restore(); }); + function getURL(options) { + return parse(buildDfpVideoUrl(Object.assign({ + adUnit: adUnit, + bid: bid, + params: { + 'iu': 'my/adUnit' + } + }, options))) + } + function getQueryParams(options) { + return utils.parseQS(getURL(options).query); + } + + function getCustomParams(options) { + return utils.parseQS('?' + decodeURIComponent(getQueryParams(options).cust_params)); + } + Object.entries({ params: { params: { @@ -51,37 +69,25 @@ describe('The DFP video support module', function () { describe(`when using ${t}`, () => { it('should use page location as default for description_url', () => { sandbox.stub(dep, 'ri').callsFake(() => ({page: 'example.com'})); - - const url = parse(buildDfpVideoUrl(Object.assign({ - adUnit: adUnit, - bid: bid, - }, options))); - const prm = utils.parseQS(url.query); + const prm = getQueryParams(options); expect(prm.description_url).to.eql('example.com'); }); it('should use a URI encoded page location as default for description_url', () => { sandbox.stub(dep, 'ri').callsFake(() => ({page: 'https://example.com?iu=/99999999/news&cust_params=current_hour%3D12%26newscat%3Dtravel&pbjs_debug=true'})); - const url = parse(buildDfpVideoUrl(Object.assign({ - adUnit: adUnit, - bid: bid, - }, options))); - const prm = utils.parseQS(url.query); + const prm = getQueryParams(options); expect(prm.description_url).to.eql('https%3A%2F%2Fexample.com%3Fiu%3D%2F99999999%2Fnews%26cust_params%3Dcurrent_hour%253D12%2526newscat%253Dtravel%26pbjs_debug%3Dtrue'); }); }); }) it('should make a legal request URL when given the required params', function () { - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bid, + const url = getURL({ params: { 'iu': 'my/adUnit', 'description_url': 'someUrl.com', } - })); - + }) expect(url.protocol).to.equal('https:'); expect(url.host).to.equal('securepubads.g.doubleclick.net'); @@ -98,15 +104,10 @@ describe('The DFP video support module', function () { }); it('can take an adserver url as a parameter', function () { - const bidCopy = utils.deepClone(bid); - bidCopy.vastUrl = 'vastUrl.example'; - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, + bid.vastUrl = 'vastUrl.example'; + const url = getURL({ url: 'https://video.adserver.example/', - })); - + }) expect(url.host).to.equal('video.adserver.example'); }); @@ -120,161 +121,64 @@ describe('The DFP video support module', function () { }); it('overwrites url params when both url and params object are given', function () { - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bid, + const params = getQueryParams({ url: 'https://video.adserver.example/ads?sz=640x480&iu=/123/aduniturl&impl=s', params: { iu: 'my/adUnit' } - })); + }); - const queryObject = utils.parseQS(url.query); - expect(queryObject.iu).to.equal('my/adUnit'); + expect(params.iu).to.equal('my/adUnit'); }); it('should override param defaults with user-provided ones', function () { - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bid, + const params = getQueryParams({ params: { - 'iu': 'my/adUnit', 'output': 'vast', } - })); - - expect(utils.parseQS(url.query)).to.have.property('output', 'vast'); + }); + expect(params.output).to.equal('vast'); }); it('should include the cache key and adserver targeting in cust_params', function () { - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { + bid.adserverTargeting = Object.assign(bid.adserverTargeting, { hb_adid: 'ad_id', }); - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); - const customParams = utils.parseQS('?' + decodeURIComponent(queryObject.cust_params)); + const customParams = getCustomParams() expect(customParams).to.have.property('hb_adid', 'ad_id'); expect(customParams).to.have.property('hb_uuid', bid.videoCacheKey); expect(customParams).to.have.property('hb_cache_id', bid.videoCacheKey); }); - it('should include the us_privacy key when USP Consent is available', function () { - let uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); - uspDataHandlerStub.returns('1YYY'); - - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { - hb_adid: 'ad_id', - }); - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); - expect(queryObject.us_privacy).to.equal('1YYY'); - uspDataHandlerStub.restore(); - }); - - it('should not include the us_privacy key when USP Consent is not available', function () { - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { - hb_adid: 'ad_id', - }); - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); - expect(queryObject.us_privacy).to.equal(undefined); - }); - it('should include the GDPR keys when GDPR Consent is available', function () { - let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ + sandbox.stub(gdprDataHandler, 'getConsentData').returns({ gdprApplies: true, consentString: 'consent', addtlConsent: 'moreConsent' }); - - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { - hb_adid: 'ad_id', - }); - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); + const queryObject = getQueryParams(); expect(queryObject.gdpr).to.equal('1'); expect(queryObject.gdpr_consent).to.equal('consent'); expect(queryObject.addtl_consent).to.equal('moreConsent'); - gdprDataHandlerStub.restore(); }); it('should not include the GDPR keys when GDPR Consent is not available', function () { - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { - hb_adid: 'ad_id', - }); - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); + const queryObject = getQueryParams() expect(queryObject.gdpr).to.equal(undefined); expect(queryObject.gdpr_consent).to.equal(undefined); expect(queryObject.addtl_consent).to.equal(undefined); }); it('should only include the GDPR keys for GDPR Consent fields with values', function () { - let gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ + sandbox.stub(gdprDataHandler, 'getConsentData').returns({ gdprApplies: true, consentString: 'consent', }); - - const bidCopy = utils.deepClone(bid); - bidCopy.adserverTargeting = Object.assign(bidCopy.adserverTargeting, { - hb_adid: 'ad_id', - }); - - const url = parse(buildDfpVideoUrl({ - adUnit: adUnit, - bid: bidCopy, - params: { - 'iu': 'my/adUnit' - } - })); - const queryObject = utils.parseQS(url.query); + const queryObject = getQueryParams() expect(queryObject.gdpr).to.equal('1'); expect(queryObject.gdpr_consent).to.equal('consent'); expect(queryObject.addtl_consent).to.equal(undefined); - gdprDataHandlerStub.restore(); }); - describe('GAM PPID', () => { let ppid; let getPPIDStub; @@ -290,29 +194,283 @@ describe('The DFP video support module', function () { 'url': {url: 'https://video.adserver.mock/', params: {'iu': 'mock/unit'}} }).forEach(([t, opts]) => { describe(`when using ${t}`, () => { - function buildUrlAndGetParams() { - const url = parse(buildDfpVideoUrl(Object.assign({ - adUnit: adUnit, - bid: deepClone(bid), - }, opts))); - return utils.parseQS(url.query); - } - it('should be included if available', () => { ppid = 'mockPPID'; - const q = buildUrlAndGetParams(); + const q = getQueryParams(opts); expect(q.ppid).to.equal('mockPPID'); }); it('should not be included if not available', () => { ppid = undefined; - const q = buildUrlAndGetParams(); + const q = getQueryParams(opts); expect(q.hasOwnProperty('ppid')).to.be.false; }) }) }) }) + describe('ORTB video parameters', () => { + Object.entries({ + plcmt: [ + { + video: { + plcmt: 1 + }, + expected: '1' + } + ], + min_ad_duration: [ + { + video: { + minduration: 123 + }, + expected: '123000' + } + ], + max_ad_duration: [ + { + video: { + maxduration: 321 + }, + expected: '321000' + } + ], + vpos: [ + { + video: { + startdelay: 0 + }, + expected: 'preroll' + }, + { + video: { + startdelay: -1 + }, + expected: 'midroll' + }, + { + video: { + startdelay: -2 + }, + expected: 'postroll' + }, + { + video: { + startdelay: 10 + }, + expected: 'midroll' + } + ], + vconp: [ + { + video: { + playbackmethod: [7] + }, + expected: '2' + }, + { + video: { + playbackmethod: [7, 1] + }, + expected: undefined + } + ], + vpa: [ + { + video: { + playbackmethod: [1, 2, 4, 5, 6, 7] + }, + expected: 'auto' + }, + { + video: { + playbackmethod: [3, 7], + }, + expected: 'click' + }, + { + video: { + playbackmethod: [1, 3], + }, + expected: undefined + } + ], + vpmute: [ + { + video: { + playbackmethod: [1, 3, 4, 5, 7] + }, + expected: '0' + }, + { + video: { + playbackmethod: [2, 6, 7], + }, + expected: '1' + }, + { + video: { + playbackmethod: [1, 2] + }, + expected: undefined + } + ] + }).forEach(([param, cases]) => { + describe(param, () => { + cases.forEach(({video, expected}) => { + describe(`when mediaTypes.video has ${JSON.stringify(video)}`, () => { + it(`fills in ${param} = ${expected}`, () => { + Object.assign(adUnit.mediaTypes.video, video); + expect(getQueryParams()[param]).to.eql(expected); + }); + it(`does not override pub-provided params.${param}`, () => { + Object.assign(adUnit.mediaTypes.video, video); + expect(getQueryParams({ + params: { + [param]: 'OG' + } + })[param]).to.eql('OG'); + }); + it('does not fill if param has no value', () => { + expect(getQueryParams().hasOwnProperty(param)).to.be.false; + }) + }) + }) + }) + }) + }); + + describe('ppsj', () => { + let ortb2; + beforeEach(() => { + ortb2 = null; + }) + + function getSignals() { + const ppsj = JSON.parse(atob(getQueryParams().ppsj)); + return Object.fromEntries(ppsj.PublisherProvidedTaxonomySignals.map(sig => [sig.taxonomy, sig.values])); + } + + Object.entries({ + 'FPD from bid request'() { + bid.requestId = 'req-id'; + sandbox.stub(auctionManager, 'index').get(() => stubAuctionIndex({ + bidRequests: [ + { + bidId: 'req-id', + ortb2 + } + ] + })); + }, + 'global FPD from auction'() { + bid.auctionId = 'auid'; + sandbox.stub(auctionManager, 'index').get(() => new AuctionIndex(() => [{ + getAuctionId: () => 'auid', + getFPD: () => ({ + global: ortb2 + }) + }])); + } + }).forEach(([t, setup]) => { + describe(`using ${t}`, () => { + beforeEach(setup); + it('does not fill if there\'s no segments in segtax 4 or 6', () => { + ortb2 = { + site: { + content: { + data: [ + { + segment: [ + {id: '1'}, + {id: '2'} + ] + }, + ] + } + }, + user: { + data: [ + { + ext: { + segtax: 1, + }, + segment: [ + {id: '3'} + ] + } + ] + } + } + expect(getQueryParams().ppsj).to.not.exist; + }); + + const SEGMENTS = [ + { + ext: { + segtax: 4, + }, + segment: [ + {id: '4-1'}, + {id: '4-2'} + ] + }, + { + ext: { + segtax: 4, + }, + segment: [ + {id: '4-2'}, + {id: '4-3'} + ] + }, + { + ext: { + segtax: 6, + }, + segment: [ + {id: '6-1'}, + {id: '6-2'} + ] + }, + { + ext: { + segtax: 6, + }, + segment: [ + {id: '6-2'}, + {id: '6-3'} + ] + }, + ] + + it('collects user.data segments with segtax = 4 into IAB_AUDIENCE_1_1', () => { + ortb2 = { + user: { + data: SEGMENTS + } + } + expect(getSignals()).to.eql({ + IAB_AUDIENCE_1_1: ['4-1', '4-2', '4-3'] + }) + }) + + it('collects site.content.data segments with segtax = 6 into IAB_CONTENT_2_2', () => { + ortb2 = { + site: { + content: { + data: SEGMENTS + } + } + } + expect(getSignals()).to.eql({ + IAB_CONTENT_2_2: ['6-1', '6-2', '6-3'] + }) + }) + }) + }) + }) + describe('special targeting unit test', function () { const allTargetingData = { 'hb_format': 'video', @@ -639,7 +797,6 @@ describe('The DFP video support module', function () { expect(queryParams).to.have.property('unviewed_position_start', '1'); expect(queryParams).to.have.property('url'); expect(queryParams).to.have.property('cust_params'); - expect(queryParams).to.have.property('us_privacy', '1YYY'); expect(queryParams).to.have.property('gdpr', '1'); expect(queryParams).to.have.property('gdpr_consent', 'consent'); expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); From 07962d64170f79ee0945ba1a1e747228fb674bd6 Mon Sep 17 00:00:00 2001 From: Quentin Gallard Date: Tue, 30 Jan 2024 14:16:21 +0100 Subject: [PATCH 383/760] SmileWanted - Add Video Instream, Video Outstream and Native support (#10996) Co-authored-by: QuentinGallard --- modules/smilewantedBidAdapter.js | 116 ++++++++--- .../modules/smilewantedBidAdapter_spec.js | 186 +++++++++++++++++- 2 files changed, 272 insertions(+), 30 deletions(-) diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 46584e54373..515aae0e092 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -1,8 +1,12 @@ -import { isArray, logError, logWarn, isFn, isPlainObject } from '../src/utils.js'; -import { Renderer } from '../src/Renderer.js'; -import { config } from '../src/config.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import {deepAccess, deepClone, isArray, isFn, isPlainObject, logError, logWarn} from '../src/utils.js'; +import {Renderer} from '../src/Renderer.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {convertOrtbRequestToProprietaryNative, toOrtbNativeRequest, toLegacyResponse} from '../src/native.js'; + +const BIDDER_CODE = 'smilewanted'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,29 +16,50 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; const GVL_ID = 639; export const spec = { - code: 'smilewanted', - aliases: ['smile', 'sw'], + code: BIDDER_CODE, gvlid: GVL_ID, - supportedMediaTypes: [BANNER, VIDEO], + aliases: ['smile', 'sw'], + supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** * Determines whether or not the given bid request is valid. * - * @param {object} bid The bid to validate. + * @param {BidRequest} bid The bid to validate. * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function(bid) { - return !!(bid.params && bid.params.zoneId); + if (!bid.params || !bid.params.zoneId) { + return false; + } + + if (deepAccess(bid, 'mediaTypes.video')) { + const videoMediaTypesParams = deepAccess(bid, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bid, 'params.video', {}); + + const videoParams = { + ...videoMediaTypesParams, + ...videoBidderParams + }; + + if (!videoParams.context || ![INSTREAM, OUTSTREAM].includes(videoParams.context)) { + return false; + } + } + + return true; }, /** * Make a server request from the list of BidRequests. * * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @param {BidderRequest} bidderRequest bidder request object. * @return ServerRequest Info describing the request to the server. */ buildRequests: function(validBidRequests, bidderRequest) { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + return validBidRequests.map(bid => { - var payload = { + const payload = { zoneId: bid.params.zoneId, currencyCode: config.getConfig('currency.adServerCurrency') || 'EUR', tagId: bid.adUnitCode, @@ -65,20 +90,41 @@ export const spec = { payload.bidfloor = bid.params.bidfloor; } - if (bidderRequest && bidderRequest.refererInfo) { + if (bidderRequest?.refererInfo) { payload.pageDomain = bidderRequest.refererInfo.page || ''; } - if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest?.gdprConsent) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side } - if (bid && bid.userIdAsEids) { - payload.eids = bid.userIdAsEids; + payload.eids = bid?.userIdAsEids; + + const videoMediaType = deepAccess(bid, 'mediaTypes.video'); + const context = deepAccess(bid, 'mediaTypes.video.context'); + + if (bid.mediaType === 'video' || (videoMediaType && context === INSTREAM) || (videoMediaType && context === OUTSTREAM)) { + payload.context = context; + payload.videoParams = deepClone(videoMediaType); } - var payloadString = JSON.stringify(payload); + const nativeMediaType = deepAccess(bid, 'mediaTypes.native'); + + if (nativeMediaType) { + payload.context = 'native'; + payload.nativeParams = nativeMediaType; + let sizes = deepAccess(bid, 'mediaTypes.native.image.sizes', []); + + if (sizes.length > 0) { + const size = Array.isArray(sizes[0]) ? sizes[0] : sizes; + + payload.width = size[0] || payload.width; + payload.height = size[1] || payload.height; + } + } + + const payloadString = JSON.stringify(payload); return { method: 'POST', url: 'https://prebid.smilewanted.com', @@ -90,18 +136,21 @@ export const spec = { /** * Unpack the response from the server into a list of bids. * - * @param {*} serverResponse A successful response from the server. + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { + if (!serverResponse.body) return []; const bidResponses = []; - var response = serverResponse.body; try { + const response = serverResponse.body; + const bidRequestData = JSON.parse(bidRequest.data); if (response) { const dealId = response.dealId || ''; const bidResponse = { - requestId: JSON.parse(bidRequest.data).bidId, + requestId: bidRequestData.bidId, cpm: response.cpm, width: response.width, height: response.height, @@ -113,14 +162,21 @@ export const spec = { ad: response.ad, }; - if (response.formatTypeSw == 'video_instream' || response.formatTypeSw == 'video_outstream') { + if (response.formatTypeSw === 'video_instream' || response.formatTypeSw === 'video_outstream') { bidResponse['mediaType'] = 'video'; bidResponse['vastUrl'] = response.ad; bidResponse['ad'] = null; + + if (response.formatTypeSw === 'video_outstream') { + bidResponse['renderer'] = newRenderer(bidRequestData, response); + } } - if (response.formatTypeSw == 'video_outstream') { - bidResponse['renderer'] = newRenderer(JSON.parse(bidRequest.data), response); + if (response.formatTypeSw === 'native') { + const nativeAdResponse = JSON.parse(response.ad); + const ortbNativeRequest = toOrtbNativeRequest(bidRequestData.nativeParams); + bidResponse['mediaType'] = 'native'; + bidResponse['native'] = toLegacyResponse(nativeAdResponse, ortbNativeRequest); } if (dealId.length > 0) { @@ -128,7 +184,7 @@ export const spec = { } bidResponse.meta = {}; - if (response.meta && response.meta.advertiserDomains && isArray(response.meta.advertiserDomains)) { + if (response.meta?.advertiserDomains && isArray(response.meta.advertiserDomains)) { bidResponse.meta.advertiserDomains = response.meta.advertiserDomains; } bidResponses.push(bidResponse); @@ -136,15 +192,18 @@ export const spec = { } catch (error) { logError('Error while parsing smilewanted response', error); } + return bidResponses; }, /** - * User syncs. + * Register the user sync pixels which should be dropped after the auction. * - * @param {*} syncOptions Publisher prebid configuration. - * @param {*} serverResponses A successful response from the server. - * @return {Syncs[]} An array of syncs that should be executed. + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} responses List of server's responses. + * @param {Object} gdprConsent The GDPR consent parameters + * @param {Object} uspConsent The USP consent parameters + * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { let params = ''; @@ -177,7 +236,8 @@ export const spec = { /** * Create SmileWanted renderer - * @param requestId + * @param bidRequest + * @param bidResponse * @returns {*} */ function newRenderer(bidRequest, bidResponse) { diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 22221dbe1ef..99c4034610f 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -93,7 +93,24 @@ const BID_RESPONSE_DISPLAY = { const VIDEO_INSTREAM_REQUEST = [{ code: 'video1', mediaTypes: { - video: {} + video: { + context: 'instream', + mimes: ['video/mp4'], + minduration: 0, + maxduration: 120, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + startdelay: 0, + placement: 1, + skip: 1, + skipafter: 10, + minbitrate: 10, + maxbitrate: 10, + delivery: [1], + playbackmethod: [2], + api: [1, 2], + linearity: 1, + playerSize: [640, 480] + } }, sizes: [ [640, 480] @@ -163,6 +180,99 @@ const BID_RESPONSE_VIDEO_OUTSTREAM = { } }; +const NATIVE_REQUEST = [{ + adUnitCode: 'native_300x250', + code: '/19968336/prebid_native_example_1', + bidId: '12345', + sizes: [ + [300, 250] + ], + mediaTypes: { + native: { + sendTargetingKeys: false, + title: { + required: true, + len: 140 + }, + image: { + required: true, + sizes: [300, 250] + }, + icon: { + required: false, + sizes: [50, 50] + }, + sponsoredBy: { + required: true + }, + body: { + required: true + }, + clickUrl: { + required: false + }, + privacyLink: { + required: false + }, + cta: { + required: false + }, + rating: { + required: false + }, + likes: { + required: false + }, + downloads: { + required: false + }, + price: { + required: false + }, + salePrice: { + required: false + }, + phone: { + required: false + }, + address: { + required: false + }, + desc2: { + required: false + }, + displayUrl: { + required: false + } + } + }, + bidder: 'smilewanted', + params: { + zoneId: 4, + }, + requestId: 'request_abcd1234', + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + }, +}]; + +const BID_RESPONSE_NATIVE = { + body: { + cpm: 3, + width: 300, + height: 250, + creativeId: 'crea_sw_1', + currency: 'EUR', + isNetCpm: true, + ttl: 300, + ad: '{"link":{"url":"https://www.smilewanted.com"},"assets":[{"id":0,"required":1,"title":{"len":50}},{"id":1,"required":1,"img":{"type":3,"w":150,"h":50,"ext":{"aspectratios":["2:1"]}}},{"id":2,"required":0,"img":{"type":1,"w":50,"h":50,"ext":{"aspectratios":["2:1"]}}},{"id":3,"required":1,"data":{"type":1,"value":"Smilewanted sponsor"}},{"id":4,"required":1,"data":{"type":2,"value":"Smilewanted Description"}}]}', + cSyncUrl: 'https://csync.smilewanted.com', + formatTypeSw: 'native' + } +}; + // Default params with optional ones describe('smilewantedBidAdapterTests', function () { it('SmileWanted - Verify build request', function () { @@ -195,6 +305,23 @@ describe('smilewantedBidAdapterTests', function () { expect(requestVideoInstreamContent.sizes[0]).to.have.property('w').and.to.equal(640); expect(requestVideoInstreamContent.sizes[0]).to.have.property('h').and.to.equal(480); expect(requestVideoInstreamContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestVideoInstreamContent).to.have.property('videoParams'); + expect(requestVideoInstreamContent.videoParams).to.have.property('context').and.to.equal('instream').and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('mimes').to.be.an('array').that.include('video/mp4').and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('minduration').and.to.equal(0).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('maxduration').and.to.equal(120).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('protocols').to.be.an('array').that.include.members([1, 2, 3, 4, 5, 6, 7, 8]).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('startdelay').and.to.equal(0).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('placement').and.to.equal(1).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('skip').and.to.equal(1).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('skipafter').and.to.equal(10).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('minbitrate').and.to.equal(10).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('maxbitrate').and.to.equal(10).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('delivery').to.be.an('array').that.include(1).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('playbackmethod').to.be.an('array').that.include(2).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('api').to.be.an('array').that.include.members([1, 2]).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('linearity').and.to.equal(1).and.to.not.be.undefined; + expect(requestVideoInstreamContent.videoParams).to.have.property('playerSize').to.be.an('array').that.include.members([640, 480]).and.to.not.be.undefined; const requestVideoOutstream = spec.buildRequests(VIDEO_OUTSTREAM_REQUEST); expect(requestVideoOutstream[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); @@ -206,6 +333,39 @@ describe('smilewantedBidAdapterTests', function () { expect(requestVideoOutstreamContent.sizes[0]).to.have.property('w').and.to.equal(640); expect(requestVideoOutstreamContent.sizes[0]).to.have.property('h').and.to.equal(480); expect(requestVideoOutstreamContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + + const requestNative = spec.buildRequests(NATIVE_REQUEST); + expect(requestNative[0]).to.have.property('url').and.to.equal('https://prebid.smilewanted.com'); + expect(requestNative[0]).to.have.property('method').and.to.equal('POST'); + const requestNativeContent = JSON.parse(requestNative[0].data); + expect(requestNativeContent).to.have.property('zoneId').and.to.equal(4); + expect(requestNativeContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestNativeContent).to.have.property('sizes'); + expect(requestNativeContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestNativeContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestNativeContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestNativeContent).to.have.property('context').and.to.equal('native').and.to.not.be.undefined; + expect(requestNativeContent).to.have.property('nativeParams'); + expect(requestNativeContent.nativeParams.title).to.have.property('required').and.to.equal(true); + expect(requestNativeContent.nativeParams.title).to.have.property('len').and.to.equal(140); + expect(requestNativeContent.nativeParams.image).to.have.property('required').and.to.equal(true); + expect(requestNativeContent.nativeParams.image).to.have.property('sizes').to.be.an('array').that.include.members([300, 250]).and.to.not.be.undefined; + expect(requestNativeContent.nativeParams.icon).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.icon).to.have.property('sizes').to.be.an('array').that.include.members([50, 50]).and.to.not.be.undefined; + expect(requestNativeContent.nativeParams.sponsoredBy).to.have.property('required').and.to.equal(true); + expect(requestNativeContent.nativeParams.body).to.have.property('required').and.to.equal(true); + expect(requestNativeContent.nativeParams.clickUrl).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.privacyLink).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.cta).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.rating).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.likes).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.downloads).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.price).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.salePrice).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.phone).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.address).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.desc2).to.have.property('required').and.to.equal(false); + expect(requestNativeContent.nativeParams.displayUrl).to.have.property('required').and.to.equal(false); }); it('SmileWanted - Verify build request with referrer', function () { @@ -337,7 +497,7 @@ describe('smilewantedBidAdapterTests', function () { }).to.not.throw(); }); - it('SmileWanted - Verify parse response - Video Oustream', function () { + it('SmileWanted - Verify parse response - Video Outstream', function () { const request = spec.buildRequests(VIDEO_OUTSTREAM_REQUEST); const bids = spec.interpretResponse(BID_RESPONSE_VIDEO_OUTSTREAM, request[0]); expect(bids).to.have.lengthOf(1); @@ -360,6 +520,28 @@ describe('smilewantedBidAdapterTests', function () { }).to.not.throw(); }); + it('SmileWanted - Verify parse response - Native', function () { + const request = spec.buildRequests(NATIVE_REQUEST); + const bids = spec.interpretResponse(BID_RESPONSE_NATIVE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(3); + expect(bid.ad).to.equal('{"link":{"url":"https://www.smilewanted.com"},"assets":[{"id":0,"required":1,"title":{"len":50}},{"id":1,"required":1,"img":{"type":3,"w":150,"h":50,"ext":{"aspectratios":["2:1"]}}},{"id":2,"required":0,"img":{"type":1,"w":50,"h":50,"ext":{"aspectratios":["2:1"]}}},{"id":3,"required":1,"data":{"type":1,"value":"Smilewanted sponsor"}},{"id":4,"required":1,"data":{"type":2,"value":"Smilewanted Description"}}]}'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('crea_sw_1'); + expect(bid.currency).to.equal('EUR'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(NATIVE_REQUEST[0].bidId); + + expect(function () { + spec.interpretResponse(BID_RESPONSE_NATIVE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + it('SmileWanted - Verify bidder code', function () { expect(spec.code).to.equal('smilewanted'); }); From 1ec144fab6b13135990b54b7b6894287115add67 Mon Sep 17 00:00:00 2001 From: Chris Southern <79725079+southern-growthcode@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:39:41 -0500 Subject: [PATCH 384/760] GC-179 Simpliy the userId module, and added feature to allow customers to provide custom EIDs (#11004) --- modules/growthCodeIdSystem.js | 169 +++---------------- modules/growthCodeIdSystem.md | 40 +++-- test/spec/modules/growthCodeIdSystem_spec.js | 66 +++----- 3 files changed, 76 insertions(+), 199 deletions(-) diff --git a/modules/growthCodeIdSystem.js b/modules/growthCodeIdSystem.js index 539a20a7302..cf72e2e5133 100644 --- a/modules/growthCodeIdSystem.js +++ b/modules/growthCodeIdSystem.js @@ -5,88 +5,15 @@ * @requires module:modules/userId */ -import {logError, logInfo, pick} from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; import { submodule } from '../src/hook.js' import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.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 GC_DATA_KEY = '_gc_data'; const GCID_KEY = 'gcid'; -const ENDPOINT_URL = 'https://p2.gcprivacy.com/v1/pb?' export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); -/** - * Read GrowthCode data from cookie or local storage - * @param key - * @return {string} - */ -export function readData(key) { - try { - let payload - if (storage.cookiesAreEnabled(null)) { - payload = tryParse(storage.getCookie(key, null)) - } - if (storage.hasLocalStorage()) { - payload = tryParse(storage.getDataFromLocalStorage(key, null)) - } - if (payload !== undefined) { - if (payload.expire_at > (Date.now() / 1000)) { - return payload - } - } - } catch (error) { - logError(error); - } -} - -/** - * Store GrowthCode data in either cookie or local storage - * expiration date: 45 days - * @param key - * @param {string} value - */ -function storeData(key, value) { - try { - logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); - - if (value) { - if (storage.hasLocalStorage(null)) { - storage.setDataInLocalStorage(key, value, null); - } - } - } catch (error) { - logError(error); - } -} - -/** - * Parse json if possible, else return null - * @param data - * @param {object|null} - */ -function tryParse(data) { - let payload; - try { - payload = JSON.parse(data); - if (payload == null) { - return undefined - } - return payload - } catch (err) { - return undefined; - } -} - /** @type {Submodule} */ export const growthCodeIdSubmodule = { /** @@ -103,96 +30,40 @@ export const growthCodeIdSubmodule = { decode(value) { return value && value !== '' ? { 'growthCodeId': value } : undefined; }, + /** * performs action to obtain id and return a value in the callback's response argument * @function * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId(config, consentData) { + getId(config) { const configParams = (config && config.params) || {}; - if (!configParams || typeof configParams.pid !== 'string') { - logError('User ID - GrowthCodeID submodule requires a valid Partner ID to be defined'); - return; - } - const gdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; - const consentString = gdpr ? consentData.consentString : ''; - if (gdpr && !consentString) { - logInfo('Consent string is required to call GrowthCode id.'); - return; - } + let ids = []; + let gcid = storage.getDataFromLocalStorage(GCID_KEY, null) - let publisherId = configParams.publisher_id ? configParams.publisher_id : '_sharedID'; + if (gcid !== null) { + const gcEid = { + source: 'growthcode.io', + uids: [{ + id: gcid, + atype: 3, + }] + } - let sharedId; - if (configParams.publisher_id_storage === 'html5') { - sharedId = storage.getDataFromLocalStorage(publisherId, null) ? (storage.getDataFromLocalStorage(publisherId, null)) : null; - } else { - sharedId = storage.getCookie(publisherId, null) ? (storage.getCookie(publisherId, null)) : null; - } - if (!sharedId) { - logError('User ID - Publisher ID is not correctly setup.'); + ids = ids.concat(gcEid) } - const resp = function(callback) { - let gcData = readData(GC_DATA_KEY); - if (gcData) { - callback(gcData); - } else { - let segment = window.location.pathname.substr(1).replace(/\/+$/, ''); - if (segment === '') { - segment = 'home'; - } - - let url = configParams.url ? configParams.url : ENDPOINT_URL; - url = tryAppendQueryString(url, 'pid', configParams.pid); - url = tryAppendQueryString(url, 'uid', sharedId); - url = tryAppendQueryString(url, 'u', window.location.href); - url = tryAppendQueryString(url, 'h', window.location.hostname); - url = tryAppendQueryString(url, 's', segment); - url = tryAppendQueryString(url, 'r', document.referrer); + let additionalEids = storage.getDataFromLocalStorage(configParams.customerEids, null) + if (additionalEids !== null) { + let data = JSON.parse(additionalEids) + ids = ids.concat(data) + } - ajax(url, { - success: response => { - let respJson = tryParse(response); - // If response is a valid json and should save is true - if (respJson) { - storeData(GC_DATA_KEY, JSON.stringify(respJson)) - storeData(GCID_KEY, respJson.gc_id); - callback(respJson); - } else { - callback(); - } - }, - error: error => { - logError(MODULE_NAME + ': ID fetch encountered an error', error); - callback(); - } - }, undefined, {method: 'GET', withCredentials: true}) - } - }; - return { callback: resp }; + return {id: ids} }, - eids: { - 'growthCodeId': { - getValue: function(data) { - return data.gc_id - }, - source: 'growthcode.io', - atype: 1, - getUidExt: function(data) { - const extendedData = pick(data, [ - 'h1', - 'h2', - 'h3', - ]); - if (Object.keys(extendedData).length) { - return extendedData; - } - } - }, - } + }; submodule('userId', growthCodeIdSubmodule); diff --git a/modules/growthCodeIdSystem.md b/modules/growthCodeIdSystem.md index f804686a7a9..de5344e966b 100644 --- a/modules/growthCodeIdSystem.md +++ b/modules/growthCodeIdSystem.md @@ -18,20 +18,38 @@ pbjs.setConfig({ userIds: [{ name: 'growthCodeId', params: { - pid: 'TEST01', // Set your Partner ID here for production (obtained from Growthcode) - publisher_id: '_sharedID', - publisher_id_storage: 'html5' + customerEids: 'customerEids', } }] } }); ``` -| Param under userSync.userIds[] | Scope | Type | Description | Example | -|--------------------------------|----------|--------| --- |-----------------| -| name | Required | String | The name of this module. | `"growthCodeId"` | -| params | Required | Object | Details of module params. | | -| params.pid | Required | String | This is the Parter ID value obtained from GrowthCode | `"TEST01"` | -| params.url | Optional | String | Custom URL for server | | -| params.publisher_id | Optional | String | Name if the variable that holds your publisher ID | `"_sharedID"` | -| params.publisher_id_storage | Optional | String | Publisher ID storage (cookie, html5) | `"html5"` | +### Sample Eids +Below is an example of the EIDs stored in Local Store (customerEids) +```json +[ + { + "source":"domain.com", + "uids":[ + { + "id":"8212212191539393121", + "ext":{ + "stype":"ppuid" + } + } + ] + }, + { + "source":"example.com", + "uids":[ + { + "id":"e06e9e5a-273c-46f8-aace-6f62cf13ea71", + "ext":{ + "stype":"ppuid" + } + } + ] + } +] +``` diff --git a/test/spec/modules/growthCodeIdSystem_spec.js b/test/spec/modules/growthCodeIdSystem_spec.js index 97083047d4e..e3848dc4844 100644 --- a/test/spec/modules/growthCodeIdSystem_spec.js +++ b/test/spec/modules/growthCodeIdSystem_spec.js @@ -6,9 +6,12 @@ import {expect} from 'chai'; import {getStorageManager} from '../../../src/storageManager.js'; import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; -const GCID_EXPIRY = 45; const MODULE_NAME = 'growthCodeId'; -const SHAREDID = 'fe9c5c89-7d56-4666-976d-e07e73b3b664'; +const EIDS = '[{"source":"domain.com","uids":[{"id":"8212212191539393121","ext":{"stype":"ppuid"}}]}]'; +const GCID = 'e06e9e5a-273c-46f8-aace-6f62cf13ea71' + +const GCID_EID = '{"id": [{"source": "growthcode.io", "uids": [{"atype": 3,"id": "e06e9e5a-273c-46f8-aace-6f62cf13ea71"}]}]}' +const GCID_EID_EID = '{"id": [{"source": "growthcode.io", "uids": [{"atype": 3,"id": "e06e9e5a-273c-46f8-aace-6f62cf13ea71"}]},{"source": "domain.com", "uids": [{"id": "8212212191539393121", "ext": {"stype":"ppuid"}}]}]}' const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); @@ -23,11 +26,8 @@ describe('growthCodeIdSystem', () => { beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); - storage.setDataInLocalStorage('_sharedid', SHAREDID); - const expiresStr = (new Date(Date.now() + (GCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled()) { - storage.setCookie('_sharedid', SHAREDID, expiresStr, 'LAX'); - } + storage.setDataInLocalStorage('gcid', GCID, null); + storage.setDataInLocalStorage('customerEids', EIDS, null); }); afterEach(function () { @@ -40,45 +40,33 @@ describe('growthCodeIdSystem', () => { }); }); - it('should NOT call the growthcode id endpoint if gdpr applies but consent string is missing', function () { - let submoduleCallback = growthCodeIdSubmodule.getId(getIdParams, { gdprApplies: true }, undefined); - expect(submoduleCallback).to.be.undefined; - }); - - it('should log an error if pid configParam was not passed when getId', function () { - growthCodeIdSubmodule.getId(); - expect(logErrorStub.callCount).to.be.equal(1); + it('test return of GCID', function () { + let ids; + ids = growthCodeIdSubmodule.getId(); + expect(ids).to.deep.equal(JSON.parse(GCID_EID)); }); - it('should log an error if sharedId (LocalStore) is not setup correctly', function () { - growthCodeIdSubmodule.getId({params: { - pid: 'TEST01', - publisher_id: '_sharedid_bad', - publisher_id_storage: 'html5', + it('test return of the GCID and an additional EID', function () { + let ids; + ids = growthCodeIdSubmodule.getId({params: { + customerEids: 'customerEids', }}); - expect(logErrorStub.callCount).to.be.equal(1); + expect(ids).to.deep.equal(JSON.parse(GCID_EID_EID)); }); - it('should log an error if sharedId (LocalStore) is not setup correctly', function () { - growthCodeIdSubmodule.getId({params: { - pid: 'TEST01', - publisher_id: '_sharedid_bad', - publisher_id_storage: 'cookie', + it('test return of the GCID and an additional EID (bad Local Store name)', function () { + let ids; + ids = growthCodeIdSubmodule.getId({params: { + customerEids: 'customerEidsBad', }}); - expect(logErrorStub.callCount).to.be.equal(1); + expect(ids).to.deep.equal(JSON.parse(GCID_EID)); }); - it('should call the growthcode id endpoint', function () { - let callBackSpy = sinon.spy(); - let submoduleCallback = growthCodeIdSubmodule.getId(getIdParams).callback; - submoduleCallback(callBackSpy); - let request = server.requests[0]; - expect(request.url.substr(0, 85)).to.be.eq('https://p2.gcprivacy.com/v1/pb?pid=TEST01&uid=' + SHAREDID + '&u='); - request.respond( - 200, - {}, - JSON.stringify({}) - ); - expect(callBackSpy.calledOnce).to.be.true; + it('test decode function)', function () { + let ids; + ids = growthCodeIdSubmodule.decode(GCID, {params: { + customerEids: 'customerEids', + }}); + expect(ids).to.deep.equal(JSON.parse('{"growthCodeId":"' + GCID + '"}')); }); }) From c161e0c57b0d87c338b64766fd7b32cf2019c8aa Mon Sep 17 00:00:00 2001 From: Jeremy Marzka Date: Tue, 30 Jan 2024 12:41:26 -0600 Subject: [PATCH 385/760] E2E testing: Remove @wdio/sync and update @wdio/* to the latest (#10990) * Remove @wdio/sync and update the rest of the @wdio packages * Update the e2e tests to use async/await * Add support for local e2e testing * Update circleci to Node 16 * Update the min Node version to 12 and a a node check for e2e tests * Fix the Node version check error --- .circleci/config.yml | 2 +- README.md | 6 + gulpfile.js | 61 +- package-lock.json | 16317 +++++++++++----- package.json | 15 +- test/helpers/testing-utils.js | 25 +- test/spec/e2e/banner/basic_banner_ad.spec.js | 4 +- .../instream/basic_instream_video_ad.spec.js | 4 +- .../longform/basic_w_bidderSettings.spec.js | 32 +- ...asic_w_custom_adserver_translation.spec.js | 32 +- .../e2e/longform/basic_w_priceGran.spec.js | 32 +- .../basic_w_requireExactDuration.spec.js | 32 +- .../basic_wo_brandCategoryExclusion.spec.js | 30 +- .../basic_wo_requireExactDuration.spec.js | 28 +- .../e2e/modules/e2e_bidderSettings.spec.js | 4 +- test/spec/e2e/modules/e2e_currency.spec.js | 4 +- .../multi-bidder/e2e_multiple_bidders.spec.js | 4 +- test/spec/e2e/native/basic_native_ad.spec.js | 4 +- .../basic_outstream_video_ad.spec.js | 12 +- wdio.conf.js | 24 +- wdio.local.conf.js | 13 + wdio.shared.conf.js | 23 + 22 files changed, 11550 insertions(+), 5158 deletions(-) create mode 100644 wdio.local.conf.js create mode 100644 wdio.shared.conf.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 84ddcdf26ea..22539912268 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ aliases: - &environment docker: # specify the version you desire here - - image: circleci/node:12.16.1-browsers + - image: cimg/node:16.20-browsers resource_class: xlarge # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/README.md b/README.md index 58007519b15..e6d25a5cb5a 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,12 @@ gulp test-coverage gulp view-coverage ``` +Local end-to-end testing can be done with: + +```bash +gulp e2e-test --local +``` + For Prebid.org members with access to BrowserStack, additional end-to-end testing can be done with: ```bash diff --git a/gulpfile.js b/gulpfile.js index 5e16af8b0c1..d8a18ba3df1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -52,6 +52,18 @@ function clean() { .pipe(gulpClean()); } +function requireNodeVersion(version) { + return (done) => { + const [major] = process.versions.node.split('.'); + + if (major < version) { + throw new Error(`This task requires Node v${version}`) + } + + done(); + } +} + // Dependant task for building postbid. It escapes postbid-config file. function escapePostbidConfig() { gulp.src('./integrationExamples/postbid/oas/postbid-config.js') @@ -293,7 +305,7 @@ function bundle(dev, moduleArr) { // If --notest is given, it will immediately skip the test task (useful for developing changes with `gulp serve --notest`) function testTaskMaker(options = {}) { - ['watch', 'e2e', 'file', 'browserstack', 'notest'].forEach(opt => { + ['watch', 'file', 'browserstack', 'notest'].forEach(opt => { options[opt] = options.hasOwnProperty(opt) ? options[opt] : argv[opt]; }) @@ -302,22 +314,6 @@ function testTaskMaker(options = {}) { return function test(done) { if (options.notest) { done(); - } else if (options.e2e) { - const integ = startIntegServer(); - startLocalServer(); - runWebdriver(options) - .then(stdout => { - // kill fake server - integ.kill('SIGINT'); - done(); - process.exit(0); - }) - .catch(err => { - // kill fake server - integ.kill('SIGINT'); - done(new Error(`Tests failed with error: ${err}`)); - process.exit(1); - }); } else { runKarma(options, done) } @@ -326,10 +322,34 @@ function testTaskMaker(options = {}) { const test = testTaskMaker(); +function e2eTestTaskMaker() { + return function test(done) { + const integ = startIntegServer(); + startLocalServer(); + runWebdriver({}) + .then(stdout => { + // kill fake server + integ.kill('SIGINT'); + done(); + process.exit(0); + }) + .catch(err => { + // kill fake server + integ.kill('SIGINT'); + done(new Error(`Tests failed with error: ${err}`)); + process.exit(1); + }); + } +} + function runWebdriver({file}) { process.env.TEST_SERVER_HOST = argv.host || 'localhost'; + + let local = argv.local || false; + + let wdioConfFile = local === true ? 'wdio.local.conf.js' : 'wdio.conf.js'; let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio'); - let wdioConf = path.join(__dirname, 'wdio.conf.js'); + let wdioConf = path.join(__dirname, wdioConfFile); let wdioOpts; if (file) { @@ -486,8 +506,9 @@ gulp.task('serve-e2e-dev', gulp.series(clean, 'build-bundle-dev', gulp.parallel( gulp.task('default', gulp.series(clean, 'build-bundle-prod')); -gulp.task('e2e-test-only', () => runWebdriver({file: argv.file})); -gulp.task('e2e-test', gulp.series(clean, 'build-bundle-prod', testTaskMaker({e2e: true}))); +gulp.task('e2e-test-only', gulp.series(requireNodeVersion(16), () => runWebdriver({file: argv.file}))); +gulp.task('e2e-test', gulp.series(requireNodeVersion(16), clean, 'build-bundle-prod', e2eTestTaskMaker())); + // other tasks gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step diff --git a/package-lock.json b/package-lock.json index 87110627d26..692afaa120a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.32.0-pre", + "version": "8.33.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -26,13 +26,12 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "~7.16.0", - "@wdio/cli": "~7.5.2", - "@wdio/concise-reporter": "~7.5.2", - "@wdio/local-runner": "~7.5.2", - "@wdio/mocha-framework": "~7.5.2", - "@wdio/spec-reporter": "~7.19.0", - "@wdio/sync": "~7.5.2", + "@wdio/browserstack-service": "^8.29.0", + "@wdio/cli": "^8.29.0", + "@wdio/concise-reporter": "^8.29.0", + "@wdio/local-runner": "^8.29.0", + "@wdio/mocha-framework": "^8.29.0", + "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", @@ -110,7 +109,7 @@ "yargs": "^1.3.1" }, "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" @@ -1602,19 +1601,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz", - "integrity": "sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg==", - "dev": true, - "dependencies": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -1911,6 +1897,102 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1936,20 +2018,45 @@ "node": ">=8" } }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "dependencies": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/types/node_modules/ansi-styles": { @@ -2088,6 +2195,18 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@ljharb/through": { + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz", + "integrity": "sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2097,12 +2216,142 @@ "eslint-scope": "5.1.1" } }, + "node_modules/@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", + "dev": true + }, + "node_modules/@percy/appium-app": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.3.tgz", + "integrity": "sha512-6INeUJSyK2LzWV4Cc9bszNqKr3/NLcjFelUC2grjPnm6+jLA29inBF4ZE3PeTfLeCSw/0jyCGWV5fr9AyxtzCA==", + "dev": true, + "dependencies": { + "@percy/sdk-utils": "^1.27.0-beta.0", + "tmp": "^0.2.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@percy/appium-app/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@percy/sdk-utils": { + "version": "1.27.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.27.7.tgz", + "integrity": "sha512-E21dIEQ9wwGDno41FdMDYf6jJow5scbWGClqKE/ptB+950W4UF5C4hxhVVQoEJxDdLE/Gy/8ZJR7upvPHShWDg==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@percy/selenium-webdriver": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.3.tgz", + "integrity": "sha512-JfLJVRkwNfqVofe7iGKtoQbOcKSSj9t4pWFbSUk95JfwAA7b9/c+dlBsxgIRrdrMYzLRjnJkYAFSZkJ4F4A19A==", + "dev": true, + "dependencies": { + "@percy/sdk-utils": "^1.27.2", + "node-request-interceptor": "^0.6.3" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@puppeteer/browsers": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", + "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -2168,6 +2417,23 @@ "node": ">=10" } }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "node_modules/@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", @@ -2210,24 +2476,6 @@ "@types/ms": "*" } }, - "node_modules/@types/diff": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", - "integrity": "sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==", - "dev": true - }, - "node_modules/@types/easy-table": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.33.tgz", - "integrity": "sha512-/vvqcJPmZUfQwCgemL0/34G7bIQnCuvgls379ygRlcC1FqNqk3n+VZ15dAO51yl6JNDoWd8vsk+kT8zfZ1VZSw==", - "dev": true - }, - "node_modules/@types/ejs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.1.tgz", - "integrity": "sha512-RQul5wEfY7BjWm0sYY86cmUN/pcXWGyVxWX93DFFJvcrxax5zKlieLwA3T77xJGwNcZW0YW6CYG70p1m8xPFmA==", - "dev": true - }, "node_modules/@types/eslint": { "version": "8.4.9", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", @@ -2266,21 +2514,12 @@ "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", "dev": true }, - "node_modules/@types/fibers": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", - "integrity": "sha512-yHoUi46uika0snoTpNcVqUSvgbRndaIps4TUCotrXjtc0DHDoPQckmyXEZ2bX3e4mpJmyEW3hRhCwQa/ISCPaA==", + "node_modules/@types/gitconfiglocal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/gitconfiglocal/-/gitconfiglocal-2.0.3.tgz", + "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "node_modules/@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/github-slugger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", @@ -2297,40 +2536,30 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, - "node_modules/@types/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==", - "dev": true, - "dependencies": { - "@types/through": "*", - "rxjs": "^6.4.0" - } - }, "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" @@ -2358,39 +2587,6 @@ "keyv": "*" } }, - "node_modules/@types/lodash": { - "version": "4.14.187", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.187.tgz", - "integrity": "sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A==", - "dev": true - }, - "node_modules/@types/lodash.flattendeep": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.7.tgz", - "integrity": "sha512-1h6GW/AeZw/Wej6uxrqgmdTDZX1yFS39lRsXYkg+3kWvOWWrlGCI6H7lXxlUHOzxDT4QeYGmgPpQ3BX9XevzOg==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/lodash.pickby": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz", - "integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/lodash.union": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.7.tgz", - "integrity": "sha512-6HXM6tsnHJzKgJE0gA/LhTGf/7AbjUk759WZ1MziVm+OBNAATHhdgj+a3KVE8g76GCLAnN4ZEQQG1EGgtBIABA==", - "dev": true, - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -2401,9 +2597,9 @@ } }, "node_modules/@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "node_modules/@types/ms": { @@ -2413,10 +2609,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "version": "20.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", + "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2424,30 +2623,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "node_modules/@types/object-inspect": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/object-inspect/-/object-inspect-1.8.1.tgz", - "integrity": "sha512-0JTdf3CGV0oWzE6Wa40Ayv2e2GhpP3pEJMcrlM74vBSJPuuNkVwfDnl0SZxyFCXETcB4oKA/MpTVfuYSMOelBg==", - "dev": true - }, - "node_modules/@types/puppeteer": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.7.tgz", - "integrity": "sha512-JdGWZZYL0vKapXF4oQTC5hLVNfOgdPrqeZ1BiQnGk5cB7HeE91EWUiTdVSdQPobRN8rIcdffjiOgCYJ/S8QrnQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/recursive-readdir": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.1.tgz", - "integrity": "sha512-Xd+Ptc4/F2ueInqy5yK2FI5FxtwwbX2+VZpcg+9oYsFJVen8qQKGapCr+Bi5wQtHU1cTXT8s+07lo/nKPgu8Gg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -2458,39 +2633,21 @@ } }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "node_modules/@types/stream-buffers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", - "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", "dev": true }, - "node_modules/@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, "node_modules/@types/ua-parser-js": { @@ -2521,19 +2678,28 @@ "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, "node_modules/@types/yauzl": { @@ -2546,12 +2712,6 @@ "@types/node": "*" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "node_modules/@videojs/http-streaming": { "version": "2.14.3", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz", @@ -2601,6 +2761,38 @@ "is-function": "^1.0.1" } }, + "node_modules/@vitest/snapshot": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", + "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@vue/compiler-core": { "version": "3.2.41", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", @@ -2697,75 +2889,142 @@ "optional": true }, "node_modules/@wdio/browserstack-service": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-7.16.16.tgz", - "integrity": "sha512-q4wUh/j0MR2SwhTkmIFif2DaXgH5yzdgOer6G/fac2n81zLCSpQHWO5aQ9T0An9CAd4L2A+t3dmChpBJPkHWSw==", - "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "browserstack-local": "^1.4.5", - "got": "^11.0.2", - "webdriverio": "7.16.16" + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.29.1.tgz", + "integrity": "sha512-dLEJcdVF0Cu+2REByVOfLUzx9FvMias1VsxSCZpKXeIAGAIWBBdNdooK6Vdc9QdS36S5v/mk0/rTTQhYn4nWjQ==", + "dev": true, + "dependencies": { + "@percy/appium-app": "^2.0.1", + "@percy/selenium-webdriver": "^2.0.3", + "@types/gitconfiglocal": "^2.0.1", + "@wdio/logger": "8.28.0", + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "browserstack-local": "^1.5.1", + "chalk": "^5.3.0", + "csv-writer": "^1.6.0", + "formdata-node": "5.0.1", + "git-repo-info": "^2.1.1", + "gitconfiglocal": "^2.1.0", + "got": "^12.6.1", + "uuid": "^9.0.0", + "webdriverio": "8.29.1", + "winston-transport": "^4.5.0", + "yauzl": "^2.10.0" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" }, "peerDependencies": { - "@wdio/cli": "^7.0.0" + "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/config": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.16.tgz", - "integrity": "sha512-K/ObPuo6Da2liz++OKOIfbdpFwI7UWiFcBylfJkCYbweuXCoW1aUqlKI6rmKPwCH9Uqr/RHWu6p8eo0zWe6xVA==", + "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/protocols": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", - "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", + "node_modules/@wdio/browserstack-service/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/repl": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.14.tgz", - "integrity": "sha512-Ezih0Y+lsGkKv3H3U56hdWgZiQGA3VaAYguSLd9+g1xbQq+zMKqSmfqECD9bAy+OgCCiVTRstES6lHZxJVPhAg==", + "node_modules/@wdio/browserstack-service/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "@wdio/utils": "7.16.14" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/browserstack-service/node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dev": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + }, + "engines": { + "node": ">= 12.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/@wdio/utils": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.14.tgz", - "integrity": "sha512-wwin8nVpIlhmXJkq6GJw9aDDzgLOJKgXTcEua0T2sdXjoW78u5Ly/GZrFXTjMGhacFvoZfitTrjyfyy4CxMVvw==", + "node_modules/@wdio/browserstack-service/node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, "dependencies": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "p-iteration": "^1.1.8" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, + "node_modules/@wdio/browserstack-service/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, "node_modules/@wdio/browserstack-service/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2775,2767 +3034,4356 @@ "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", - "integrity": "sha512-M0kzkuSgfEhpqIis3gdtWsNjn/HQ+vRAmEzDnbYx/7FfjFxhSv1d+rOOT20pvd60soItMYpsOova1igACEGkGQ==", + "node_modules/@wdio/browserstack-service/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, - "dependencies": { - "@types/node": "^17.0.4", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^8.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { - "version": "0.0.973690", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.973690.tgz", - "integrity": "sha512-myh3hSFp0YWa2GED11PmbLhV4dv9RdO7YUz27XJrbQLnP5bMbZL6dfOOILTHO57yH0kX5GfuOZBsg/4NamfPvQ==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "node_modules/@wdio/browserstack-service/node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" } }, - "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "node_modules/@wdio/browserstack-service/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], "engines": { - "node": "*" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/@wdio/browserstack-service/node_modules/chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, "bin": { - "uuid": "dist/bin/uuid" + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriver": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.16.tgz", - "integrity": "sha512-x8UoG9k/P8KDrfSh1pOyNevt9tns3zexoMxp9cKnyA/7HYSErhZYTLGlgxscAXLtQG41cMH/Ba/oBmOx7Hgd8w==", + "node_modules/@wdio/browserstack-service/node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, "dependencies": { - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "got": "^11.0.2", - "ky": "^0.29.0", - "lodash.merge": "^4.6.1" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.16.tgz", - "integrity": "sha512-caPaEWyuD3Qoa7YkW4xCCQA4v9Pa9wmhFGPvNZh3ERtjMCNi8L/XXOdkekWNZmFh3tY0kFguBj7+fAwSY7HAGw==", + "node_modules/@wdio/browserstack-service/node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, "dependencies": { - "@types/aria-query": "^5.0.0", - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.14", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "archiver": "^5.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.16.16", - "devtools-protocol": "^0.0.973690", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^5.0.0", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.16.16" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/cli": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.5.7.tgz", - "integrity": "sha512-nOQJLskrY+UECDd3NxE7oBzb6cDA7e7x02YWQugOlOgnZ4a+PJmkFoSsO8C2uNCpdFngy5rJKGUo5vbtAHEF9Q==", - "dev": true, - "dependencies": { - "@types/ejs": "^3.0.5", - "@types/fs-extra": "^9.0.4", - "@types/inquirer": "^7.3.1", - "@types/lodash.flattendeep": "^4.4.6", - "@types/lodash.pickby": "^4.6.6", - "@types/lodash.union": "^4.6.6", - "@types/recursive-readdir": "^2.2.0", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "async-exit-hook": "^2.0.1", - "chalk": "^4.0.0", - "chokidar": "^3.0.0", - "cli-spinners": "^2.1.0", - "ejs": "^3.0.1", - "fs-extra": "^10.0.0", - "inquirer": "^8.0.0", - "lodash.flattendeep": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.union": "^4.6.0", - "mkdirp": "^1.0.4", - "recursive-readdir": "^2.2.2", - "webdriverio": "7.5.7", - "yargs": "^17.0.0", - "yarn-install": "^1.0.0" - }, - "bin": { - "wdio": "bin/wdio.js" - }, - "engines": { - "node": ">=12.0.0" + "node_modules/@wdio/browserstack-service/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/cli/node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/browserstack-service/node_modules/devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/cli/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "got": "^11.8.1" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@wdio/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/browserstack-service/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=8" + "node": ">=14.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/@wdio/cli/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "node_modules/@wdio/browserstack-service/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "optional": true, + "peer": true, + "engines": { + "node": ">=10" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "engines": { - "node": ">=6.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/browserstack-service/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "node_modules/@wdio/browserstack-service/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@wdio/cli/node_modules/chrome-launcher/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/@wdio/browserstack-service/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "dependencies": { - "minimist": "^1.2.6" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/browserstack-service/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/@wdio/cli/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "node_modules/@wdio/browserstack-service/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=10.19.0" } }, - "node_modules/@wdio/cli/node_modules/devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/browserstack-service/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "optional": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/@wdio/cli/node_modules/puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "engines": { - "node": ">=10.18.1" + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "has-flag": "^4.0.0" + "ms": "2.0.0" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/@wdio/browserstack-service/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, - "bin": { - "uuid": "dist/bin/uuid" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", - "dev": true, - "dependencies": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "node_modules/@wdio/browserstack-service/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/cli/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "node_modules/@wdio/browserstack-service/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/browserstack-service/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": ">=8.3.0" + "node": "4.x || >=6.0.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "encoding": "^0.1.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "encoding": { "optional": true } } }, - "node_modules/@wdio/cli/node_modules/yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "node_modules/@wdio/browserstack-service/node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "engines": { + "node": ">=14.16" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, "engines": { - "node": ">=12" + "node": ">=12.20" } }, - "node_modules/@wdio/concise-reporter": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.5.7.tgz", - "integrity": "sha512-964i7eQ4sboSla2bdR8714Er82QBgS6u39GmDFX8Izy9Ge38xaE75HuF5S7mnOWGzSojCWgqtwy5k7Rfg6GE3g==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, "dependencies": { - "@wdio/reporter": "7.5.7", - "@wdio/types": "7.5.3", - "chalk": "^4.0.0", - "pretty-ms": "^7.0.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@wdio/cli": "^7.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/concise-reporter/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "dependencies": { - "got": "^11.8.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/concise-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, - "node_modules/@wdio/concise-reporter/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/browserstack-service/node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 14" } }, - "node_modules/@wdio/concise-reporter/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/concise-reporter/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/concise-reporter/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/@wdio/concise-reporter/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/browserstack-service/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "lowercase-keys": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/config": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.5.3.tgz", - "integrity": "sha512-udvVizYoilOxuWj/BmoN6y7ZCd4wPdYNlSfWznrbCezAdaLZ4/pNDOO0WRWx2C4+q1wdkXZV/VuQPUGfL0lEHQ==", + "node_modules/@wdio/browserstack-service/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "dependencies": { - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" + "type-fest": "^2.12.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/config/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/browserstack-service/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=12.0.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/@wdio/config/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/browserstack-service/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "dependencies": { - "got": "^11.8.1" + "engines": { + "node": ">=12.20" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "optional": true, + "peer": true, "engines": { - "node": ">=12.0.0" + "node": "*" } }, - "node_modules/@wdio/config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/browserstack-service/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" }, "engines": { - "node": ">=8" + "node": "^16.13 || >=18" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } } }, - "node_modules/@wdio/config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=10" + "node": ">=16.3.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "mitt": "3.0.0" }, - "engines": { - "node": ">=7.0.0" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/@wdio/config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/config/node_modules/has-flag": { + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/cross-fetch": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "node-fetch": "^2.6.12" } }, - "node_modules/@wdio/config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" }, "engines": { - "node": ">=8" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/local-runner": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.5.7.tgz", - "integrity": "sha512-aYc0XUV+/e3cg8Fp+CWlC4FbwSSG3mKAv1iuy/+Hwzg2kJE+aa+Rf2p2BQYc7HPRtKNW0bM8o+aCImZLAiPM+A==", + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/@wdio/browserstack-service/node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "dependencies": { - "@types/stream-buffers": "^3.0.3", - "@wdio/logger": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/runner": "7.5.7", - "@wdio/types": "7.5.3", - "async-exit-hook": "^2.0.1", - "split2": "^3.2.2", - "stream-buffers": "^3.0.2" - }, + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, "engines": { - "node": ">=12.0.0" + "node": ">=10.0.0" }, "peerDependencies": { - "@wdio/cli": "^7.0.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/browserstack-service/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/@wdio/local-runner/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/browserstack-service/node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", "dev": true, "dependencies": { - "got": "^11.8.1" + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/local-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" + "node_modules/@wdio/cli": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.29.1.tgz", + "integrity": "sha512-WWRTf0g0O+ovTTvS1kEhZ/svX32M7jERuuMF1MaldKCi7rZwHsQqOyJD+fO1UDjuxqS96LHSGsZn0auwUfCTXA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.1", + "@vitest/snapshot": "^1.2.1", + "@wdio/config": "8.29.1", + "@wdio/globals": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "async-exit-hook": "^2.0.1", + "chalk": "^5.2.0", + "chokidar": "^3.5.3", + "cli-spinners": "^2.9.0", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "execa": "^8.0.1", + "import-meta-resolve": "^4.0.0", + "inquirer": "9.2.12", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "read-pkg-up": "^10.0.0", + "recursive-readdir": "^2.2.3", + "webdriverio": "8.29.1", + "yargs": "^17.7.2" }, - "engines": { - "node": ">=8" + "bin": { + "wdio": "bin/wdio.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/local-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/local-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=12" } }, - "node_modules/@wdio/local-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/local-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/cli/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true, - "engines": { - "node": ">=8" - } + "optional": true, + "peer": true }, - "node_modules/@wdio/local-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/cli/node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/logger": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.16.0.tgz", - "integrity": "sha512-/6lOGb2Iow5eSsy7RJOl1kCwsP4eMlG+/QKro5zUJsuyNJSQXf2ejhpkzyKWLgQbHu83WX6cM1014AZuLkzoQg==", + "node_modules/@wdio/cli/node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/logger/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/cli/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/@wdio/cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/logger/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/cli/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/logger/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/cli/node_modules/chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@wdio/logger/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/logger/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">=12.13.0" } }, - "node_modules/@wdio/logger/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/cli/node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=8" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/mocha-framework": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.5.3.tgz", - "integrity": "sha512-96QCVWsiyZxEgOZP3oTq2B2T7zne5dCdehLa2n4q/BLjk96Rj0jifidJZfd/1+vdNPKX0gWWAzpy98Znn8MVMw==", + "node_modules/@wdio/cli/node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, "dependencies": { - "@types/mocha": "^8.0.0", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "expect-webdriverio": "^2.0.0", - "mocha": "^8.0.1" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/mocha-framework/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/cli/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=12.0.0" + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/mocha-framework/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/cli/node_modules/devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "got": "^11.8.1" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/mocha-framework/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@wdio/cli/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, - "engines": { - "node": ">=6" - } + "optional": true, + "peer": true }, - "node_modules/@wdio/mocha-framework/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/cli/node_modules/devtools/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "isexe": "^3.1.1" }, - "engines": { - "node": ">=8" + "bin": { + "node-which": "bin/which.js" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@wdio/mocha-framework/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/cli/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/@wdio/mocha-framework/node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "node_modules/@wdio/cli/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, + "optional": true, + "peer": true, "engines": { - "node": ">= 8.10.0" + "node": ">=10" }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/cli/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@wdio/mocha-framework/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/@wdio/cli/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@wdio/mocha-framework/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/@wdio/cli/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/@wdio/cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/mocha-framework/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/cli/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/@wdio/mocha-framework/node_modules/js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "node_modules/@wdio/cli/node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", "dev": true, "dependencies": { - "argparse": "^2.0.1" + "lru-cache": "^10.0.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@wdio/mocha-framework/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/@wdio/cli/node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "14 || >=16.14" } }, - "node_modules/@wdio/mocha-framework/node_modules/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "node_modules/@wdio/cli/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "chalk": "^4.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 6" } }, - "node_modules/@wdio/mocha-framework/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/@wdio/cli/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "node_modules/@wdio/cli/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@wdio/cli/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/@wdio/cli/node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/@wdio/cli/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" }, "engines": { - "node": ">= 10.12.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "node_modules/@wdio/cli/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@wdio/mocha-framework/node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@wdio/cli/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/mocha-framework/node_modules/nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "node_modules/@wdio/cli/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/cli/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@wdio/mocha-framework/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/@wdio/cli/node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", "dev": true, "dependencies": { - "yocto-queue": "^0.1.0" + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@wdio/mocha-framework/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/@wdio/cli/node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", "dev": true, "dependencies": { - "p-limit": "^3.0.2" + "path-key": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "node_modules/@wdio/cli/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "dependencies": { - "picomatch": "^2.2.1" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "node_modules/@wdio/cli/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/@wdio/mocha-framework/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "yocto-queue": "^1.0.0" + }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/cli/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, - "node_modules/@wdio/mocha-framework/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/@wdio/cli/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "dev": true, "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" }, "engines": { - "node": ">=10" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/mocha-framework/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "node_modules/@wdio/cli/node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/protocols": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.5.3.tgz", - "integrity": "sha512-lpNaKwxYhDSL6neDtQQYXvzMAw+u4PXx65ryeMEX82mkARgzSZps5Kyrg9ub7X4T17K1NPfnY6UhZEWg6cKJCg==", + "node_modules/@wdio/cli/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "engines": { - "node": ">=12.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/@wdio/repl": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.5.3.tgz", - "integrity": "sha512-jfNJwNoc2nWdnLsFoGHmOJR9zaWfDTBMWM3W1eR5kXIjevD6gAfWsB5ZoA4IdybujCXxdnhlsm4o2jIzp/6f7A==", + "node_modules/@wdio/cli/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, - "dependencies": { - "@wdio/utils": "7.5.3" - }, "engines": { - "node": ">=12.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/reporter": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.5.7.tgz", - "integrity": "sha512-9PXqZtCXDtU6UYLNDPu9MZQ8BiABGnRlJTrlbYB3gBfZDibMkJMvwXzPderipBv2+ifDZXmGe3Njf1ao2TkbFA==", + "node_modules/@wdio/cli/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, "dependencies": { - "@wdio/types": "7.5.3", - "fs-extra": "^10.0.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/reporter/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, "dependencies": { - "got": "^11.8.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/runner": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.5.7.tgz", - "integrity": "sha512-RzVXd+xnwK/thkx1/xo9K5iscQ0Ofobgsx5dNVtwLDVMn9V7jCW/WX4dSCPAPaVSqnUCmkcQp3P5AoSBPpCZnQ==", + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, "dependencies": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "deepmerge": "^4.0.0", - "gaze": "^1.1.2", - "webdriver": "7.5.3", - "webdriverio": "7.5.7" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/@wdio/runner/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/cli/node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "dependencies": { - "got": "^11.8.1" - }, "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/@wdio/runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/cli/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-convert": "^2.0.1" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "node_modules/@wdio/cli/node_modules/read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "dev": true, "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" }, "engines": { - "node": ">=6.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/cli/node_modules/read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" }, "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "node_modules/@wdio/cli/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/@wdio/runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/cli/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=7.0.0" + "node": ">=10" } }, - "node_modules/@wdio/runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "node_modules/@wdio/cli/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "dependencies": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "type-fest": "^2.12.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/cli/node_modules/serialize-error/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/@wdio/cli/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "dependencies": { - "minimist": "^1.2.6" + "engines": { + "node": ">=14" }, - "bin": { - "mkdirp": "bin/cmd.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/runner/node_modules/puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "node_modules/@wdio/cli/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "engines": { - "node": ">=10.18.1" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/cli/node_modules/type-fest": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "engines": { + "node": ">=16" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/cli/node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "optional": true, + "peer": true, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/@wdio/runner/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/@wdio/cli/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "peer": true, "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/@wdio/runner/node_modules/webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", - "dev": true, - "dependencies": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", + "node_modules/@wdio/cli/node_modules/webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", + "devtools-protocol": "^0.0.1249869", "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" + }, + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } } }, - "node_modules/@wdio/runner/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, "engines": { - "node": ">=8.3.0" + "node": ">=16.3.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "typescript": ">= 4.7.4" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "typescript": { "optional": true } } }, - "node_modules/@wdio/spec-reporter": { - "version": "7.19.7", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.7.tgz", - "integrity": "sha512-BDBZU2EK/GuC9VxtfqPtoW43FmvKxYDsvcDVDi3F7o+9fkcuGSJiWbw1AX251ZzzVQ7YP9ImTitSpdpUKXkilQ==", + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, "dependencies": { - "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.19.7", - "@wdio/types": "7.19.5", - "chalk": "^4.0.0", - "easy-table": "^1.1.1", - "pretty-ms": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" + "mitt": "3.0.0" }, "peerDependencies": { - "@wdio/cli": "^7.0.0" + "devtools-protocol": "*" } }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/reporter": { - "version": "7.19.7", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.7.tgz", - "integrity": "sha512-Dum19gpfru66FnIq78/4HTuW87B7ceLDp6PJXwQM5kXyN7Gb7zhMgp6FZTM0FCYLyi6U/zXZSvpNUYl77caS6g==", + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, "dependencies": { - "@types/diff": "^5.0.0", - "@types/node": "^17.0.4", - "@types/object-inspect": "^1.8.0", - "@types/supports-color": "^8.1.0", - "@types/tmp": "^0.2.0", - "@wdio/types": "7.19.5", - "diff": "^5.0.0", - "fs-extra": "^10.0.0", - "object-inspect": "^1.10.3", - "supports-color": "8.1.1" - }, - "engines": { - "node": ">=12.0.0" + "node-fetch": "^2.6.12" } }, - "node_modules/@wdio/spec-reporter/node_modules/@wdio/types": { - "version": "7.19.5", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.5.tgz", - "integrity": "sha512-S1lC0pmtEO7NVH/2nM1c7NHbkgxLZH3VVG/z6ym3Bbxdtcqi2LMsEvvawMAU/fmhyiIkMsGZCO8vxG9cRw4z4A==", + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, "dependencies": { - "@types/node": "^17.0.4", - "got": "^11.8.1" + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=16.3.0" }, "peerDependencies": { - "typescript": "^4.6.2" + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/spec-reporter/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/@wdio/cli/node_modules/webdriverio/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" + "node": ">=12" + } + }, + "node_modules/@wdio/cli/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@wdio/spec-reporter/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" + } + }, + "node_modules/@wdio/cli/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/spec-reporter/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/cli/node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=8" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/spec-reporter/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/concise-reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.29.1.tgz", + "integrity": "sha512-dUhClWeq1naL1Qa1nSMDeH8aCVViOKiEzhBhQjgrMOz1Mh3l6O/woqbK2iKDVZDRhfGghtGcV0vpoEUvt8ZKOA==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" }, "engines": { - "node": ">=7.0.0" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/spec-reporter/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/spec-reporter/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/spec-reporter/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/@wdio/config": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz", + "integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.0.0", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/sync": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.5.7.tgz", - "integrity": "sha512-Zu/AYLjwqbFSbaOU1US7ownv3ov8JrtoGHq51JfJ4masefJDXNkHix2cZ0qEgl3IvkkWQ0ewL0G8GTXb3KOemA==", + "node_modules/@wdio/config/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "@types/fibers": "^3.1.0", - "@types/puppeteer": "^5.4.0", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "fibers": "^5.0.0", - "webdriverio": "7.5.7" - }, - "engines": { - "node": ">=12.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/sync/node_modules/@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "node_modules/@wdio/sync/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/config/node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/sync/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/config/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { - "got": "^11.8.1" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/sync/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/config/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/sync/node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "node_modules/@wdio/globals": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.29.1.tgz", + "integrity": "sha512-F+fPnX75f44/crZDfQ2FYSino/IMIdbnQGLIkaH0VnoljVJIHuxnX4y5Zqr4yRgurL9DsZaH22cLHrPXaHUhPg==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, "engines": { - "node": ">=6.0" + "node": "^16.13 || >=18" + }, + "optionalDependencies": { + "expect-webdriverio": "^4.9.3", + "webdriverio": "8.29.1" } }, - "node_modules/@wdio/sync/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@wdio/sync/node_modules/chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "node_modules/@wdio/globals/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" - } + "optional": true, + "peer": true }, - "node_modules/@wdio/sync/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/globals/node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, + "optional": true, "dependencies": { - "color-name": "~1.1.4" + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/sync/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/sync/node_modules/devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", + "node_modules/@wdio/globals/node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, + "optional": true, "dependencies": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/sync/node_modules/devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", - "dev": true + "node_modules/@wdio/globals/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "optional": true }, - "node_modules/@wdio/sync/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/globals/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "engines": { - "node": ">=8" + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/@wdio/sync/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/@wdio/globals/node_modules/chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "minimist": "^1.2.6" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" }, "bin": { - "mkdirp": "bin/cmd.js" + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" } }, - "node_modules/@wdio/sync/node_modules/puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", + "node_modules/@wdio/globals/node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, + "optional": true, "dependencies": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=10.18.1" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/sync/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true - }, - "node_modules/@wdio/sync/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/globals/node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, + "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" }, "engines": { - "node": ">=8" + "node": ">= 12.0.0" } }, - "node_modules/@wdio/sync/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/@wdio/globals/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, - "bin": { - "uuid": "dist/bin/uuid" + "optional": true, + "peer": true, + "dependencies": { + "node-fetch": "^2.6.11" } }, - "node_modules/@wdio/sync/node_modules/webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", + "node_modules/@wdio/globals/node_modules/devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/sync/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "node_modules/@wdio/globals/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } + "optional": true, + "peer": true }, - "node_modules/@wdio/types": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.14.tgz", - "integrity": "sha512-AyNI9iBSos9xWBmiFAF3sBs6AJXO/55VppU/eeF4HRdbZMtMarnvMuahM+jlUrA3vJSmDW+ufelG0MT//6vrnw==", + "node_modules/@wdio/globals/node_modules/devtools/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@types/node": "^17.0.4", - "got": "^11.8.1" + "isexe": "^3.1.1" }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@wdio/utils": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.5.3.tgz", - "integrity": "sha512-nlLDKr8v8abLOHCKroBwQkGPdCIxjID2MllgWX23xqkYZylM9RdwPBdL8osQt9m3rq2TxiPAT4OlbzNt2WtN6Q==", - "dev": true, - "dependencies": { - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3" + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@wdio/utils/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/@wdio/globals/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/@wdio/utils/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/@wdio/globals/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "got": "^11.8.1" - }, + "optional": true, + "peer": true, "engines": { - "node": ">=12.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@wdio/globals/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, + "optional": true, "dependencies": { - "color-convert": "^2.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@wdio/globals/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "optional": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@wdio/globals/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "color-name": "~1.1.4" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=7.0.0" + "node": ">= 6" } }, - "node_modules/@wdio/utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@wdio/utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@wdio/globals/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, + "optional": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">=16" } }, - "node_modules/@wdio/utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@wdio/globals/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "node_modules/@wdio/globals/node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + "ms": "2.0.0" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true + "node_modules/@wdio/globals/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=12" + } }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "node_modules/@wdio/globals/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true + "node_modules/@wdio/globals/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "node_modules/@wdio/globals/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "node_modules/@wdio/globals/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, + "optional": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, + "optional": true, "dependencies": { - "@xtuc/long": "4.2.2" + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "node_modules/@wdio/globals/node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "node_modules/@wdio/globals/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "node_modules/@wdio/globals/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "node_modules/@wdio/globals/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", - "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", + "node_modules/@wdio/globals/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, - "engines": { - "node": ">=10.0.0" + "optional": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "node_modules/@wdio/globals/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "node_modules/@wdio/globals/node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "optional": true, + "peer": true, + "engines": { + "node": "*" + } }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true + "node_modules/@wdio/globals/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@wdio/globals/node_modules/webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "optional": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" }, "engines": { - "node": ">= 0.6" + "node": "^16.13 || >=18" + }, + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } } }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, + "optional": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, "bin": { - "acorn": "bin/acorn" + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, + "optional": true, + "dependencies": { + "mitt": "3.0.0" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "devtools-protocol": "*" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, - "engines": { - "node": ">=0.4.0" + "optional": true, + "dependencies": { + "node-fetch": "^2.6.12" } }, - "node_modules/aes-decrypter": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", - "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.5", - "global": "^4.4.0", - "pkcs7": "^1.0.4" - } + "optional": true }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, + "optional": true, "dependencies": { - "debug": "4" + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "node_modules/@wdio/globals/node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, + "optional": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "node_modules/@wdio/globals/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, + "optional": true, + "engines": { + "node": ">=10.0.0" + }, "peerDependencies": { - "ajv": "^6.9.1" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "node_modules/@wdio/globals/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, "optional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, "engines": { - "node": ">=0.4.2" + "node": ">=12" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/@wdio/globals/node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", "dev": true, + "optional": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + }, "engines": { - "node": ">=6" + "node": ">= 12.0.0" } }, - "node_modules/ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", + "node_modules/@wdio/local-runner": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.29.1.tgz", + "integrity": "sha512-Z3QAgxe1uQ97C7NS1CdMhgmHaLu/sbb47HTbw1yuuLk+SwsBIQGhNpTSA18QVRSUXq70G3bFvjACwqyap1IEQg==", "dev": true, "dependencies": { - "ansi-wrap": "0.1.0" + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/repl": "8.24.12", + "@wdio/runner": "8.29.1", + "@wdio/types": "8.29.1", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" }, "engines": { - "node": ">=0.10.0" + "node": "^16.13 || >=18" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", "dev": true, "dependencies": { - "type-fest": "^0.21.3" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/logger/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, "engines": { - "node": ">=0.10.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", + "node_modules/@wdio/logger/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "dependencies": { - "ansi-wrap": "0.1.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@wdio/mocha-framework": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.29.1.tgz", + "integrity": "sha512-R9dKMNqWgtUvZo33ORjUQV8Z/WLX5h/pg9u/xIvZSGXuNSw1h+5DWF6UiNFscxBFblL9UvBi6V9ila2LHgE4ew==", "dev": true, + "dependencies": { + "@types/mocha": "^10.0.0", + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "mocha": "^10.0.0" + }, "engines": { - "node": ">=8" + "node": "^16.13 || >=18" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@wdio/protocols": { + "version": "8.24.12", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz", + "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==", + "dev": true + }, + "node_modules/@wdio/repl": { + "version": "8.24.12", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", + "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=4" + "node": "^16.13 || >=18" } }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "node_modules/@wdio/reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.29.1.tgz", + "integrity": "sha512-LZeYHC+HHJRYiFH9odaotDazZh0zNhu4mTuL/T/e3c/Q3oPSQjLvfQYhB3Ece1QA9PKjP1VPmr+g9CvC0lMixA==", "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^16.13 || >=18" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "node_modules/@wdio/runner": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.29.1.tgz", + "integrity": "sha512-MvYFf4RgRmzxjAzy6nxvaDG1ycBRvoz772fT06csjxuaVYm57s8mlB8X+U1UQMx/IzujAb53fSeAmNcyU3FNEA==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/globals": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "deepmerge-ts": "^5.0.0", + "expect-webdriverio": "^4.9.3", + "gaze": "^1.1.2", + "webdriver": "8.29.1", + "webdriverio": "8.29.1" }, "engines": { - "node": ">= 8" + "node": "^16.13 || >=18" } }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "buffer-equal": "^1.0.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "node_modules/@wdio/runner/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/runner/node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", + "archiver-utils": "^4.0.1", + "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" }, "engines": { - "node": ">= 10" + "node": ">= 12.0.0" } }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "node_modules/@wdio/runner/node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, "dependencies": { - "glob": "^7.1.4", + "glob": "^8.0.0", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", + "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "readable-stream": "^3.6.0" }, "engines": { - "node": ">= 6" + "node": ">= 12.0.0" } }, - "node_modules/archiver/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "node_modules/@wdio/runner/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/@wdio/runner/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "balanced-match": "^1.0.0" + } + }, + "node_modules/@wdio/runner/node_modules/chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" }, "engines": { - "node": ">= 6" + "node": ">=12.13.0" } }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true + "node_modules/@wdio/runner/node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@wdio/runner/node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, "dependencies": { - "sprintf-js": "~1.0.2" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 12.0.0" } }, - "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "node_modules/@wdio/runner/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "deep-equal": "^2.0.5" + "node-fetch": "^2.6.11" } }, - "node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "node_modules/@wdio/runner/node_modules/devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^16.13 || >=18" } }, - "node_modules/arr-diff/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "node_modules/@wdio/runner/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/runner/node_modules/devtools/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, "engines": { - "node": ">=0.10.0" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", + "node_modules/@wdio/runner/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "make-iterator": "^1.0.0" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "node_modules/@wdio/runner/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "optional": true, + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", + "node_modules/@wdio/runner/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { - "make-iterator": "^1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "node_modules/@wdio/runner/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "node_modules/@wdio/runner/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "node_modules/@wdio/runner/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@wdio/runner/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/@wdio/runner/node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@wdio/runner/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wdio/runner/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@wdio/runner/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/runner/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@wdio/runner/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@wdio/runner/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@wdio/runner/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/runner/node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "optional": true, + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/@wdio/runner/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "engines": { + "node": "^16.13 || >=18" + }, + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + }, + "node_modules/@wdio/runner/node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/@wdio/runner/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@wdio/runner/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wdio/runner/node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@wdio/spec-reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.29.1.tgz", + "integrity": "sha512-tuDHihrTjCxFCbSjT0jMvAarLA1MtatnCnhv0vguu3ZWXELR1uESX2KzBmpJ+chGZz3oCcKszT8HOr6Pg2a1QA==", + "dev": true, + "dependencies": { + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/spec-reporter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/types": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz", + "integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/utils": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "^1.6.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.1.0", + "edgedriver": "^5.3.5", + "geckodriver": "^4.2.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.1.0", + "safaridriver": "^0.1.0", + "split2": "^4.2.0", + "wait-port": "^1.0.4" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/utils/node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", + "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aes-decrypter": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", + "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.4.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", + "dev": true, + "dependencies": { + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", + "dev": true, + "dependencies": { + "buffer-equal": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/archiver": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.3", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-diff/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", + "dev": true, + "dependencies": { + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5721,6 +7569,24 @@ "node": ">=0.10.0" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -5823,6 +7689,12 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, "node_modules/babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -6317,6 +8189,15 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/basic-ftp": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", + "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -6748,158 +8629,6 @@ "node": ">= 0.8" } }, - "node_modules/cac": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", - "integrity": "sha512-hq4rxE3NT5PlaEiVV39Z45d6MoFcQZG5dsgJqtAUeOz3408LEQAElToDkf9i5IYSCOmK0If/81dLg7nKxqPR0w==", - "dev": true, - "dependencies": { - "camelcase-keys": "^3.0.0", - "chalk": "^1.1.3", - "indent-string": "^3.0.0", - "minimist": "^1.2.0", - "read-pkg-up": "^1.0.1", - "suffix": "^0.1.0", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cac/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "dependencies": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/cac/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/cac/node_modules/path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "dependencies": { - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "dependencies": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "dependencies": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/cac/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cac/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -6963,12 +8692,13 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6992,28 +8722,6 @@ "node": ">=6" } }, - "node_modules/camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha512-U4E6A6aFyYnNW+tDt5/yIUKQURKXe3WMFPfX4FxrQFcwZ/R08AUk1xWcUtlr7oq6CV07Ji+aa69V2g7BSpblnQ==", - "dev": true, - "dependencies": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/camelcase-keys/node_modules/camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/can-autoplay": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz", @@ -7211,6 +8919,35 @@ "node": ">=6.0" } }, + "node_modules/chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -7328,9 +9065,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "engines": { "node": ">=6" @@ -7935,6 +9672,12 @@ "node": ">=0.10.0" } }, + "node_modules/csv-writer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", + "integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==", + "dev": true + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -7963,6 +9706,15 @@ "node": ">=0.10" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -8138,6 +9890,15 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -8189,6 +9950,19 @@ "node": ">=10" } }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -8218,6 +9992,73 @@ "node": ">=0.10.0" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/degenerator/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/degenerator/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/degenerator/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -8274,18 +10115,6 @@ "node": ">=0.10.0" } }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -8560,12 +10389,12 @@ } }, "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/dlv": { @@ -8813,6 +10642,18 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", + "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -8941,9 +10782,154 @@ "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", "dev": true, - "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" + "dependencies": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "node_modules/edgedriver": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz", + "integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@wdio/logger": "^8.16.17", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "node-fetch": "^3.3.2", + "unzipper": "^0.10.14", + "which": "^4.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + } + }, + "node_modules/edgedriver/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true + }, + "node_modules/edgedriver/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/edgedriver/node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/edgedriver/node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/edgedriver/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "dependencies": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" + } + }, + "node_modules/edgedriver/node_modules/edge-paths/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/edgedriver/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/edgedriver/node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/edgedriver/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/edgedriver/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/ee-first": { @@ -8952,9 +10938,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -9909,424 +11895,1167 @@ "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", "dev": true, "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "optional": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-stream/node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expect-webdriverio": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.9.3.tgz", + "integrity": "sha512-ASHsFc/QaK5ipF4ct3e8hd3elm8wNXk/Qa3EemtYDmfUQ4uzwqDf75m/QFQpwVNCjEpkNP7Be/6X9kz7bN0P9Q==", + "dev": true, + "dependencies": { + "@vitest/snapshot": "^1.2.1", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=16 || >=18 || >=20" + }, + "optionalDependencies": { + "@wdio/globals": "^8.27.0", + "@wdio/logger": "^8.24.12", + "webdriverio": "^8.27.0" + } + }, + "node_modules/expect-webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/expect-webdriverio/node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/expect-webdriverio/node_modules/archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dev": true, + "optional": true, + "dependencies": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dev": true, + "optional": true, + "dependencies": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "optional": true + }, + "node_modules/expect-webdriverio/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/expect-webdriverio/node_modules/compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "dev": true, + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "dev": true, + "optional": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/expect-webdriverio/node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "node-fetch": "^2.6.11" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/expect-webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/expect-webdriverio/node_modules/devtools/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=0.10.0" + "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "node_modules/expect-webdriverio/node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "estraverse": "^5.1.0" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=0.10" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/expect-webdriverio/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "optional": true, + "peer": true, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/expect-webdriverio/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, + "optional": true, "dependencies": { - "estraverse": "^5.2.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">=4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/expect-webdriverio/node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=4.0" + "node": ">=10" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/expect-webdriverio/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=4.0" + "node": ">= 6" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/expect-webdriverio/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "optional": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "optional": true, + "peer": true, "engines": { - "node": ">= 0.6" + "node": ">=16" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "node_modules/expect-webdriverio/node_modules/lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "debug": "^2.6.9", + "marky": "^1.2.2" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "node_modules/expect-webdriverio/node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "ms": "2.0.0" } }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/expect-webdriverio/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "optional": true, "engines": { - "node": ">=0.8.x" + "node": ">=12" } }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/expect-webdriverio/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "optional": true, "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/expect-webdriverio/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/expect-webdriverio/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, + "optional": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=4.8" + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/expect-webdriverio/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, + "optional": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" + "node": ">= 14" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", "dev": true, + "optional": true, "dependencies": { - "shebang-regex": "^1.0.0" + "debug": "^4.3.4" }, "engines": { - "node": ">=0.10.0" + "node": ">= 14" } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, + "optional": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 14" } }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/expect-webdriverio/node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", "dev": true, + "optional": true, "dependencies": { - "isexe": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">= 14" } }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "node_modules/expect-webdriverio/node_modules/puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/expect-webdriverio/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, "dependencies": { - "ms": "2.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/expect-webdriverio/node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, + "optional": true, "dependencies": { - "is-descriptor": "^0.1.0" + "type-fest": "^2.12.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/expect-webdriverio/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "optional": true, "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "node_modules/expect-webdriverio/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, + "optional": true, "engines": { - "node": ">=0.10.0" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/expect-webdriverio/node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "optional": true, + "peer": true, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/expand-brackets/node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "node_modules/expect-webdriverio/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "node_modules/expect-webdriverio/node_modules/webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", "dev": true, + "optional": true, "dependencies": { - "kind-of": "^3.0.2" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" }, "engines": { - "node": ">=0.10.0" + "node": "^16.13 || >=18" + }, + "peerDependencies": { + "devtools": "^8.14.0" + }, + "peerDependenciesMeta": { + "devtools": { + "optional": true + } } }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", "dev": true, + "optional": true, "dependencies": { - "is-buffer": "^1.1.5" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", "dev": true, + "optional": true, "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "mitt": "3.0.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, - "engines": { - "node": ">=0.10.0" + "optional": true, + "dependencies": { + "node-fetch": "^2.6.12" } }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true, + "optional": true }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", "dev": true, + "optional": true, "dependencies": { - "homedir-polyfill": "^1.0.1" + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + }, + "node_modules/expect-webdriverio/node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", "dev": true, + "optional": true, "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "node_modules/expect-webdriverio": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-2.0.2.tgz", - "integrity": "sha512-dst0tqP1aZ2p7TPmbatqoIQ+7hRTw+IeKNi830XxKhu2DNNe5vQ85i9ttf9rpXgbnUf91HxKcocn4G7A5bQxDA==", + "node_modules/expect-webdriverio/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, - "dependencies": { - "expect": "^26.6.2", - "jest-matcher-utils": "^26.6.2" + "optional": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/expect-webdriverio/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, + "optional": true, "dependencies": { - "color-convert": "^2.0.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=12" } }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/expect-webdriverio/node_modules/zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", "dev": true, + "optional": true, "dependencies": { - "color-name": "~1.1.4" + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=7.0.0" + "node": ">= 12.0.0" } }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -10560,6 +13289,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -10593,17 +13328,42 @@ "pend": "~1.2.0" } }, - "node_modules/fibers": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.3.tgz", - "integrity": "sha512-/qYTSoZydQkM21qZpGLDLuCq8c+B8KhuCQ1kLPvnRNhxhVbvrpmH9l2+Lblf5neDuEsY4bfT7LeO553TXQDvJw==", + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dev": true, - "hasInstallScript": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], "dependencies": { - "detect-libc": "^1.0.3" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" }, "engines": { - "node": ">=10.0.0" + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-blob/node_modules/web-streams-polyfill": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", + "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "dev": true, + "engines": { + "node": ">= 8" } }, "node_modules/figures": { @@ -10659,9 +13419,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -11064,6 +13824,34 @@ "integrity": "sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==", "dev": true }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -11093,6 +13881,40 @@ "node": ">= 0.12" } }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", + "integrity": "sha512-8xnIjMYGKPj+rY2BTbAmpqVpi8der/2FT4d9f7J32FlsCpO5EzZPq3C/N56zdv8KweHzVF6TGijsS1JT6r1H2g==", + "dev": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -11285,9 +14107,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -11334,6 +14159,166 @@ "node": ">= 4.0.0" } }, + "node_modules/geckodriver": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz", + "integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@wdio/logger": "^8.24.12", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.4", + "unzipper": "^0.10.14", + "which": "^4.0.0" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": "^16.13 || >=18 || >=20" + } + }, + "node_modules/geckodriver/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/geckodriver/node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/geckodriver/node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/geckodriver/node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/geckodriver/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/geckodriver/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/geckodriver/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/geckodriver/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/geckodriver/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/geckodriver/node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/geckodriver/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -11361,13 +14346,14 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11383,12 +14369,12 @@ } }, "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", + "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11422,6 +14408,53 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/get-uri/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/get-uri/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -11440,6 +14473,15 @@ "assert-plus": "^1.0.0" } }, + "node_modules/git-repo-info": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.1.tgz", + "integrity": "sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==", + "dev": true, + "engines": { + "node": ">= 4.0" + } + }, "node_modules/git-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", @@ -11459,6 +14501,21 @@ "git-up": "^7.0.0" } }, + "node_modules/gitconfiglocal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz", + "integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==", + "dev": true, + "dependencies": { + "ini": "^1.3.2" + } + }, + "node_modules/gitconfiglocal/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/github-slugger": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", @@ -11994,6 +15051,17 @@ "node": ">= 0.10" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/got": { "version": "11.8.5", "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", @@ -12031,15 +15099,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, "node_modules/gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -13761,12 +16820,22 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13867,6 +16936,17 @@ "node": ">=0.10.0" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-is-element": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", @@ -13935,6 +17015,12 @@ "he": "bin/he" } }, + "node_modules/headers-utils": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.5.tgz", + "integrity": "sha512-DAzV5P/pk3wTU/8TLZN+zFTDv4Xa1QDTU8pRvovPetcOMbmqq8CwsAvZBLPZHH6usxyy31zMp7I4aCYb6XIf6w==", + "dev": true + }, "node_modules/highlight.js": { "version": "11.6.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", @@ -14038,6 +17124,31 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -14079,6 +17190,15 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -14144,6 +17264,16 @@ "node": ">=4" } }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -14153,15 +17283,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/individual": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", @@ -14193,29 +17314,29 @@ } }, "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.18.0" } }, "node_modules/inquirer/node_modules/ansi-styles": { @@ -14234,21 +17355,26 @@ } }, "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/inquirer/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/inquirer/node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -14267,42 +17393,93 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/inquirer/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/inquirer/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inquirer/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" } }, "node_modules/inquirer/node_modules/rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" } }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/inquirer/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/inquirer/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", @@ -14344,6 +17521,12 @@ "node": ">=0.10.0" } }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -15227,16 +18410,34 @@ "url": "https://bevry.me/fund" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "dev": true, "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" @@ -15261,9 +18462,9 @@ } }, "node_modules/jake/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "node_modules/jake/node_modules/chalk": { @@ -15322,18 +18523,18 @@ } }, "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-diff/node_modules/ansi-styles": { @@ -15407,27 +18608,27 @@ } }, "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { @@ -15501,23 +18702,23 @@ } }, "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "stack-utils": "^2.0.3" }, "engines": { - "node": ">= 10.14.2" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util/node_modules/ansi-styles": { @@ -15599,13 +18800,91 @@ "node": ">=8" } }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 10.14.2" + "node": ">=8" } }, "node_modules/jest-worker": { @@ -16230,9 +19509,9 @@ "dev": true }, "node_modules/keyv": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", - "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -16267,12 +19546,12 @@ } }, "node_modules/ky": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.29.0.tgz", - "integrity": "sha512-01TBSOqlHmLfcQhHseugGHLxPtU03OyZWaLDWt5MfzCkijG6xWFvAQPhKVn0cR2MMjYvBP9keQ8A3+rQEhLO5g==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/ky?sponsor=1" @@ -16515,6 +19794,29 @@ "node": ">=8.9.0" } }, + "node_modules/locate-app": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.13.tgz", + "integrity": "sha512-1jp6iRFrHKBj9vq6Idb0cSjly+KnCIMbxZ2BBKSEzIC4ZJosv47wnLoiJu2EgOAdjhGvNcy/P2fbDCS/WziI8g==", + "dev": true, + "dependencies": { + "n12": "1.8.16", + "type-fest": "2.13.0", + "userhome": "1.0.0" + } + }, + "node_modules/locate-app/node_modules/type-fest": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.0.tgz", + "integrity": "sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -16655,6 +19957,12 @@ "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", "dev": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -16776,6 +20084,32 @@ "node": ">=8.0" } }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dev": true, + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/loglevel": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -16928,15 +20262,6 @@ "node": ">=0.10.0" } }, - "node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -18200,6 +21525,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -18213,18 +21553,6 @@ "node": ">=0.10.0" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -18712,6 +22040,12 @@ "npm": ">=5" } }, + "node_modules/n12": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.16.tgz", + "integrity": "sha512-CZqHAqbzS0UsaUGkMsL+lMaYLyFr1+/ea+pD8dMziqSjkcuWVWDtgWx9phyfT7C3llqQ2+LwnStSb5afggBMfA==", + "dev": true + }, "node_modules/nan": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", @@ -18813,6 +22147,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -18872,6 +22215,25 @@ "isarray": "0.0.1" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -18907,6 +22269,18 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, + "node_modules/node-request-interceptor": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.6.3.tgz", + "integrity": "sha512-8I2V7H2Ch0NvW7qWcjmS0/9Lhr0T6x7RD6PDirhvWEkUQvy83x8BA4haYMr09r/rig7hcgYSjYh6cd4U7G1vLA==", + "dev": true, + "dependencies": { + "@open-draft/until": "^1.0.3", + "debug": "^4.3.0", + "headers-utils": "^1.2.0", + "strict-event-emitter": "^0.1.0" + } + }, "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -19568,6 +22942,64 @@ "node": ">=6" } }, + "node_modules/pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "dependencies": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -19733,6 +23165,31 @@ "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -19761,6 +23218,12 @@ "node": ">=0.10.0" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -19938,53 +23401,31 @@ } }, "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">= 10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -20070,6 +23511,59 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -20097,12 +23591,6 @@ "node": ">= 0.10" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -20266,6 +23754,12 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -20310,9 +23804,9 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, "node_modules/read-pkg": { @@ -21156,6 +24650,12 @@ "node": ">=6" } }, + "node_modules/safaridriver": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.2.tgz", + "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -21204,6 +24704,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -21456,6 +24965,21 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "node_modules/set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "dependencies": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -21653,6 +25177,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -21897,6 +25431,52 @@ "node": ">=10.0.0" } }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks/node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -22045,26 +25625,12 @@ } }, "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">= 10.x" } }, "node_modules/sprintf-js": { @@ -22108,9 +25674,9 @@ } }, "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" @@ -22305,6 +25871,22 @@ "node": ">= 4.0.0" } }, + "node_modules/streamx": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", + "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.1.0.tgz", + "integrity": "sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw==", + "dev": true + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -22340,6 +25922,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -22394,6 +25991,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -22421,6 +26031,18 @@ "node": ">=0.10.0" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", @@ -22433,15 +26055,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/suffix": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", - "integrity": "sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -23043,6 +26656,15 @@ "node": ">=0.10.0" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -23163,6 +26785,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true, + "optional": true, "peer": true, "bin": { "tsc": "bin/tsc", @@ -23295,6 +26918,12 @@ "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -23670,6 +27299,15 @@ "node": ">=0.10.0" } }, + "node_modules/userhome": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", + "integrity": "sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -24141,6 +27779,102 @@ "he": "^1.2.0" } }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/walk": { "version": "2.3.15", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.15.tgz", @@ -24172,119 +27906,196 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/webdriver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.5.3.tgz", - "integrity": "sha512-cDTn/hYj5x8BYwXxVb/WUwqGxrhCMP2rC8ttIWCfzmiVtmOnJGulC7CyxU3+p9Q5R/gIKTzdJOss16dhb+5CoA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz", + "integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==", "dev": true, "dependencies": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "deepmerge-ts": "^5.1.0", + "got": "^12.6.1", + "ky": "^0.33.0", + "ws": "^8.8.0" }, "engines": { - "node": ">=12.0.0" + "node": "^16.13 || >=18" } }, - "node_modules/webdriver/node_modules/@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "node_modules/webdriver/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/webdriver/node_modules/@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "node_modules/webdriver/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "got": "^11.8.1" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.16" } }, - "node_modules/webdriver/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/webdriver/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/webdriver/node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" + } + }, + "node_modules/webdriver/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriver/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/webdriver/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/webdriver/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/webdriver/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=10.19.0" } }, - "node_modules/webdriver/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/webdriver/node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/webdriver/node_modules/has-flag": { + "node_modules/webdriver/node_modules/mimic-response": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webdriver/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/webdriver/node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriver/node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/webdriver/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "lowercase-keys": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/webdriverio": { @@ -25068,56 +28879,32 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "node_modules/winston-transport": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", + "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", "dev": true, "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" }, "engines": { - "node": ">=4" + "node": ">= 12.0.0" } }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "ansi-regex": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=4" + "node": ">= 6" } }, "node_modules/word-wrap": { @@ -25158,6 +28945,57 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -25317,117 +29155,6 @@ "node": ">=8" } }, - "node_modules/yarn-install": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", - "integrity": "sha512-VO1u181msinhPcGvQTVMnHVOae8zjX/NSksR17e6eXHRveDvHCF5mGjh9hkN8mzyfnCqcBe42LdTs7bScuTaeg==", - "dev": true, - "dependencies": { - "cac": "^3.0.3", - "chalk": "^1.1.3", - "cross-spawn": "^4.0.2" - }, - "bin": { - "yarn-install": "bin/yarn-install.js", - "yarn-remove": "bin/yarn-remove.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yarn-install/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "node_modules/yarn-install/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/yarn-install/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yarn-install/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/yarn-install/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/yarn-install/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -26490,16 +30217,6 @@ "regenerator-runtime": "^0.13.10" } }, - "@babel/runtime-corejs3": { - "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.20.1.tgz", - "integrity": "sha512-CGulbEDcg/ND1Im7fUNRZdGXmX2MTWVVZacQi/6DiKE5HNwZ3aVTm5PV4lO8HHz0B2h8WQyvKKjbX5XgTtydsg==", - "dev": true, - "requires": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.10" - } - }, "@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -26731,6 +30448,71 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -26750,16 +30532,35 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dev": true, "requires": { + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", - "@types/yargs": "^15.0.0", + "@types/yargs": "^17.0.8", "chalk": "^4.0.0" }, "dependencies": { @@ -26870,6 +30671,15 @@ "@jridgewell/sourcemap-codec": "1.4.14" } }, + "@ljharb/through": { + "version": "2.3.12", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.12.tgz", + "integrity": "sha512-ajo/heTlG3QgC8EGP6APIejksVAYt4ayz4tqoP3MolFELzcH1x1fzwEYRJTPO0IELutZ5HQ0c26/GqAYy79u3g==", + "dev": true, + "requires": { + "call-bind": "^1.0.5" + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -26879,12 +30689,122 @@ "eslint-scope": "5.1.1" } }, + "@open-draft/until": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz", + "integrity": "sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==", + "dev": true + }, + "@percy/appium-app": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@percy/appium-app/-/appium-app-2.0.3.tgz", + "integrity": "sha512-6INeUJSyK2LzWV4Cc9bszNqKr3/NLcjFelUC2grjPnm6+jLA29inBF4ZE3PeTfLeCSw/0jyCGWV5fr9AyxtzCA==", + "dev": true, + "requires": { + "@percy/sdk-utils": "^1.27.0-beta.0", + "tmp": "^0.2.1" + }, + "dependencies": { + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "@percy/sdk-utils": { + "version": "1.27.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.27.7.tgz", + "integrity": "sha512-E21dIEQ9wwGDno41FdMDYf6jJow5scbWGClqKE/ptB+950W4UF5C4hxhVVQoEJxDdLE/Gy/8ZJR7upvPHShWDg==", + "dev": true + }, + "@percy/selenium-webdriver": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@percy/selenium-webdriver/-/selenium-webdriver-2.0.3.tgz", + "integrity": "sha512-JfLJVRkwNfqVofe7iGKtoQbOcKSSj9t4pWFbSUk95JfwAA7b9/c+dlBsxgIRrdrMYzLRjnJkYAFSZkJ4F4A19A==", + "dev": true, + "requires": { + "@percy/sdk-utils": "^1.27.2", + "node-request-interceptor": "^0.6.3" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "@puppeteer/browsers": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.9.1.tgz", + "integrity": "sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.1", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.2" + }, + "dependencies": { + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -26941,6 +30861,20 @@ "defer-to-connect": "^2.0.0" } }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "optional": true, + "peer": true + }, + "@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true + }, "@types/aria-query": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz", @@ -26983,24 +30917,6 @@ "@types/ms": "*" } }, - "@types/diff": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.2.tgz", - "integrity": "sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==", - "dev": true - }, - "@types/easy-table": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/easy-table/-/easy-table-0.0.33.tgz", - "integrity": "sha512-/vvqcJPmZUfQwCgemL0/34G7bIQnCuvgls379ygRlcC1FqNqk3n+VZ15dAO51yl6JNDoWd8vsk+kT8zfZ1VZSw==", - "dev": true - }, - "@types/ejs": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.1.tgz", - "integrity": "sha512-RQul5wEfY7BjWm0sYY86cmUN/pcXWGyVxWX93DFFJvcrxax5zKlieLwA3T77xJGwNcZW0YW6CYG70p1m8xPFmA==", - "dev": true - }, "@types/eslint": { "version": "8.4.9", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.9.tgz", @@ -27039,21 +30955,12 @@ "integrity": "sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==", "dev": true }, - "@types/fibers": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/fibers/-/fibers-3.1.1.tgz", - "integrity": "sha512-yHoUi46uika0snoTpNcVqUSvgbRndaIps4TUCotrXjtc0DHDoPQckmyXEZ2bX3e4mpJmyEW3hRhCwQa/ISCPaA==", + "@types/gitconfiglocal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/gitconfiglocal/-/gitconfiglocal-2.0.3.tgz", + "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "@types/fs-extra": { - "version": "9.0.13", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", - "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/github-slugger": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz", @@ -27070,40 +30977,30 @@ } }, "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, - "@types/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ==", - "dev": true, - "requires": { - "@types/through": "*", - "rxjs": "^6.4.0" - } - }, "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" } }, "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "requires": { "@types/istanbul-lib-report": "*" @@ -27130,39 +31027,6 @@ "keyv": "*" } }, - "@types/lodash": { - "version": "4.14.187", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.187.tgz", - "integrity": "sha512-MrO/xLXCaUgZy3y96C/iOsaIqZSeupyTImKClHunL5GrmaiII2VwvWmLBu2hwa0Kp0sV19CsyjtrTc/Fx8rg/A==", - "dev": true - }, - "@types/lodash.flattendeep": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@types/lodash.flattendeep/-/lodash.flattendeep-4.4.7.tgz", - "integrity": "sha512-1h6GW/AeZw/Wej6uxrqgmdTDZX1yFS39lRsXYkg+3kWvOWWrlGCI6H7lXxlUHOzxDT4QeYGmgPpQ3BX9XevzOg==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.pickby": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.pickby/-/lodash.pickby-4.6.7.tgz", - "integrity": "sha512-4ebXRusuLflfscbD0PUX4eVknDHD9Yf+uMtBIvA/hrnTqeAzbuHuDjvnYriLjUrI9YrhCPVKUf4wkRSXJQ6gig==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/lodash.union": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/@types/lodash.union/-/lodash.union-4.6.7.tgz", - "integrity": "sha512-6HXM6tsnHJzKgJE0gA/LhTGf/7AbjUk759WZ1MziVm+OBNAATHhdgj+a3KVE8g76GCLAnN4ZEQQG1EGgtBIABA==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, "@types/mdast": { "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz", @@ -27173,9 +31037,9 @@ } }, "@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", "dev": true }, "@types/ms": { @@ -27185,10 +31049,13 @@ "dev": true }, "@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "version": "20.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz", + "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/normalize-package-data": { "version": "2.4.1", @@ -27196,30 +31063,6 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, - "@types/object-inspect": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@types/object-inspect/-/object-inspect-1.8.1.tgz", - "integrity": "sha512-0JTdf3CGV0oWzE6Wa40Ayv2e2GhpP3pEJMcrlM74vBSJPuuNkVwfDnl0SZxyFCXETcB4oKA/MpTVfuYSMOelBg==", - "dev": true - }, - "@types/puppeteer": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.7.tgz", - "integrity": "sha512-JdGWZZYL0vKapXF4oQTC5hLVNfOgdPrqeZ1BiQnGk5cB7HeE91EWUiTdVSdQPobRN8rIcdffjiOgCYJ/S8QrnQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/recursive-readdir": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/recursive-readdir/-/recursive-readdir-2.2.1.tgz", - "integrity": "sha512-Xd+Ptc4/F2ueInqy5yK2FI5FxtwwbX2+VZpcg+9oYsFJVen8qQKGapCr+Bi5wQtHU1cTXT8s+07lo/nKPgu8Gg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -27230,39 +31073,21 @@ } }, "@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, - "@types/stream-buffers": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/stream-buffers/-/stream-buffers-3.0.4.tgz", - "integrity": "sha512-qU/K1tb2yUdhXkLIATzsIPwbtX6BpZk0l3dPW6xqWyhfzzM1ECaQ/8faEnu3CNraLiQ9LHyQQPBGp7N9Fbs25w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", "dev": true }, - "@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==", + "@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, "@types/ua-parser-js": { @@ -27293,19 +31118,28 @@ "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", "dev": true }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, "requires": { "@types/yargs-parser": "*" } }, "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, "@types/yauzl": { @@ -27318,12 +31152,6 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "@videojs/http-streaming": { "version": "2.14.3", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.14.3.tgz", @@ -27362,6 +31190,34 @@ "is-function": "^1.0.1" } }, + "@vitest/snapshot": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.1.tgz", + "integrity": "sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==", + "dev": true, + "requires": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + } + } + }, "@vue/compiler-core": { "version": "3.2.41", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.41.tgz", @@ -27456,1756 +31312,2876 @@ "optional": true }, "@wdio/browserstack-service": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-7.16.16.tgz", - "integrity": "sha512-q4wUh/j0MR2SwhTkmIFif2DaXgH5yzdgOer6G/fac2n81zLCSpQHWO5aQ9T0An9CAd4L2A+t3dmChpBJPkHWSw==", - "dev": true, - "requires": { - "@types/node": "^17.0.4", - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "browserstack-local": "^1.4.5", - "got": "^11.0.2", - "webdriverio": "7.16.16" + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.29.1.tgz", + "integrity": "sha512-dLEJcdVF0Cu+2REByVOfLUzx9FvMias1VsxSCZpKXeIAGAIWBBdNdooK6Vdc9QdS36S5v/mk0/rTTQhYn4nWjQ==", + "dev": true, + "requires": { + "@percy/appium-app": "^2.0.1", + "@percy/selenium-webdriver": "^2.0.3", + "@types/gitconfiglocal": "^2.0.1", + "@wdio/logger": "8.28.0", + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "browserstack-local": "^1.5.1", + "chalk": "^5.3.0", + "csv-writer": "^1.6.0", + "formdata-node": "5.0.1", + "git-repo-info": "^2.1.1", + "gitconfiglocal": "^2.1.0", + "got": "^12.6.1", + "uuid": "^9.0.0", + "webdriverio": "8.29.1", + "winston-transport": "^4.5.0", + "yauzl": "^2.10.0" }, "dependencies": { - "@wdio/config": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.16.16.tgz", - "integrity": "sha512-K/ObPuo6Da2liz++OKOIfbdpFwI7UWiFcBylfJkCYbweuXCoW1aUqlKI6rmKPwCH9Uqr/RHWu6p8eo0zWe6xVA==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dev": true, + "requires": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + } + }, + "archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dev": true, + "requires": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "requires": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + } + }, + "compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" + }, + "dependencies": { + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "isexe": "^3.1.1" + } + } + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "peer": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "@wdio/protocols": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.16.7.tgz", - "integrity": "sha512-Wv40pNQcLiPzQ3o98Mv4A8T1EBQ6k4khglz/e2r16CTm+F3DDYh8eLMAsU5cgnmuwwDKX1EyOiFwieykBn5MCg==", - "dev": true + "got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } }, - "@wdio/repl": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.16.14.tgz", - "integrity": "sha512-Ezih0Y+lsGkKv3H3U56hdWgZiQGA3VaAYguSLd9+g1xbQq+zMKqSmfqECD9bAy+OgCCiVTRstES6lHZxJVPhAg==", + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "requires": { - "@wdio/utils": "7.16.14" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" } }, - "@wdio/utils": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.16.14.tgz", - "integrity": "sha512-wwin8nVpIlhmXJkq6GJw9aDDzgLOJKgXTcEua0T2sdXjoW78u5Ly/GZrFXTjMGhacFvoZfitTrjyfyy4CxMVvw==", + "http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "requires": { - "@wdio/logger": "7.16.0", - "@wdio/types": "7.16.14", - "p-iteration": "^1.1.8" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" } }, - "brace-expansion": { + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true + }, + "lighthouse-logger": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "balanced-match": "^1.0.0" + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "devtools": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.16.16.tgz", - "integrity": "sha512-M0kzkuSgfEhpqIis3gdtWsNjn/HQ+vRAmEzDnbYx/7FfjFxhSv1d+rOOT20pvd60soItMYpsOova1igACEGkGQ==", - "dev": true, - "requires": { - "@types/node": "^17.0.4", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^8.0.0" - } + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true }, - "devtools-protocol": { - "version": "0.0.973690", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.973690.tgz", - "integrity": "sha512-myh3hSFp0YWa2GED11PmbLhV4dv9RdO7YUz27XJrbQLnP5bMbZL6dfOOILTHO57yH0kX5GfuOZBsg/4NamfPvQ==", + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, - "ua-parser-js": { - "version": "1.0.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz", - "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", "dev": true }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true }, - "webdriver": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.16.16.tgz", - "integrity": "sha512-x8UoG9k/P8KDrfSh1pOyNevt9tns3zexoMxp9cKnyA/7HYSErhZYTLGlgxscAXLtQG41cMH/Ba/oBmOx7Hgd8w==", + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, "requires": { - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "got": "^11.0.2", - "ky": "^0.29.0", - "lodash.merge": "^4.6.1" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } + } + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "requires": { + "type-fest": "^2.12.2" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "optional": true, + "peer": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + }, "webdriverio": { - "version": "7.16.16", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.16.16.tgz", - "integrity": "sha512-caPaEWyuD3Qoa7YkW4xCCQA4v9Pa9wmhFGPvNZh3ERtjMCNi8L/XXOdkekWNZmFh3tY0kFguBj7+fAwSY7HAGw==", - "dev": true, - "requires": { - "@types/aria-query": "^5.0.0", - "@types/node": "^17.0.4", - "@wdio/config": "7.16.16", - "@wdio/logger": "7.16.0", - "@wdio/protocols": "7.16.7", - "@wdio/repl": "7.16.14", - "@wdio/types": "7.16.14", - "@wdio/utils": "7.16.14", - "archiver": "^5.0.0", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.16.16", - "devtools-protocol": "^0.0.973690", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", + "devtools-protocol": "^0.0.1249869", "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^5.0.0", - "puppeteer-core": "^13.1.3", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.16.16" + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "requires": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + } + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + } + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "requires": {} + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "requires": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" } } } }, "@wdio/cli": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-7.5.7.tgz", - "integrity": "sha512-nOQJLskrY+UECDd3NxE7oBzb6cDA7e7x02YWQugOlOgnZ4a+PJmkFoSsO8C2uNCpdFngy5rJKGUo5vbtAHEF9Q==", - "dev": true, - "requires": { - "@types/ejs": "^3.0.5", - "@types/fs-extra": "^9.0.4", - "@types/inquirer": "^7.3.1", - "@types/lodash.flattendeep": "^4.4.6", - "@types/lodash.pickby": "^4.6.6", - "@types/lodash.union": "^4.6.6", - "@types/recursive-readdir": "^2.2.0", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.29.1.tgz", + "integrity": "sha512-WWRTf0g0O+ovTTvS1kEhZ/svX32M7jERuuMF1MaldKCi7rZwHsQqOyJD+fO1UDjuxqS96LHSGsZn0auwUfCTXA==", + "dev": true, + "requires": { + "@types/node": "^20.1.1", + "@vitest/snapshot": "^1.2.1", + "@wdio/config": "8.29.1", + "@wdio/globals": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", "async-exit-hook": "^2.0.1", - "chalk": "^4.0.0", - "chokidar": "^3.0.0", - "cli-spinners": "^2.1.0", - "ejs": "^3.0.1", - "fs-extra": "^10.0.0", - "inquirer": "^8.0.0", + "chalk": "^5.2.0", + "chokidar": "^3.5.3", + "cli-spinners": "^2.9.0", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "execa": "^8.0.1", + "import-meta-resolve": "^4.0.0", + "inquirer": "9.2.12", "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "mkdirp": "^1.0.4", - "recursive-readdir": "^2.2.2", - "webdriverio": "7.5.7", - "yargs": "^17.0.0", - "yarn-install": "^1.0.0" + "read-pkg-up": "^10.0.0", + "recursive-readdir": "^2.2.3", + "webdriverio": "8.29.1", + "yargs": "^17.7.2" }, "dependencies": { - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "dependencies": { + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } }, - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, "requires": { - "got": "^11.8.1" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "balanced-match": "^1.0.0" } }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, "requires": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" }, "dependencies": { - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, + "optional": true, + "peer": true, "requires": { - "minimist": "^1.2.6" + "isexe": "^3.1.1" } } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "requires": { - "color-name": "~1.1.4" + "@types/which": "^2.0.1", + "which": "^2.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "peer": true + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "requires": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", "dev": true }, - "devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", - "dev": true, - "requires": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", + "hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true + } + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true + }, + "json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", "dev": true }, - "puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" + "lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "^2.6.9", + "marky": "^1.2.2" }, "dependencies": { - "devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } } } }, - "supports-color": { + "lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true + }, + "locate-path": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "p-locate": "^6.0.0" } }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true }, - "webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", - "dev": true, - "requires": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" } }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "requires": {} + "optional": true, + "peer": true }, - "yargs": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.0.tgz", - "integrity": "sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==", + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "whatwg-url": "^5.0.0" } - } - } - }, - "@wdio/concise-reporter": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-7.5.7.tgz", - "integrity": "sha512-964i7eQ4sboSla2bdR8714Er82QBgS6u39GmDFX8Izy9Ge38xaE75HuF5S7mnOWGzSojCWgqtwy5k7Rfg6GE3g==", - "dev": true, - "requires": { - "@wdio/reporter": "7.5.7", - "@wdio/types": "7.5.3", - "chalk": "^4.0.0", - "pretty-ms": "^7.0.0" - }, - "dependencies": { - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + }, + "normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", "dev": true, "requires": { - "got": "^11.8.1" + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "path-key": "^4.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "mimic-fn": "^4.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "yocto-queue": "^1.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "requires": { + "p-limit": "^4.0.0" + } + }, + "parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "dependencies": { + "type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true + } + } + }, + "path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true }, - "has-flag": { + "path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@wdio/config": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.5.3.tgz", - "integrity": "sha512-udvVizYoilOxuWj/BmoN6y7ZCd4wPdYNlSfWznrbCezAdaLZ4/pNDOO0WRWx2C4+q1wdkXZV/VuQPUGfL0lEHQ==", - "dev": true, - "requires": { - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "deepmerge": "^4.0.0", - "glob": "^7.1.2" - }, - "dependencies": { - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "requires": { - "got": "^11.8.1" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "lru-cache": "^6.0.0" } - } - } - }, - "@wdio/local-runner": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-7.5.7.tgz", - "integrity": "sha512-aYc0XUV+/e3cg8Fp+CWlC4FbwSSG3mKAv1iuy/+Hwzg2kJE+aa+Rf2p2BQYc7HPRtKNW0bM8o+aCImZLAiPM+A==", - "dev": true, - "requires": { - "@types/stream-buffers": "^3.0.3", - "@wdio/logger": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/runner": "7.5.7", - "@wdio/types": "7.5.3", - "async-exit-hook": "^2.0.1", - "split2": "^3.2.2", - "stream-buffers": "^3.0.2" - }, - "dependencies": { - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + }, + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "type-fest": "^2.12.2" + }, + "dependencies": { + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + } } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "requires": { - "got": "^11.8.1" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "type-fest": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.1.tgz", + "integrity": "sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==", + "dev": true + }, + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "dev": true, - "requires": { - "color-convert": "^2.0.1" - } + "optional": true, + "peer": true }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "optional": true, + "peer": true + }, + "webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "requires": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + } + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "requires": {} + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { - "color-name": "~1.1.4" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" } } } }, - "@wdio/logger": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.16.0.tgz", - "integrity": "sha512-/6lOGb2Iow5eSsy7RJOl1kCwsP4eMlG+/QKro5zUJsuyNJSQXf2ejhpkzyKWLgQbHu83WX6cM1014AZuLkzoQg==", + "@wdio/concise-reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.29.1.tgz", + "integrity": "sha512-dUhClWeq1naL1Qa1nSMDeH8aCVViOKiEzhBhQjgrMOz1Mh3l6O/woqbK2iKDVZDRhfGghtGcV0vpoEUvt8ZKOA==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } + } + }, + "@wdio/config": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.29.1.tgz", + "integrity": "sha512-zNUac4lM429HDKAitO+fdlwUH1ACQU8lww+DNVgUyuEb86xgVdTqHeiJr/3kOMJAq9IATeE7mDtYyyn6HPm1JA==", + "dev": true, + "requires": { + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.0.0", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + }, + "dependencies": { + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "color-name": "~1.1.4" + "balanced-match": "^1.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "brace-expansion": "^2.0.1" } } } }, - "@wdio/mocha-framework": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-7.5.3.tgz", - "integrity": "sha512-96QCVWsiyZxEgOZP3oTq2B2T7zne5dCdehLa2n4q/BLjk96Rj0jifidJZfd/1+vdNPKX0gWWAzpy98Znn8MVMw==", + "@wdio/globals": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.29.1.tgz", + "integrity": "sha512-F+fPnX75f44/crZDfQ2FYSino/IMIdbnQGLIkaH0VnoljVJIHuxnX4y5Zqr4yRgurL9DsZaH22cLHrPXaHUhPg==", "dev": true, "requires": { - "@types/mocha": "^8.0.0", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "expect-webdriverio": "^2.0.0", - "mocha": "^8.0.1" + "expect-webdriverio": "^4.9.3", + "webdriverio": "8.29.1" }, "dependencies": { - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, + "optional": true, "requires": { - "got": "^11.8.1" + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, + "optional": true, "requires": { - "color-convert": "^2.0.1" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "argparse": { + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "optional": true + }, + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0" + } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" } }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, + "optional": true, "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, + "optional": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "requires": { - "color-name": "~1.1.4" + "node-fetch": "^2.6.11" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" + }, + "dependencies": { + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "isexe": "^3.1.1" + } + } + } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "requires": { - "ms": "2.1.2" + "@types/which": "^2.0.1", + "which": "^2.0.2" } }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } + "optional": true, + "peer": true }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, + "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "requires": { - "argparse": "^2.0.1" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true + }, + "lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "p-locate": "^5.0.0" + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + } } }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, + "optional": true, "requires": { - "chalk": "^4.0.0" + "brace-expansion": "^2.0.1" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, + "optional": true, + "peer": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "optional": true, "requires": { - "brace-expansion": "^1.1.7" + "whatwg-url": "^5.0.0" } }, - "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" }, "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "optional": true, + "requires": { + "debug": "^4.3.4" + } }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", "dev": true, + "optional": true, "requires": { - "has-flag": "^4.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" } } } }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "requires": { - "yocto-queue": "^0.1.0" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" } }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "optional": true, "requires": { - "p-limit": "^3.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, + "optional": true, "requires": { - "picomatch": "^2.2.1" + "type-fest": "^2.12.2" } }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, + "optional": true, "requires": { - "randombytes": "^2.1.0" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "optional": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "optional": true, + "peer": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "optional": true, + "peer": true + }, + "webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", "dev": true, + "optional": true, "requires": { - "has-flag": "^4.0.0" + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "optional": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true, + "optional": true + }, + "puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "optional": true, + "requires": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + } + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "optional": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + } } }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "optional": true, + "requires": {} }, "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", "dev": true, + "optional": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "optional": true, + "requires": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + } } } }, - "@wdio/protocols": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.5.3.tgz", - "integrity": "sha512-lpNaKwxYhDSL6neDtQQYXvzMAw+u4PXx65ryeMEX82mkARgzSZps5Kyrg9ub7X4T17K1NPfnY6UhZEWg6cKJCg==", - "dev": true - }, - "@wdio/repl": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.5.3.tgz", - "integrity": "sha512-jfNJwNoc2nWdnLsFoGHmOJR9zaWfDTBMWM3W1eR5kXIjevD6gAfWsB5ZoA4IdybujCXxdnhlsm4o2jIzp/6f7A==", + "@wdio/local-runner": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.29.1.tgz", + "integrity": "sha512-Z3QAgxe1uQ97C7NS1CdMhgmHaLu/sbb47HTbw1yuuLk+SwsBIQGhNpTSA18QVRSUXq70G3bFvjACwqyap1IEQg==", "dev": true, "requires": { - "@wdio/utils": "7.5.3" + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/repl": "8.24.12", + "@wdio/runner": "8.29.1", + "@wdio/types": "8.29.1", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" } }, - "@wdio/reporter": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.5.7.tgz", - "integrity": "sha512-9PXqZtCXDtU6UYLNDPu9MZQ8BiABGnRlJTrlbYB3gBfZDibMkJMvwXzPderipBv2+ifDZXmGe3Njf1ao2TkbFA==", + "@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", "dev": true, "requires": { - "@wdio/types": "7.5.3", - "fs-extra": "^10.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "dependencies": { - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "got": "^11.8.1" + "ansi-regex": "^6.0.1" } } } }, - "@wdio/runner": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-7.5.7.tgz", - "integrity": "sha512-RzVXd+xnwK/thkx1/xo9K5iscQ0Ofobgsx5dNVtwLDVMn9V7jCW/WX4dSCPAPaVSqnUCmkcQp3P5AoSBPpCZnQ==", + "@wdio/mocha-framework": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.29.1.tgz", + "integrity": "sha512-R9dKMNqWgtUvZo33ORjUQV8Z/WLX5h/pg9u/xIvZSGXuNSw1h+5DWF6UiNFscxBFblL9UvBi6V9ila2LHgE4ew==", "dev": true, "requires": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "deepmerge": "^4.0.0", + "@types/mocha": "^10.0.0", + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "mocha": "^10.0.0" + } + }, + "@wdio/protocols": { + "version": "8.24.12", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.24.12.tgz", + "integrity": "sha512-QnVj3FkapmVD3h2zoZk+ZQ8gevSj9D9MiIQIy8eOnY4FAneYZ9R9GvoW+mgNcCZO8S8++S/jZHetR8n+8Q808g==", + "dev": true + }, + "@wdio/repl": { + "version": "8.24.12", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", + "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.29.1.tgz", + "integrity": "sha512-LZeYHC+HHJRYiFH9odaotDazZh0zNhu4mTuL/T/e3c/Q3oPSQjLvfQYhB3Ece1QA9PKjP1VPmr+g9CvC0lMixA==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" + } + }, + "@wdio/runner": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.29.1.tgz", + "integrity": "sha512-MvYFf4RgRmzxjAzy6nxvaDG1ycBRvoz772fT06csjxuaVYm57s8mlB8X+U1UQMx/IzujAb53fSeAmNcyU3FNEA==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/globals": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "deepmerge-ts": "^5.0.0", + "expect-webdriverio": "^4.9.3", "gaze": "^1.1.2", - "webdriver": "7.5.3", - "webdriverio": "7.5.7" + "webdriver": "8.29.1", + "webdriverio": "8.29.1" }, "dependencies": { - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", - "dev": true - }, - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true, - "requires": { - "got": "^11.8.1" - } + "optional": true, + "peer": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" } }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", "dev": true, "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^1.0.0" } }, "chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", "dev": true, + "optional": true, + "peer": true, "requires": { "@types/node": "*", - "escape-string-regexp": "^1.0.5", + "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" + "lighthouse-logger": "^2.0.1" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", "dev": true, "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", - "dev": true, - "requires": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" } }, - "devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", "dev": true, "requires": { - "minimist": "^1.2.6" + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" } }, - "puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", "dev": true, + "optional": true, + "peer": true, "requires": { - "has-flag": "^4.0.0" + "node-fetch": "^2.6.11" } }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", - "dev": true, - "requires": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", + "devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" + }, + "dependencies": { + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "isexe": "^3.1.1" + } + } } }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} - } - } - }, - "@wdio/spec-reporter": { - "version": "7.19.7", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-7.19.7.tgz", - "integrity": "sha512-BDBZU2EK/GuC9VxtfqPtoW43FmvKxYDsvcDVDi3F7o+9fkcuGSJiWbw1AX251ZzzVQ7YP9ImTitSpdpUKXkilQ==", - "dev": true, - "requires": { - "@types/easy-table": "^0.0.33", - "@wdio/reporter": "7.19.7", - "@wdio/types": "7.19.5", - "chalk": "^4.0.0", - "easy-table": "^1.1.1", - "pretty-ms": "^7.0.0" - }, - "dependencies": { - "@wdio/reporter": { - "version": "7.19.7", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-7.19.7.tgz", - "integrity": "sha512-Dum19gpfru66FnIq78/4HTuW87B7ceLDp6PJXwQM5kXyN7Gb7zhMgp6FZTM0FCYLyi6U/zXZSvpNUYl77caS6g==", + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", "dev": true, - "requires": { - "@types/diff": "^5.0.0", - "@types/node": "^17.0.4", - "@types/object-inspect": "^1.8.0", - "@types/supports-color": "^8.1.0", - "@types/tmp": "^0.2.0", - "@wdio/types": "7.19.5", - "diff": "^5.0.0", - "fs-extra": "^10.0.0", - "object-inspect": "^1.10.3", - "supports-color": "8.1.1" - } + "optional": true, + "peer": true }, - "@wdio/types": { - "version": "7.19.5", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.19.5.tgz", - "integrity": "sha512-S1lC0pmtEO7NVH/2nM1c7NHbkgxLZH3VVG/z6ym3Bbxdtcqi2LMsEvvawMAU/fmhyiIkMsGZCO8vxG9cRw4z4A==", + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, + "optional": true, + "peer": true, "requires": { - "@types/node": "^17.0.4", - "got": "^11.8.1" + "@types/which": "^2.0.1", + "which": "^2.0.2" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "requires": { - "color-convert": "^2.0.1" - } + "optional": true, + "peer": true }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "brace-expansion": "^2.0.1" } } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "optional": true, + "peer": true, "requires": { - "color-name": "~1.1.4" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", "dev": true, + "optional": true, + "peer": true, "requires": { - "has-flag": "^4.0.0" + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + } } - } - } - }, - "@wdio/sync": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/@wdio/sync/-/sync-7.5.7.tgz", - "integrity": "sha512-Zu/AYLjwqbFSbaOU1US7ownv3ov8JrtoGHq51JfJ4masefJDXNkHix2cZ0qEgl3IvkkWQ0ewL0G8GTXb3KOemA==", - "dev": true, - "requires": { - "@types/fibers": "^3.1.0", - "@types/puppeteer": "^5.4.0", - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3", - "fibers": "^5.0.0", - "webdriverio": "7.5.7" - }, - "dependencies": { - "@types/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==", + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "brace-expansion": "^2.0.1" } }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "requires": { - "got": "^11.8.1" - } + "optional": true, + "peer": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "whatwg-url": "^5.0.0" } }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", "dev": true, "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", "dev": true, + "optional": true, + "peer": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" } }, - "chrome-launcher": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", - "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { - "@types/node": "*", - "escape-string-regexp": "^1.0.5", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0", - "mkdirp": "^0.5.3", - "rimraf": "^3.0.2" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "devtools": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.5.7.tgz", - "integrity": "sha512-+kqmvFbceElhYpN35yjm1T4Rz3VbH0QaqrNWKRpeyFp657Y5W0bm1s5FyMUeIv0aTNkAgWcETtqL+EG9X9uvjQ==", - "dev": true, - "requires": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "chrome-launcher": "^0.13.1", - "edge-paths": "^2.1.0", - "puppeteer-core": "^9.1.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^0.7.21", - "uuid": "^8.0.0" + "type-fest": "^2.12.2" } }, - "devtools-protocol": { - "version": "0.0.878340", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.878340.tgz", - "integrity": "sha512-W0q8Y02r1RNwfZtI4Jjh1/MZxRHyrIgy9FvElbJzQelZjmNH197H4mBQs7DZjlUUDA9s6Zz2jl+zUYFgLgEnzw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "requires": { - "minimist": "^1.2.6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, - "puppeteer-core": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-9.1.1.tgz", - "integrity": "sha512-zbedbitVIGhmgz0nt7eIdLsnaoVZSlNJfBivqm2w67T8LR2bU1dvnruDZ8nQO0zn++Iet7zHbAOdnuS5+H2E7A==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "devtools-protocol": "0.0.869402", - "extract-zip": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "pkg-dir": "^4.2.0", - "progress": "^2.0.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "tar-fs": "^2.0.0", - "unbzip2-stream": "^1.3.3", - "ws": "^7.2.3" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.869402", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz", - "integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA==", - "dev": true - } - } + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "optional": true, + "peer": true }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "optional": true, + "peer": true }, "webdriverio": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.5.7.tgz", - "integrity": "sha512-TLluVPLo6Snn/dxEITvMz7ZuklN4qZOBddDuLb9LO3rhsfKDMNbnhcBk0SLdFsWny0aCuhWNpJ6co93702XC0A==", - "dev": true, - "requires": { - "@types/aria-query": "^4.2.1", - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/repl": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "archiver": "^5.0.0", - "aria-query": "^4.2.2", - "atob": "^2.1.2", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", "css-shorthand-properties": "^1.1.1", "css-value": "^0.0.1", - "devtools": "7.5.7", - "devtools-protocol": "^0.0.878340", - "fs-extra": "^10.0.0", - "get-port": "^5.1.1", + "devtools-protocol": "^0.0.1249869", "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", "lodash.zip": "^4.2.0", - "minimatch": "^3.0.4", - "puppeteer-core": "^9.1.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", "query-selector-shadow-dom": "^1.0.0", "resq": "^1.9.1", "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.5.3" + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true + }, + "puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "requires": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true + } + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + } } }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "requires": {} + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "requires": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + } } } }, - "@wdio/types": { - "version": "7.16.14", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.16.14.tgz", - "integrity": "sha512-AyNI9iBSos9xWBmiFAF3sBs6AJXO/55VppU/eeF4HRdbZMtMarnvMuahM+jlUrA3vJSmDW+ufelG0MT//6vrnw==", + "@wdio/spec-reporter": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.29.1.tgz", + "integrity": "sha512-tuDHihrTjCxFCbSjT0jMvAarLA1MtatnCnhv0vguu3ZWXELR1uESX2KzBmpJ+chGZz3oCcKszT8HOr6Pg2a1QA==", "dev": true, "requires": { - "@types/node": "^17.0.4", - "got": "^11.8.1" + "@wdio/reporter": "8.29.1", + "@wdio/types": "8.29.1", + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" + }, + "dependencies": { + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } } }, - "@wdio/utils": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.5.3.tgz", - "integrity": "sha512-nlLDKr8v8abLOHCKroBwQkGPdCIxjID2MllgWX23xqkYZylM9RdwPBdL8osQt9m3rq2TxiPAT4OlbzNt2WtN6Q==", + "@wdio/types": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.29.1.tgz", + "integrity": "sha512-rZYzu+sK8zY1PjCEWxNu4ELJPYKDZRn7HFcYNgR122ylHygfldwkb5TioI6Pn311hQH/S+663KEeoq//Jb0f8A==", "dev": true, "requires": { - "@wdio/logger": "7.5.3", - "@wdio/types": "7.5.3" + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-Dm91DKL/ZKeZ2QogWT8Twv0p+slEgKyB/5x9/kcCG0Q2nNa+tZedTjOhryzrsPiWc+jTSBmjGE4katRXpJRFJg==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^1.6.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.29.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.1.0", + "edgedriver": "^5.3.5", + "geckodriver": "^4.2.0", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.1.0", + "safaridriver": "^0.1.0", + "split2": "^4.2.0", + "wait-port": "^1.0.4" }, "dependencies": { - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", - "dev": true, - "requires": { - "got": "^11.8.1" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -29810,6 +34786,23 @@ "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", "dev": true }, + "ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -29885,6 +34878,12 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, + "b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -30308,6 +35307,12 @@ } } }, + "basic-ftp": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz", + "integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==", + "dev": true + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -30652,127 +35657,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "cac": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/cac/-/cac-3.0.4.tgz", - "integrity": "sha512-hq4rxE3NT5PlaEiVV39Z45d6MoFcQZG5dsgJqtAUeOz3408LEQAElToDkf9i5IYSCOmK0If/81dLg7nKxqPR0w==", - "dev": true, - "requires": { - "camelcase-keys": "^3.0.0", - "chalk": "^1.1.3", - "indent-string": "^3.0.0", - "minimist": "^1.2.0", - "read-pkg-up": "^1.0.1", - "suffix": "^0.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } - } - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -30823,12 +35707,13 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" } }, "callsites": { @@ -30843,24 +35728,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "camelcase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-3.0.0.tgz", - "integrity": "sha512-U4E6A6aFyYnNW+tDt5/yIUKQURKXe3WMFPfX4FxrQFcwZ/R08AUk1xWcUtlr7oq6CV07Ji+aa69V2g7BSpblnQ==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==", - "dev": true - } - } - }, "can-autoplay": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz", @@ -30996,6 +35863,23 @@ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true }, + "chromium-bidi": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", + "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "mitt": "3.0.0" + } + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -31092,9 +35976,9 @@ } }, "cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true }, "cli-width": { @@ -31574,6 +36458,12 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, + "csv-writer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", + "integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==", + "dev": true + }, "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -31599,6 +36489,12 @@ "assert-plus": "^1.0.0" } }, + "data-uri-to-buffer": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz", + "integrity": "sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg==", + "dev": true + }, "date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -31730,6 +36626,12 @@ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, + "deepmerge-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", + "dev": true + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -31768,6 +36670,16 @@ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, "define-properties": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", @@ -31788,6 +36700,50 @@ "isobject": "^3.0.1" } }, + "degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "requires": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "dependencies": { + "escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -31825,12 +36781,6 @@ "repeating": "^2.0.0" } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true - }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -32029,9 +36979,9 @@ "dev": true }, "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true }, "dlv": { @@ -32228,6 +37178,12 @@ "domhandler": "^5.0.3" } }, + "dotenv": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", + "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "dev": true + }, "dset": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", @@ -32356,15 +37312,125 @@ "which": "^2.0.2" } }, + "edgedriver": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.3.9.tgz", + "integrity": "sha512-G0wNgFMFRDnFfKaXG2R6HiyVHqhKwdQ3EgoxW3wPlns2wKqem7F+HgkWBcevN7Vz0nN4AXtskID7/6jsYDXcKw==", + "dev": true, + "requires": { + "@wdio/logger": "^8.16.17", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "node-fetch": "^3.3.2", + "unzipper": "^0.10.14", + "which": "^4.0.0" + }, + "dependencies": { + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true + }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true + }, + "decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "requires": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "requires": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "requires": { + "isexe": "^3.1.1" + }, + "dependencies": { + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true + } + } + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "dev": true, "requires": { "jake": "^10.8.5" @@ -33391,55 +38457,605 @@ } }, "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "expect-webdriverio": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.9.3.tgz", + "integrity": "sha512-ASHsFc/QaK5ipF4ct3e8hd3elm8wNXk/Qa3EemtYDmfUQ4uzwqDf75m/QFQpwVNCjEpkNP7Be/6X9kz7bN0P9Q==", + "dev": true, + "requires": { + "@vitest/snapshot": "^1.2.1", + "@wdio/globals": "^8.27.0", + "@wdio/logger": "^8.24.12", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0", + "webdriverio": "^8.27.0" }, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@puppeteer/browsers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", + "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", "dev": true, + "optional": true, + "peer": true, "requires": { - "color-convert": "^2.0.1" + "debug": "4.3.4", + "extract-zip": "2.0.1", + "http-proxy-agent": "5.0.0", + "https-proxy-agent": "5.0.1", + "progress": "2.0.3", + "proxy-from-env": "1.1.0", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" } }, - "color-convert": { + "@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "optional": true, + "peer": true + }, + "archiver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-6.0.1.tgz", + "integrity": "sha512-CXGy4poOLBKptiZH//VlWdFuUC1RESbdZjGjILwBuZ73P7WkAUN0htfSfBq/7k6FRFlpu7bg4JOkj1vU9G6jcQ==", + "dev": true, + "optional": true, + "requires": { + "archiver-utils": "^4.0.1", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^5.0.1" + } + }, + "archiver-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-4.0.1.tgz", + "integrity": "sha512-Q4Q99idbvzmgCTEAAhi32BkOyq8iVI5EwdO0PmBDSGIzzjYNdcFn7Q7k3OzbLy4kLUPXfJtG6fO2RjftXbobBg==", + "dev": true, + "optional": true, + "requires": { + "glob": "^8.0.0", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "optional": true + }, + "brace-expansion": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "optional": true, "requires": { - "color-name": "~1.1.4" + "balanced-match": "^1.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "chrome-launcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.0.tgz", + "integrity": "sha512-rJYWeEAERwWIr3c3mEVXwNiODPEdMRlRxHc47B1qHPOolHZnkj7rMv1QSUfPoG6MgatWj5AxSpnKKR4QEwEQIQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^2.0.1" + } + }, + "compress-commons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-5.0.1.tgz", + "integrity": "sha512-MPh//1cERdLtqwO3pOFLeXtpuai0Y2WCd5AhtKxznqM7WtaMYaOEMSgn45d9D10sIHSfIKE603HlOp8OPGrvag==", + "dev": true, + "optional": true, + "requires": { + "crc-32": "^1.2.0", + "crc32-stream": "^5.0.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + } + }, + "crc32-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-5.0.0.tgz", + "integrity": "sha512-B0EPa1UK+qnpBZpG+7FgPCu0J2ETLpXq09o9BkLkEAhdB6Z61Qo4pJ3JYu0c+Qi+/SAL7QThqnzS06pmSSyZaw==", + "dev": true, + "optional": true, + "requires": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + } + }, + "cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "node-fetch": "^2.6.11" + } + }, + "devtools": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.29.1.tgz", + "integrity": "sha512-fbH0Z7CPK4OZSgUw2QcAppczowxtSyvFztPUmiFyi99cUadjEOwlg0aL3pBVlIDo67olYjGb8GD1M5Z4yI/P6w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "chrome-launcher": "^1.0.0", + "edge-paths": "^3.0.5", + "import-meta-resolve": "^4.0.0", + "puppeteer-core": "20.3.0", + "query-selector-shadow-dom": "^1.0.0", + "ua-parser-js": "^1.0.1", + "uuid": "^9.0.0", + "which": "^4.0.0" + }, + "dependencies": { + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "isexe": "^3.1.1" + } + } + } + }, + "devtools-protocol": { + "version": "0.0.1120988", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", + "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "dev": true, + "optional": true, + "peer": true + }, + "edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "optional": true, + "peer": true + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "optional": true, + "peer": true + }, + "lighthouse-logger": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", + "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "^2.6.9", + "marky": "^1.2.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "optional": true + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "optional": true, + "peer": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "optional": true, + "requires": { + "debug": "^4.3.4" + } + }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "optional": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } + } + }, + "puppeteer-core": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", + "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "@puppeteer/browsers": "1.3.0", + "chromium-bidi": "0.4.9", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1120988", + "ws": "8.13.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "optional": true, + "requires": { + "type-fest": "^2.12.2" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "optional": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "optional": true + }, + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "optional": true, + "peer": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "optional": true, + "peer": true + }, + "webdriverio": { + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.29.1.tgz", + "integrity": "sha512-NZK95ivXCqdPraB3FHMw6ByxnCvtgFXkjzG2l3Oq5z0IuJS2aMow3AKFIyiuG6is/deGCe+Tb8eFTCqak7UV+w==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^20.1.0", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/repl": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "archiver": "^6.0.0", + "aria-query": "^5.0.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1249869", + "grapheme-splitter": "^1.0.2", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.1", + "webdriver": "8.29.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "optional": true, + "requires": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + } + }, + "chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "optional": true, + "requires": { + "mitt": "3.0.0" + } + }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "optional": true, + "requires": { + "node-fetch": "^2.6.12" + } + }, + "devtools-protocol": { + "version": "0.0.1249869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1249869.tgz", + "integrity": "sha512-Ctp4hInA0BEavlUoRy9mhGq0i+JSo/AwVyX2EFgZmV1kYB+Zq+EMBAn52QWu6FbRr10hRb6pBl420upbp4++vg==", + "dev": true, + "optional": true + }, + "puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "optional": true, + "requires": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "dependencies": { + "devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "optional": true + } + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "optional": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + } + } + }, + "ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "optional": true, + "requires": {} + }, + "yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "optional": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "zip-stream": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-5.0.1.tgz", + "integrity": "sha512-UfZ0oa0C8LI58wJ+moL46BDIMgCQbnsb+2PoiJYtonhBsMh2bq1eRBVkvjfVsqbEHd9/EgKPUuL9saSSsec8OA==", + "dev": true, + "optional": true, + "requires": { + "archiver-utils": "^4.0.1", + "compress-commons": "^5.0.1", + "readable-stream": "^3.6.0" + } } } }, - "expect-webdriverio": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-2.0.2.tgz", - "integrity": "sha512-dst0tqP1aZ2p7TPmbatqoIQ+7hRTw+IeKNi830XxKhu2DNNe5vQ85i9ttf9rpXgbnUf91HxKcocn4G7A5bQxDA==", - "dev": true, - "requires": { - "expect": "^26.6.2", - "jest-matcher-utils": "^26.6.2" - } - }, "express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -33639,6 +39255,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -33669,13 +39291,28 @@ "pend": "~1.2.0" } }, - "fibers": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.3.tgz", - "integrity": "sha512-/qYTSoZydQkM21qZpGLDLuCq8c+B8KhuCQ1kLPvnRNhxhVbvrpmH9l2+Lblf5neDuEsY4bfT7LeO553TXQDvJw==", + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "dev": true + }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "dev": true, "requires": { - "detect-libc": "^1.0.3" + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "dependencies": { + "web-streams-polyfill": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.2.tgz", + "integrity": "sha512-3pRGuxRF5gpuZc0W+EpwQRmCD7gRqcDOMt688KmdlDAgAyaB1XlN0zq2njfDNm44XVdIouE7pZ6GzbdyH47uIQ==", + "dev": true + } } }, "figures": { @@ -33722,9 +39359,9 @@ } }, "minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -34046,6 +39683,24 @@ "integrity": "sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==", "dev": true }, + "foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -34069,6 +39724,31 @@ "mime-types": "^2.1.12" } }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true + }, + "formdata-node": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", + "integrity": "sha512-8xnIjMYGKPj+rY2BTbAmpqVpi8der/2FT4d9f7J32FlsCpO5EzZPq3C/N56zdv8KweHzVF6TGijsS1JT6r1H2g==", + "dev": true, + "requires": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + } + }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -34229,9 +39909,9 @@ } }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.5", @@ -34266,6 +39946,130 @@ "globule": "^1.0.0" } }, + "geckodriver": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.3.1.tgz", + "integrity": "sha512-ol7JLsj55o5k+z7YzeSy2mdJROXMAxIa+uzr3A1yEMr5HISqQOTslE3ZeARcxR4jpAY3fxmHM+sq32qbe/eXfA==", + "dev": true, + "requires": { + "@wdio/logger": "^8.24.12", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.4", + "unzipper": "^0.10.14", + "which": "^4.0.0" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true + }, + "decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "dev": true + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true + }, + "node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "requires": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + } + }, + "tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "requires": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dev": true, + "requires": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "requires": { + "isexe": "^3.1.1" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -34284,13 +40088,14 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-package-type": { @@ -34300,9 +40105,9 @@ "dev": true }, "get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", + "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", "dev": true }, "get-stream": { @@ -34324,6 +40129,46 @@ "get-intrinsic": "^1.1.1" } }, + "get-uri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.2.tgz", + "integrity": "sha512-5KLucCJobh8vBY1K07EFV4+cPZH3mrV9YeAruUseCQKHB58SGjjT2l9/eA9LD082IiuMjSlFJEcdJ27TXvbZNw==", + "dev": true, + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.0", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -34339,6 +40184,12 @@ "assert-plus": "^1.0.0" } }, + "git-repo-info": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.1.tgz", + "integrity": "sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==", + "dev": true + }, "git-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", @@ -34358,6 +40209,23 @@ "git-up": "^7.0.0" } }, + "gitconfiglocal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz", + "integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==", + "dev": true, + "requires": { + "ini": "^1.3.2" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, "github-slugger": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", @@ -34797,6 +40665,14 @@ "sparkles": "^1.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "got": { "version": "11.8.5", "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", @@ -34828,12 +40704,6 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -36224,14 +42094,18 @@ } }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "requires": { - "get-intrinsic": "^1.1.1" + "get-intrinsic": "^1.2.2" } }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -36304,6 +42178,14 @@ } } }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } + }, "hast-util-is-element": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-2.1.2.tgz", @@ -36353,6 +42235,12 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "headers-utils": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.5.tgz", + "integrity": "sha512-DAzV5P/pk3wTU/8TLZN+zFTDv4Xa1QDTU8pRvovPetcOMbmqq8CwsAvZBLPZHH6usxyy31zMp7I4aCYb6XIf6w==", + "dev": true + }, "highlight.js": { "version": "11.6.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz", @@ -36434,6 +42322,27 @@ "requires-port": "^1.0.0" } }, + "http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + } + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -36465,6 +42374,12 @@ "debug": "4" } }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -36503,18 +42418,18 @@ } } }, + "import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", - "dev": true - }, "individual": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", @@ -36543,26 +42458,26 @@ "dev": true }, "inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "dependencies": { "ansi-styles": { @@ -36575,14 +42490,16 @@ } }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true }, "color-convert": { "version": "2.0.1", @@ -36599,35 +42516,65 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true }, - "rxjs": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.7.tgz", - "integrity": "sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==", + "figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", "dev": true, "requires": { - "tslib": "^2.1.0" + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true + }, + "mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true + }, + "run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "tslib": "^2.1.0" } }, "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } } } }, @@ -36663,6 +42610,12 @@ "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==", "dev": true }, + "ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -37290,16 +43243,26 @@ "textextensions": "^3.2.0" } }, + "jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jake": { - "version": "10.8.5", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", - "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", "dev": true, "requires": { "async": "^3.2.3", "chalk": "^4.0.2", - "filelist": "^1.0.1", - "minimatch": "^3.0.4" + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "dependencies": { "ansi-styles": { @@ -37312,9 +43275,9 @@ } }, "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "chalk": { @@ -37359,16 +43322,85 @@ } } }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "requires": { "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "dependencies": { "ansi-styles": { @@ -37422,22 +43454,21 @@ } } }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "dependencies": { "ansi-styles": { @@ -37480,6 +43511,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -37491,21 +43528,18 @@ } } }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", + "@jest/types": "^29.6.3", + "@types/node": "*", "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "dependencies": { "ansi-styles": { @@ -37548,12 +43582,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -37565,12 +43593,6 @@ } } }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, "jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -38070,9 +44092,9 @@ "dev": true }, "keyv": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.0.tgz", - "integrity": "sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -38101,9 +44123,9 @@ } }, "ky": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.29.0.tgz", - "integrity": "sha512-01TBSOqlHmLfcQhHseugGHLxPtU03OyZWaLDWt5MfzCkijG6xWFvAQPhKVn0cR2MMjYvBP9keQ8A3+rQEhLO5g==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", + "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", "dev": true }, "last-run": { @@ -38301,6 +44323,25 @@ "json5": "^2.1.2" } }, + "locate-app": { + "version": "2.2.13", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.2.13.tgz", + "integrity": "sha512-1jp6iRFrHKBj9vq6Idb0cSjly+KnCIMbxZ2BBKSEzIC4ZJosv47wnLoiJu2EgOAdjhGvNcy/P2fbDCS/WziI8g==", + "dev": true, + "requires": { + "n12": "1.8.16", + "type-fest": "2.13.0", + "userhome": "1.0.0" + }, + "dependencies": { + "type-fest": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.0.tgz", + "integrity": "sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==", + "dev": true + } + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -38438,6 +44479,12 @@ "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, "lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", @@ -38550,6 +44597,28 @@ "streamroller": "^3.1.3" } }, + "logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dev": true, + "requires": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "dev": true + } + } + }, "loglevel": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -38669,12 +44738,6 @@ "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", "dev": true }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true - }, "map-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", @@ -39547,6 +45610,18 @@ "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", "dev": true }, + "minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true + }, + "mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -39557,12 +45632,6 @@ "is-extendable": "^1.0.1" } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -39928,6 +45997,12 @@ "global": "^4.4.0" } }, + "n12": { + "version": "1.8.16", + "resolved": "https://registry.npmjs.org/n12/-/n12-1.8.16.tgz", + "integrity": "sha512-CZqHAqbzS0UsaUGkMsL+lMaYLyFr1+/ea+pD8dMziqSjkcuWVWDtgWx9phyfT7C3llqQ2+LwnStSb5afggBMfA==", + "dev": true + }, "nan": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", @@ -40007,6 +46082,12 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true + }, "next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -40068,6 +46149,12 @@ } } }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -40092,6 +46179,18 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, + "node-request-interceptor": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.6.3.tgz", + "integrity": "sha512-8I2V7H2Ch0NvW7qWcjmS0/9Lhr0T6x7RD6PDirhvWEkUQvy83x8BA4haYMr09r/rig7hcgYSjYh6cd4U7G1vLA==", + "dev": true, + "requires": { + "@open-draft/until": "^1.0.3", + "debug": "^4.3.0", + "headers-utils": "^1.2.0", + "strict-event-emitter": "^0.1.0" + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -40576,6 +46675,54 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "pac-proxy-agent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz", + "integrity": "sha512-ASV8yU4LLKBAjqIPMbrgtaKIvxQri/yh2OpI+S6hVa9JRkUI3Y3NPFbfngDtY7oFtSMD3w31Xns89mDa3Feo5A==", + "dev": true, + "requires": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "pac-resolver": "^7.0.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + } + } + }, + "pac-resolver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.0.tgz", + "integrity": "sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==", + "dev": true, + "requires": { + "degenerator": "^5.0.0", + "ip": "^1.1.8", + "netmask": "^2.0.2" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -40699,6 +46846,24 @@ "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", "dev": true }, + "path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "requires": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true + } + } + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -40723,6 +46888,12 @@ } } }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, "pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -40847,39 +47018,20 @@ "dev": true }, "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "dependencies": { "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } @@ -40944,6 +47096,49 @@ "ipaddr.js": "1.9.1" } }, + "proxy-agent": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.1.tgz", + "integrity": "sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + } + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -40965,12 +47160,6 @@ "event-stream": "=3.3.4" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true - }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -41101,6 +47290,12 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -41133,9 +47328,9 @@ } }, "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, "read-pkg": { @@ -41795,6 +47990,12 @@ "mri": "^1.1.0" } }, + "safaridriver": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-0.1.2.tgz", + "integrity": "sha512-4R309+gWflJktzPXBQCobbWEHlzC4aK3a+Ov3tz2Ib2aBxiwd11phkdIBH1l0EO22x24CJMUQkpKFumRriCSRg==", + "dev": true + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -41826,6 +48027,12 @@ "is-regex": "^1.1.4" } }, + "safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -42034,6 +48241,18 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, + "set-function-length": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", + "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "requires": { + "define-data-property": "^1.1.1", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -42191,6 +48410,12 @@ } } }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -42399,6 +48624,46 @@ "debug": "~4.3.1" } }, + "socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "requires": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "dependencies": { + "ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + } + } + }, + "socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "dependencies": { + "agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "requires": { + "debug": "^4.3.4" + } + } + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -42525,26 +48790,10 @@ } }, "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "requires": { - "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true }, "sprintf-js": { "version": "1.0.3", @@ -42576,9 +48825,9 @@ "dev": true }, "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -42741,6 +48990,22 @@ } } }, + "streamx": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", + "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "dev": true, + "requires": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "strict-event-emitter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.1.0.tgz", + "integrity": "sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -42775,6 +49040,17 @@ "strip-ansi": "^6.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, "string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -42816,6 +49092,15 @@ "ansi-regex": "^5.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -42834,18 +49119,18 @@ "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", "dev": true }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, "strip-json-comments": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", "integrity": "sha512-V1LGY4UUo0jgwC+ELQ2BNWfPa17TIuwBLg+j1AA/9RPzKINl1lhxVEu2r+ZTTO8aetIsUzE5Qj6LMSBkoGYKKw==", "dev": true }, - "suffix": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/suffix/-/suffix-0.1.1.tgz", - "integrity": "sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -43332,6 +49617,12 @@ "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", "dev": true }, + "triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "dev": true + }, "trough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", @@ -43429,6 +49720,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true, + "optional": true, "peer": true }, "typescript-compare": { @@ -43525,6 +49817,12 @@ "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -43815,6 +50113,12 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "userhome": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.0.tgz", + "integrity": "sha512-ayFKY3H+Pwfy4W98yPdtH1VqH4psDeyW8lYYFzfecR9d6hqLpqhecktvYR3SEEXt7vG0S1JEpciI3g94pMErig==", + "dev": true + }, "util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -44198,6 +50502,74 @@ "he": "^1.2.0" } }, + "wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "walk": { "version": "2.3.15", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.15.tgz", @@ -44226,89 +50598,133 @@ "defaults": "^1.0.3" } }, + "web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true + }, "webdriver": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.5.3.tgz", - "integrity": "sha512-cDTn/hYj5x8BYwXxVb/WUwqGxrhCMP2rC8ttIWCfzmiVtmOnJGulC7CyxU3+p9Q5R/gIKTzdJOss16dhb+5CoA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.29.1.tgz", + "integrity": "sha512-D3gkbDUxFKBJhNHRfMriWclooLbNavVQC1MRvmENAgPNKaHnFn+M+WtP9K2sEr0XczLGNlbOzT7CKR9K5UXKXA==", "dev": true, "requires": { - "@wdio/config": "7.5.3", - "@wdio/logger": "7.5.3", - "@wdio/protocols": "7.5.3", - "@wdio/types": "7.5.3", - "@wdio/utils": "7.5.3", - "got": "^11.0.2", - "lodash.merge": "^4.6.1" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "8.29.1", + "@wdio/logger": "8.28.0", + "@wdio/protocols": "8.24.12", + "@wdio/types": "8.29.1", + "@wdio/utils": "8.29.1", + "deepmerge-ts": "^5.1.0", + "got": "^12.6.1", + "ky": "^0.33.0", + "ws": "^8.8.0" }, "dependencies": { - "@wdio/logger": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.5.3.tgz", - "integrity": "sha512-r9EADpm0ncS1bDQSWi/nhF9C59/WNLbdAAFlo782E9ItFCpDhNit3aQP9vETv1vFxJRjUIM8Fw/HW8zwPadkbw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } + "@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true }, - "@wdio/types": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.5.3.tgz", - "integrity": "sha512-jmumhKBhNDABnpmrshYLEcdE9WoP5tmynsDNbDABlb/W8FFiLySQOejukhYIL9CLys4zXerV3/edks0SCzHOiQ==", + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "requires": { - "got": "^11.8.1" + "defer-to-connect": "^2.0.1" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true + }, + "cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true }, - "has-flag": { + "mimic-response": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "lowercase-keys": "^3.0.0" } } } @@ -44883,44 +51299,26 @@ "is-typed-array": "^1.1.9" } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "winston-transport": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", + "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } } } @@ -44980,6 +51378,43 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -45069,94 +51504,6 @@ } } }, - "yarn-install": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yarn-install/-/yarn-install-1.0.0.tgz", - "integrity": "sha512-VO1u181msinhPcGvQTVMnHVOae8zjX/NSksR17e6eXHRveDvHCF5mGjh9hkN8mzyfnCqcBe42LdTs7bScuTaeg==", - "dev": true, - "requires": { - "cac": "^3.0.3", - "chalk": "^1.1.3", - "cross-spawn": "^4.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true - } - } - }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 38697197d0b..9d4347eeab1 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,16 @@ "author": "the prebid.js contributors", "license": "Apache-2.0", "engines": { - "node": ">=8.9.0" + "node": ">=12.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", - "@wdio/browserstack-service": "~7.16.0", - "@wdio/cli": "~7.5.2", - "@wdio/concise-reporter": "~7.5.2", - "@wdio/local-runner": "~7.5.2", - "@wdio/mocha-framework": "~7.5.2", - "@wdio/spec-reporter": "~7.19.0", - "@wdio/sync": "~7.5.2", + "@wdio/browserstack-service": "^8.29.0", + "@wdio/cli": "^8.29.0", + "@wdio/concise-reporter": "^8.29.0", + "@wdio/local-runner": "^8.29.0", + "@wdio/mocha-framework": "^8.29.0", + "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", diff --git a/test/helpers/testing-utils.js b/test/helpers/testing-utils.js index 1336a90ecbf..3f59411ff6c 100644 --- a/test/helpers/testing-utils.js +++ b/test/helpers/testing-utils.js @@ -7,23 +7,23 @@ const utils = { testPageURL: function(name) { return `${utils.protocol}://${utils.host}:9999/test/pages/${name}` }, - waitForElement: function(elementRef, time = DEFAULT_TIMEOUT) { + waitForElement: async function(elementRef, time = DEFAULT_TIMEOUT) { let element = $(elementRef); - element.waitForExist({timeout: time}); + await element.waitForExist({timeout: time}); }, - switchFrame: function(frameRef) { - let iframe = $(frameRef); + switchFrame: async function(frameRef) { + let iframe = await $(frameRef); browser.switchToFrame(iframe); }, - loadAndWaitForElement(url, selector, pause = 3000, timeout = DEFAULT_TIMEOUT, retries = 3, attempt = 1) { - browser.url(url); - browser.pause(pause); + async loadAndWaitForElement(url, selector, pause = 3000, timeout = DEFAULT_TIMEOUT, retries = 3, attempt = 1) { + await browser.url(url); + await browser.pause(pause); if (selector != null) { try { - utils.waitForElement(selector, timeout); + await utils.waitForElement(selector, timeout); } catch (e) { if (attempt < retries) { - utils.loadAndWaitForElement(url, selector, pause, timeout, retries, attempt + 1); + await utils.loadAndWaitForElement(url, selector, pause, timeout, retries, attempt + 1); } } } @@ -35,14 +35,15 @@ const utils = { fn.call(this); if (expectGAMCreative) { expectGAMCreative = expectGAMCreative === true ? waitFor : expectGAMCreative; - it(`should render GAM creative`, () => { - utils.switchFrame(expectGAMCreative); + it(`should render GAM creative`, async () => { + await utils.switchFrame(expectGAMCreative); const creative = [ '> a > img', // banner '> div[class="card"]' // native ].map((child) => `body > div[class="GoogleActiveViewElement"] ${child}`) .join(', '); - expect($(creative).isExisting()).to.be.true; + const existing = await $(creative).isExisting(); + expect(existing).to.be.true; }); } }); diff --git a/test/spec/e2e/banner/basic_banner_ad.spec.js b/test/spec/e2e/banner/basic_banner_ad.spec.js index e8103581d9d..511b1002d80 100644 --- a/test/spec/e2e/banner/basic_banner_ad.spec.js +++ b/test/spec/e2e/banner/basic_banner_ad.spec.js @@ -19,8 +19,8 @@ setupTest({ waitFor: CREATIVE_IFRAME_CSS_SELECTOR, expectGAMCreative: true }, 'Prebid.js Banner Ad Unit Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('div-gpt-ad-1460505748561-1'); }); const targetingKeys = result['div-gpt-ad-1460505748561-1']; diff --git a/test/spec/e2e/instream/basic_instream_video_ad.spec.js b/test/spec/e2e/instream/basic_instream_video_ad.spec.js index 02d218f9175..ca5296f050c 100644 --- a/test/spec/e2e/instream/basic_instream_video_ad.spec.js +++ b/test/spec/e2e/instream/basic_instream_video_ad.spec.js @@ -18,8 +18,8 @@ setupTest({ url: TEST_PAGE_URL, waitFor: ALERT_BOX_CSS_SELECTOR, }, 'Prebid.js Instream Video Ad Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.top.pbjs.getAdserverTargeting('video1'); }); diff --git a/test/spec/e2e/longform/basic_w_bidderSettings.spec.js b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js index e8bdbcf3b4f..1b884aeca1b 100644 --- a/test/spec/e2e/longform/basic_w_bidderSettings.spec.js +++ b/test/spec/e2e/longform/basic_w_bidderSettings.spec.js @@ -10,25 +10,25 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); - it('process the bids successfully', function() { - browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_bidderSettings.html?pbjs_debug=true'); - browser.pause(7000); + it('process the bids successfully', async function() { + await browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_bidderSettings.html?pbjs_debug=true'); + await browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath, 3000); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath, 3000); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath, 3000); + await waitForElement(listOfCpmsXpath, 3000); - let listOfCpms = $$(listOfCpmsXpath); - let listOfCats = $$(listOfCategoriesXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfCats = await $$(listOfCategoriesXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -41,14 +41,14 @@ describe('longform ads not using requireExactDuration field', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js index e4ea87bab1a..e66c9eb0cd5 100644 --- a/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js +++ b/test/spec/e2e/longform/basic_w_custom_adserver_translation.spec.js @@ -10,25 +10,25 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads using custom adserver translation file', function() { this.retries(3); - it('process the bids successfully', function() { - browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_custom_adserver_translation.html?pbjs_debug=true'); - browser.pause(7000); + it('process the bids successfully', async function() { + await browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_custom_adserver_translation.html?pbjs_debug=true'); + await browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath, 3000); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath, 3000); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath); + await waitForElement(listOfCpmsXpath); - let listOfCpms = $$(listOfCpmsXpath); - let listOfCats = $$(listOfCategoriesXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfCats = await $$(listOfCategoriesXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -41,14 +41,14 @@ describe('longform ads using custom adserver translation file', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/longform/basic_w_priceGran.spec.js b/test/spec/e2e/longform/basic_w_priceGran.spec.js index b4a5272a69c..df375fb1d39 100644 --- a/test/spec/e2e/longform/basic_w_priceGran.spec.js +++ b/test/spec/e2e/longform/basic_w_priceGran.spec.js @@ -10,25 +10,25 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); - it('process the bids successfully', function() { - browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_priceGran.html?pbjs_debug=true'); - browser.pause(7000); + it('process the bids successfully', async function() { + await browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_priceGran.html?pbjs_debug=true'); + await browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath); + await waitForElement(listOfCpmsXpath); - let listOfCpms = $$(listOfCpmsXpath); - let listOfCats = $$(listOfCategoriesXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfCats = await $$(listOfCategoriesXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -41,14 +41,14 @@ describe('longform ads not using requireExactDuration field', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js index 6f9acf33061..f36c5815750 100644 --- a/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js +++ b/test/spec/e2e/longform/basic_w_requireExactDuration.spec.js @@ -10,25 +10,25 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads using requireExactDuration field', function() { this.retries(3); - it('process the bids successfully', function() { - browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_requireExactDuration.html?pbjs_debug=true'); - browser.pause(7000); + it('process the bids successfully', async function() { + await browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_w_requireExactDuration.html?pbjs_debug=true'); + await browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath); + await waitForElement(listOfCpmsXpath); - let listOfCpms = $$(listOfCpmsXpath); - let listOfCats = $$(listOfCategoriesXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfCats = await $$(listOfCategoriesXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -41,14 +41,14 @@ describe('longform ads using requireExactDuration field', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js index 1775bfafa77..2a10e46fc6d 100644 --- a/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js +++ b/test/spec/e2e/longform/basic_wo_brandCategoryExclusion.spec.js @@ -9,23 +9,23 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads without using brandCategoryExclusion', function() { this.retries(3); - it('process the bids successfully', function() { - browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_brandCategoryExclusion.html?pbjs_debug=true'); - browser.pause(7000); + it('process the bids successfully', async function() { + await browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_brandCategoryExclusion.html?pbjs_debug=true'); + await browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath); + await waitForElement(listOfCpmsXpath); - let listOfCpms = $$(listOfCpmsXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -36,14 +36,14 @@ describe('longform ads without using brandCategoryExclusion', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js index 9e92c15e5f5..a2974edca11 100644 --- a/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js +++ b/test/spec/e2e/longform/basic_wo_requireExactDuration.spec.js @@ -10,25 +10,25 @@ const uuidRegex = /(\d|\w){8}-((\d|\w){4}-){3}(\d|\w){12}/; describe('longform ads not using requireExactDuration field', function() { this.retries(3); - it('process the bids successfully', function() { + it('process the bids successfully', async function() { browser.url(protocol + '://' + host + ':9999/integrationExamples/longform/basic_wo_requireExactDuration.html?pbjs_debug=true'); browser.pause(7000); const loadPrebidBtnXpath = '//*[@id="loadPrebidRequestBtn"]'; - waitForElement(loadPrebidBtnXpath); - const prebidBtn = $(loadPrebidBtnXpath); - prebidBtn.click(); - browser.pause(5000); + await waitForElement(loadPrebidBtnXpath); + const prebidBtn = await $(loadPrebidBtnXpath); + await prebidBtn.click(); + await browser.pause(5000); const listOfCpmsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[2]'; const listOfCategoriesXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[3]'; const listOfDurationsXpath = '/html/body/div[1]/div/div/div/div[1]/div[2]/div/table/tbody/tr/td[4]'; - waitForElement(listOfCpmsXpath); + await waitForElement(listOfCpmsXpath); - let listOfCpms = $$(listOfCpmsXpath); - let listOfCats = $$(listOfCategoriesXpath); - let listOfDuras = $$(listOfDurationsXpath); + let listOfCpms = await $$(listOfCpmsXpath); + let listOfCats = await $$(listOfCategoriesXpath); + let listOfDuras = await $$(listOfDurationsXpath); expect(listOfCpms.length).to.equal(listOfCats.length).and.to.equal(listOfDuras.length); for (let i = 0; i < listOfCpms.length; i++) { @@ -41,14 +41,14 @@ describe('longform ads not using requireExactDuration field', function() { } }); - it('formats the targeting keys properly', function () { + it('formats the targeting keys properly', async function () { const listOfKeyElementsXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[1]'; const listOfKeyValuesXpath = '/html/body/div[1]/div/div/div/div[2]/div[2]/div/table/tbody/tr/td[2]'; - waitForElement(listOfKeyElementsXpath); - waitForElement(listOfKeyValuesXpath); + await waitForElement(listOfKeyElementsXpath); + await waitForElement(listOfKeyValuesXpath); - let listOfKeyElements = $$(listOfKeyElementsXpath); - let listOfKeyValues = $$(listOfKeyValuesXpath); + let listOfKeyElements = await $$(listOfKeyElementsXpath); + let listOfKeyValues = await $$(listOfKeyValuesXpath); let firstKey = listOfKeyElements[0].getText(); expect(firstKey).to.equal('hb_pb_cat_dur'); diff --git a/test/spec/e2e/modules/e2e_bidderSettings.spec.js b/test/spec/e2e/modules/e2e_bidderSettings.spec.js index f8aedfea652..46251d39be3 100644 --- a/test/spec/e2e/modules/e2e_bidderSettings.spec.js +++ b/test/spec/e2e/modules/e2e_bidderSettings.spec.js @@ -26,8 +26,8 @@ setupTest({ waitFor: CREATIVE_IFRAME_CSS_SELECTOR, expectGAMCreative: true }, 'Prebid.js Bidder Settings Ad Unit Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); }); diff --git a/test/spec/e2e/modules/e2e_currency.spec.js b/test/spec/e2e/modules/e2e_currency.spec.js index e4eeeab4f5e..865c24cbeb1 100644 --- a/test/spec/e2e/modules/e2e_currency.spec.js +++ b/test/spec/e2e/modules/e2e_currency.spec.js @@ -13,8 +13,8 @@ setupTest({ waitFor: CREATIVE_IFRAME_CSS_SELECTOR, expectGAMCreative: true }, 'Prebid.js Currency Ad Unit Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); }); diff --git a/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js b/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js index ef34cdc98f1..098dee3647d 100644 --- a/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js +++ b/test/spec/e2e/multi-bidder/e2e_multiple_bidders.spec.js @@ -26,8 +26,8 @@ setupTest({ waitFor: CREATIVE_BANNER_CSS_SELECTOR, expectGAMCreative: true, }, 'Prebid.js Multiple Bidder Ad Unit Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('div-banner-native-2'); }); diff --git a/test/spec/e2e/native/basic_native_ad.spec.js b/test/spec/e2e/native/basic_native_ad.spec.js index 4167046b553..ded7ba610f2 100644 --- a/test/spec/e2e/native/basic_native_ad.spec.js +++ b/test/spec/e2e/native/basic_native_ad.spec.js @@ -26,8 +26,8 @@ setupTest({ waitFor: CREATIVE_IFRAME_CSS_SELECTOR, expectGAMCreative: true }, 'Prebid.js Native Ad Unit Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('/19968336/prebid_native_example_2'); }); diff --git a/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js index 427839fa92a..4b5c8566f28 100644 --- a/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js +++ b/test/spec/e2e/outstream/basic_outstream_video_ad.spec.js @@ -19,8 +19,8 @@ setupTest({ url: TEST_PAGE_URL, waitFor: CREATIVE_IFRAME_CSS_SELECTOR, }, 'Prebid.js Outstream Video Ad Test', function () { - it('should load the targeting keys with correct values', function () { - const result = browser.execute(function () { + it('should load the targeting keys with correct values', async function () { + const result = await browser.execute(function () { return window.pbjs.getAdserverTargeting('video_ad_unit_2'); }); @@ -30,13 +30,13 @@ setupTest({ expect(targetingKeys.hb_adid_appnexus).to.be.a('string'); }); - it('should render the video ad on the page', function() { + it('should render the video ad on the page', async function() { // skipping test in Edge due to wdio bug: https://github.com/webdriverio/webdriverio/issues/3880 // the iframe for the video does not have a name property and id is generated automatically... if (browser.capabilities.browserName !== 'edge') { - switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); - const ele = $('body > div[id*="an_video_ad_player"] > video'); - expect(ele.isExisting()).to.be.true; + await switchFrame(CREATIVE_IFRAME_CSS_SELECTOR); + const existing = await $('body > div[id*="an_video_ad_player"] > video').isExisting(); + expect(existing).to.be.true; } }); }); diff --git a/wdio.conf.js b/wdio.conf.js index 3d93f909971..d23fecd0b15 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -1,3 +1,5 @@ +const shared = require('./wdio.shared.conf.js'); + const browsers = Object.fromEntries( Object.entries(require('./browsers.json')) .filter(([k, v]) => { @@ -35,14 +37,7 @@ function getCapabilities() { } exports.config = { - specs: [ - './test/spec/e2e/**/*.spec.js', - ], - exclude: [ - // TODO: decipher original intent for "longform" tests - // they all appear to be almost exact copies - './test/spec/e2e/longform/**/*' - ], + ...shared.config, services: [ ['browserstack', { browserstackLocal: true @@ -53,17 +48,4 @@ exports.config = { maxInstances: 5, // Do not increase this, since we have only 5 parallel tests in browserstack account maxInstancesPerCapability: 1, capabilities: getCapabilities(), - logLevel: 'info', // put option here: info | trace | debug | warn| error | silent - bail: 0, - waitforTimeout: 60000, // Default timeout for all waitFor* commands. - connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response - connectionRetryCount: 3, // Default request retries count - framework: 'mocha', - mochaOpts: { - ui: 'bdd', - timeout: 60000, - compilers: ['js:babel-register'], - }, - // if you see error, update this to spec reporter and logLevel above to get detailed report. - reporters: ['spec'] } diff --git a/wdio.local.conf.js b/wdio.local.conf.js new file mode 100644 index 00000000000..772448472bf --- /dev/null +++ b/wdio.local.conf.js @@ -0,0 +1,13 @@ +const shared = require('./wdio.shared.conf.js'); + +exports.config = { + ...shared.config, + capabilities: [ + { + browserName: 'chrome', + 'goog:chromeOptions': { + args: ['headless', 'disable-gpu'], + }, + }, + ], +}; diff --git a/wdio.shared.conf.js b/wdio.shared.conf.js new file mode 100644 index 00000000000..34e1ee9c675 --- /dev/null +++ b/wdio.shared.conf.js @@ -0,0 +1,23 @@ +exports.config = { + specs: [ + './test/spec/e2e/**/*.spec.js', + ], + exclude: [ + // TODO: decipher original intent for "longform" tests + // they all appear to be almost exact copies + './test/spec/e2e/longform/**/*' + ], + logLevel: 'info', // put option here: info | trace | debug | warn| error | silent + bail: 0, + waitforTimeout: 60000, // Default timeout for all waitFor* commands. + connectionRetryTimeout: 60000, // Default timeout in milliseconds for request if Selenium Grid doesn't send response + connectionRetryCount: 3, // Default request retries count + framework: 'mocha', + mochaOpts: { + ui: 'bdd', + timeout: 60000, + compilers: ['js:babel-register'], + }, + // if you see error, update this to spec reporter and logLevel above to get detailed report. + reporters: ['spec'] +} From eee58b0adce674e4da3891ec967bad88a4a30540 Mon Sep 17 00:00:00 2001 From: Evgenii Novikov Date: Tue, 30 Jan 2024 20:05:00 +0100 Subject: [PATCH 386/760] Yandex Analytics Adapter: initial release (#10876) * Yandex Analytics Adapter: Initial release * Release preparations * Updated trackable events * Updated trackable events * tag URL * Added tests and chanded init logic * Fixed already loaded script scenario * One level of object destruction * Global domain, yandex.com * Removed script insertion logic * Update yandexAnalyticsAdapter.md --------- Co-authored-by: Stanislavsky34200 --- modules/yandexAnalyticsAdapter.js | 154 ++++++++++++++++++ modules/yandexAnalyticsAdapter.md | 36 ++++ .../modules/yandexAnalyticsAdapter_spec.js | 147 +++++++++++++++++ 3 files changed, 337 insertions(+) create mode 100644 modules/yandexAnalyticsAdapter.js create mode 100644 modules/yandexAnalyticsAdapter.md create mode 100644 test/spec/modules/yandexAnalyticsAdapter_spec.js diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js new file mode 100644 index 00000000000..045de18e21b --- /dev/null +++ b/modules/yandexAnalyticsAdapter.js @@ -0,0 +1,154 @@ +import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { logError, logInfo } from '../src/utils.js'; +import CONSTANTS from '../src/constants.json'; +import * as events from '../src/events.js'; + +const timeoutIds = {}; +const tryUntil = (operationId, conditionCb, cb) => { + if (!conditionCb()) { + cb(); + timeoutIds[operationId] = setTimeout( + () => tryUntil(conditionCb, conditionCb, cb), + 100 + ); + } +}; + +const clearTryUntilTimeouts = (timeouts) => { + timeouts.forEach((timeoutID) => { + if (timeoutIds[timeoutID]) { + clearTimeout(timeoutIds[timeoutID]); + } + }); +}; + +const SEND_EVENTS_BUNDLE_TIMEOUT = 1500; +const { + BID_REQUESTED, + BID_RESPONSE, + BID_ADJUSTMENT, + BID_WON, + BIDDER_DONE, + AUCTION_END, + BID_TIMEOUT, +} = CONSTANTS.EVENTS; + +export const EVENTS_TO_TRACK = [ + BID_REQUESTED, + BID_RESPONSE, + BID_ADJUSTMENT, + BID_WON, + BIDDER_DONE, + AUCTION_END, + BID_TIMEOUT, +]; + +const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' }), { + bufferedEvents: [], + initTimeoutId: 0, + counters: {}, + counterInitTimeouts: {}, + oneCounterInited: false, + + onEvent: (eventName, eventData) => { + const innerEvent = { + event: eventName, + data: eventData, + }; + yandexAnalytics.bufferedEvents.push(innerEvent); + }, + + sendEvents: () => { + if (yandexAnalytics.bufferedEvents.length) { + const data = yandexAnalytics.bufferedEvents.splice( + 0, + yandexAnalytics.bufferedEvents.length + ); + + Object.keys(yandexAnalytics.counters).forEach((counterId) => { + yandexAnalytics.counters[counterId].pbjs(data); + }); + } + setTimeout(yandexAnalytics.sendEvents, SEND_EVENTS_BUNDLE_TIMEOUT); + }, + + onCounterInit: (counterId) => { + yandexAnalytics.counters[counterId] = window[`yaCounter${counterId}`]; + logInfo(`Found metrika counter ${counterId}`); + if (!yandexAnalytics.oneCounterInited) { + yandexAnalytics.oneCounterInited = true; + setTimeout(() => { + yandexAnalytics.sendEvents(); + }, SEND_EVENTS_BUNDLE_TIMEOUT); + clearTimeout(yandexAnalytics.initTimeoutId); + } + }, + + enableAnalytics: (config) => { + yandexAnalytics.options = (config && config.options) || {}; + const { counters } = yandexAnalytics.options || {}; + const validCounters = counters.filter((counterId) => { + if (!counterId) { + return false; + } + + if (isNaN(counterId)) { + return false; + } + + return true; + }); + + if (!validCounters.length) { + logError('options.counters contains no valid counter ids'); + return; + } + + const unsubscribeCallbacks = [ + () => clearTryUntilTimeouts(['countersInit']), + ]; + + yandexAnalytics.initTimeoutId = setTimeout(() => { + yandexAnalytics.bufferedEvents = []; + unsubscribeCallbacks.forEach((cb) => cb()); + logError(`Can't find metrika counter after 25 seconds.`); + logError('Aborting yandex analytics provider initialization.'); + }, 25000); + + events.getEvents().forEach((event) => { + if (event && EVENTS_TO_TRACK.indexOf(event.eventType) >= 0) { + yandexAnalytics.onEvent(event.eventType, event); + } + }); + + EVENTS_TO_TRACK.forEach((eventName) => { + const eventCallback = yandexAnalytics.onEvent.bind(null, eventName); + unsubscribeCallbacks.push(() => events.off(eventName, eventCallback)); + events.on(eventName, eventCallback); + }); + + let allCountersInited = false; + tryUntil('countersInit', () => allCountersInited, () => { + allCountersInited = validCounters.reduce((result, counterId) => { + if (yandexAnalytics.counters[counterId]) { + return result && true; + } + + if (window[`yaCounter${counterId}`]) { + yandexAnalytics.onCounterInit(counterId); + return result && true; + } + + return false; + }, true); + }); + }, +}); + +adapterManager.registerAnalyticsAdapter({ + adapter: yandexAnalytics, + code: 'yandexAnalytics' +}); + +export default yandexAnalytics; diff --git a/modules/yandexAnalyticsAdapter.md b/modules/yandexAnalyticsAdapter.md new file mode 100644 index 00000000000..96d2b08320e --- /dev/null +++ b/modules/yandexAnalyticsAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Yandex Analytics Adapter +Module Type: Analytics Adapter +Maintainer: prebid@yandex-team.com +``` + +# Description + +This adapter is designed to work with [Yandex Metrica](https://metrica.yandex.com/about) - Top-5 worldwide web analytics tool. + +Disclosure: provider use Metrica Tag build based on https://github.com/yandex/metrica-tag, ~60 kB gzipped. + +## How to setup provider + +Register your application on https://metrica.yandex.com/ and get counter id. +Insert counter initialization code obtained from the page https://metrica.yandex.com/settings?id={counterId} into your html code. +Init provider like this, where `123` is your counter id. + +Note: If you have Single Page Application (SPA), [configure your tag](https://yandex.com/support/metrica/code/counter-spa-setup.html). + +```javascript +pbjs.enableAnalytics({ + provider: 'yandexAnalytics', + options: { + counters: [ + 123, + ], + }, +}); +``` + +## Where to find data + +Go to https://metrika.yandex.com/dashboard -> Prebid Analytics diff --git a/test/spec/modules/yandexAnalyticsAdapter_spec.js b/test/spec/modules/yandexAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..ca9b29d13a5 --- /dev/null +++ b/test/spec/modules/yandexAnalyticsAdapter_spec.js @@ -0,0 +1,147 @@ +import * as sinon from 'sinon'; +import yandexAnalytics, { EVENTS_TO_TRACK } from 'modules/yandexAnalyticsAdapter.js'; +import * as log from '../../../src/utils.js' +import * as events from '../../../src/events.js'; + +describe('Yandex analytics adapter testing', () => { + const sandbox = sinon.createSandbox(); + let clock; + let logError; + let getEvents; + let onEvent; + const counterId = 123; + const counterWindowKey = 'yaCounter123'; + + beforeEach(() => { + yandexAnalytics.counters = {}; + yandexAnalytics.counterInitTimeouts = {}; + yandexAnalytics.bufferedEvents = []; + yandexAnalytics.oneCounterInited = false; + clock = sinon.useFakeTimers(); + logError = sandbox.stub(log, 'logError'); + sandbox.stub(log, 'logInfo'); + getEvents = sandbox.stub(events, 'getEvents').returns([]); + onEvent = sandbox.stub(events, 'on'); + sandbox.stub(window.document, 'createElement').callsFake((tag) => { + const element = { + tag, + events: {}, + attributes: {}, + addEventListener: (event, cb) => { + element.events[event] = cb; + }, + removeEventListener: (event, cb) => { + chai.expect(element.events[event]).to.equal(cb); + }, + setAttribute: (attr, val) => { + element.attributes[attr] = val; + }, + }; + return element; + }); + }); + + afterEach(() => { + window.Ya = null; + window[counterWindowKey] = null; + sandbox.restore(); + clock.restore(); + }); + + it('fails if timeout for counter insertion is exceeded', () => { + yandexAnalytics.enableAnalytics({ + options: { + counters: [ + 123, + ], + }, + }); + clock.tick(25001); + chai.expect(yandexAnalytics.bufferedEvents).to.deep.equal([]); + sinon.assert.calledWith(logError, `Can't find metrika counter after 25 seconds.`); + sinon.assert.calledWith(logError, `Aborting yandex analytics provider initialization.`); + }); + + it('fails if no valid counters provided', () => { + yandexAnalytics.enableAnalytics({ + options: { + counters: [ + 'abc', + ], + }, + }); + sinon.assert.calledWith(logError, 'options.counters contains no valid counter ids'); + }); + + it('subscribes to events if counter is already present', () => { + window[counterWindowKey] = { + pbjs: sandbox.stub(), + }; + + getEvents.returns([ + { + eventType: EVENTS_TO_TRACK[0], + }, + { + eventType: 'Some_untracked_event', + } + ]); + const eventsToSend = [{ + event: EVENTS_TO_TRACK[0], + data: { + eventType: EVENTS_TO_TRACK[0], + } + }]; + + yandexAnalytics.enableAnalytics({ + options: { + counters: [ + counterId, + ], + }, + }); + + EVENTS_TO_TRACK.forEach((eventName, i) => { + const [event, callback] = onEvent.getCall(i).args; + chai.expect(event).to.equal(eventName); + callback(i); + eventsToSend.push({ + event: eventName, + data: i, + }); + }); + + clock.tick(1501); + + const [ sentEvents ] = window[counterWindowKey].pbjs.getCall(0).args; + chai.expect(sentEvents).to.deep.equal(eventsToSend); + }); + + it('waits for counter initialization', () => { + window.Ya = {}; + // Simulatin metrika script initialization + yandexAnalytics.enableAnalytics({ + options: { + counters: [ + counterId, + ], + }, + }); + + // Sending event + const [event, eventCallback] = onEvent.getCall(0).args; + eventCallback({}); + + const counterPbjsMethod = sandbox.stub(); + window[`yaCounter${counterId}`] = { + pbjs: counterPbjsMethod, + }; + clock.tick(2001); + + const [ sentEvents ] = counterPbjsMethod.getCall(0).args; + chai.expect(sentEvents).to.deep.equal([{ + event, + data: {}, + }]); + }); +}); From 25927069cb77b297d655f02875d353a420d24ae7 Mon Sep 17 00:00:00 2001 From: xmgiddev <133856186+xmgiddev@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:32:52 +0200 Subject: [PATCH 387/760] MgidX Bid Adapter: add optional region param (#10612) * new adapter - MgidX * add new required param host * rem host, add region --------- Co-authored-by: Evgeny Nagorny Co-authored-by: xmgiddev <> --- modules/mgidXBidAdapter.js | 13 +++++++++++-- test/spec/modules/mgidXBidAdapter_spec.js | 21 ++++++++++++--------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index 7e084977cf1..ac25a419de1 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -17,7 +17,7 @@ import { USERSYNC_DEFAULT_CONFIG } from '../src/userSync.js'; const BIDDER_CODE = 'mgidX'; const GVLID = 358; -const AD_URL = 'https://us-east-x.mgid.com/pbjs'; +const AD_URL = 'https://#{REGION}#.mgid.com/pbjs'; const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; @@ -181,9 +181,18 @@ export const spec = { placements.push(getPlacementReqData(bid)); } + const region = validBidRequests[0].params?.region; + + let url; + if (region === 'eu') { + url = AD_URL.replace('#{REGION}#', 'eu'); + } else { + url = AD_URL.replace('#{REGION}#', 'us-east-x'); + } + return { method: 'POST', - url: AD_URL, + url: url, data: request }; }, diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 978ca3de036..e0b1e1a84e9 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -6,7 +6,6 @@ import { config } from '../../../src/config'; import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync'; const bidder = 'mgidX' -const adUrl = 'https://us-east-x.mgid.com/pbjs'; describe('MGIDXBidAdapter', function () { const bids = [ @@ -19,6 +18,7 @@ describe('MGIDXBidAdapter', function () { } }, params: { + region: 'eu', placementId: 'testBanner', } }, @@ -56,6 +56,7 @@ describe('MGIDXBidAdapter', function () { } }, params: { + region: 'eu', placementId: 'testNative', } } @@ -108,8 +109,16 @@ describe('MGIDXBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); - it('Returns valid URL', function () { - expect(serverRequest.url).to.equal(adUrl); + it('Returns valid EU URL', function () { + bids[0].params.region = 'eu'; + serverRequest = spec.buildRequests(bids, bidderRequest); + expect(serverRequest.url).to.equal('https://eu.mgid.com/pbjs'); + }); + + it('Returns valid EAST URL', function () { + bids[0].params.region = 'other'; + serverRequest = spec.buildRequests(bids, bidderRequest); + expect(serverRequest.url).to.equal('https://us-east-x.mgid.com/pbjs'); }); it('Returns general data valid', function () { @@ -193,12 +202,6 @@ describe('MGIDXBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); - - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('interpretResponse', function () { From ad85b03f7fc3fef0d86011608b809558fb9bcd64 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 30 Jan 2024 13:33:57 -0800 Subject: [PATCH 388/760] Video module: log error when adUnit provides unknown player div ID (#10664) * Video module: log error when adUnit provides unknown player div ID * moves error logging to separate function * remove trailing spaces * Update modules/videoModule/coreVideo.js --------- Co-authored-by: Karim Mourra --- modules/videoModule/coreVideo.js | 5 ++++- modules/videoModule/index.js | 19 ++++++++++++++++++- test/spec/modules/videoModule/pbVideo_spec.js | 4 +++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index ce66acc2b02..fc54d0d0b98 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -218,7 +218,10 @@ export function VideoCore(parentModule_) { getOrtbContent, setAdTagUrl, onEvents, - offEvents + offEvents, + hasProviderFor(divId) { + return !!parentModule.getSubmodule(divId); + } }; } diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index b9cba60594d..bfb239210db 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -1,7 +1,7 @@ import { config } from '../../src/config.js'; import { find } from '../../src/polyfill.js'; import * as events from '../../src/events.js'; -import { mergeDeep, logWarn } from '../../src/utils.js'; +import {mergeDeep, logWarn, logError} from '../../src/utils.js'; import { getGlobal } from '../../src/prebidGlobal.js'; import CONSTANTS from '../../src/constants.json'; import { @@ -99,6 +99,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent return { init, renderBid, getOrtbVideo, getOrtbContent }; function beforeBidsRequested(nextFn, bidderRequest) { + logErrorForInvalidDivIds(bidderRequest); enrichAuction(bidderRequest); const bidsBackHandler = bidderRequest.bidsBackHandler; @@ -109,6 +110,22 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent return nextFn.call(this, bidderRequest); } + function logErrorForInvalidDivIds(bidderRequest) { + const adUnits = bidderRequest.adUnits || pbGlobal.adUnits || []; + adUnits.forEach(adUnit => { + const video = adUnit.video; + if (!video) { + return; + } + if (!video.divId) { + logError(`Missing Video player div ID for ad unit '${adUnit.code}'`); + } + if (!videoCore.hasProviderFor(video.divId)) { + logError(`Video player div ID '${video.divId}' for ad unit '${adUnit.code}' does not match any registered player`); + } + }); + } + function enrichAuction(bidderRequest) { if (mainContentDivId) { enrichOrtb2(mainContentDivId, bidderRequest); diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js index 2e26737da40..1ccd9766eab 100644 --- a/test/spec/modules/videoModule/pbVideo_spec.js +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -1,3 +1,4 @@ +import 'src/prebid.js'; import { expect } from 'chai'; import { PbVideo } from 'modules/videoModule'; import CONSTANTS from 'src/constants.json'; @@ -26,7 +27,8 @@ function resetTestVars() { onEvents: sinon.spy(), getOrtbVideo: () => ortbVideoMock, getOrtbContent: () => ortbContentMock, - setAdTagUrl: sinon.spy() + setAdTagUrl: sinon.spy(), + hasProviderFor: sinon.spy(), }; getConfigMock = () => {}; requestBidsMock = { From 081541463fa8a7e403921f9689e172608aa649b6 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 30 Jan 2024 22:45:31 +0100 Subject: [PATCH 389/760] ZetaGlobalSsp: bugfix (#10882) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 68cf7c4e4cd..4cdda3a1105 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -83,8 +83,9 @@ export const spec = { id: request.bidId, secure: secure }; - if (params.tagid) { - impData.tagid = params.tagid; + const tagid = request.params?.tagid; + if (tagid) { + impData.tagid = tagid; } if (request.mediaTypes) { for (const mediaType in request.mediaTypes) { @@ -112,8 +113,11 @@ export const spec = { impData.bidfloor = floorInfo.floor; } } - if (!impData.bidfloor && params.bidfloor) { - impData.bidfloor = params.bidfloor; + if (!impData.bidfloor) { + const bidfloor = request.params?.bidfloor; + if (bidfloor) { + impData.bidfloor = bidfloor; + } } return impData; From 951d7d0c6243ef1b81aaf57b668e0417e10b852e Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Wed, 31 Jan 2024 00:46:28 +0300 Subject: [PATCH 390/760] Yieldmo Bid Adapter : send topics in the Bid Request (#10892) * Adding topics to bid request Getting topics and adding them to bid request. * converting topics to numbers * Adding unit tests --- modules/yieldmoBidAdapter.js | 28 +++++++++++- test/spec/modules/yieldmoBidAdapter_spec.js | 47 +++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 78c2e7430e7..3dd5baf771a 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -75,7 +75,7 @@ export const spec = { const videoBidRequests = bidRequests.filter(request => hasVideoMediaType(request)); let serverRequests = []; const eids = getEids(bidRequests[0]) || []; - + const topicsData = getTopics(bidderRequest); if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', @@ -97,6 +97,9 @@ export const spec = { }), us_privacy: deepAccess(bidderRequest, 'uspConsent') || '', }; + if (topicsData) { + serverRequest.topics = topicsData; + } if (canAccessTopWindow()) { serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; @@ -161,6 +164,9 @@ export const spec = { if (videoBidRequests.length > 0) { const serverRequest = openRtbRequest(videoBidRequests, bidderRequest); + if (topicsData) { + serverRequest.topics = topicsData; + } if (eids.length) { serverRequest.user = { eids }; }; @@ -416,10 +422,28 @@ function openRtbRequest(bidRequests, bidderRequest) { openRtbRequest.auctionId = bidRequests[0].auctionId; } populateOpenRtbGdpr(openRtbRequest, bidderRequest); - return openRtbRequest; } +function getTopics(bidderRequest) { + const userData = deepAccess(bidderRequest, 'ortb2.user.data') || []; + const topicsData = userData.filter((dataObj) => { + const segtax = dataObj.ext?.segtax; + return segtax >= 600 && segtax <= 609; + })[0]; + + if (topicsData) { + let topicsObject = { + taxonomy: topicsData.ext.segtax, + classifier: topicsData.ext.segclass, + // topics needs to be array of numbers + topics: Object.values(topicsData.segment).map(i => Number(i)), + }; + return topicsObject; + } + return null; +} + /** * @param {BidRequest} bidRequest bidder request object. * @return Object OpenRTB's 'imp' (impression) object diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index edb3ef3af27..6d68ee79170 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -394,6 +394,25 @@ describe('YieldmoAdapter', function () { expect(placementInfo).to.include('"gpid":"/6355419/Travel/Europe/France/Paris"'); }); + it('should add topics to the banner bid request', function () { + const biddata = build([mockBannerBid()], mockBidderRequest({ortb2: { user: { + data: [ + { + ext: { + segtax: 600, + segclass: '2206021246', + }, + segment: ['7', '8', '9'], + }, + ], + }}})); + expect(biddata[0].data.topics).to.deep.equal({ + taxonomy: 600, + classifier: '2206021246', + topics: [7, 8, 9], + }); + }); + it('should add eids to the banner bid request', function () { const params = { userIdAsEids: [{ @@ -620,6 +639,34 @@ describe('YieldmoAdapter', function () { }; expect(buildAndGetData([mockVideoBid({...params})]).user.eids).to.eql(params.fakeUserIdAsEids); }); + + it('should add topics to the bid request', function () { + let videoBidder = mockBidderRequest( + { + ortb2: { + user: { + data: [ + { + ext: { + segtax: 600, + segclass: '2206021246', + }, + segment: ['7', '8', '9'], + }, + ], + }, + }, + }, + [mockVideoBid()] + ); + let payload = buildAndGetData([mockVideoBid()], 0, videoBidder); + expect(payload.topics).to.deep.equal({ + taxonomy: 600, + classifier: '2206021246', + topics: [7, 8, 9], + }); + }); + it('should add device info to payload if available', function () { let videoBidder = mockBidderRequest({ ortb2: { device: { From 2171ca4bde6e71b0889f5ba882d6b941dcdbef72 Mon Sep 17 00:00:00 2001 From: freemmy Date: Wed, 31 Jan 2024 03:20:02 +0530 Subject: [PATCH 391/760] SilverMob adapter initial commit (#10896) --- modules/silvermobBidAdapter.js | 76 +++++ modules/silvermobBidAdapter.md | 70 ++++ test/spec/modules/silvermobBidAdapter_spec.js | 301 ++++++++++++++++++ 3 files changed, 447 insertions(+) create mode 100644 modules/silvermobBidAdapter.js create mode 100644 modules/silvermobBidAdapter.md create mode 100644 test/spec/modules/silvermobBidAdapter_spec.js diff --git a/modules/silvermobBidAdapter.js b/modules/silvermobBidAdapter.js new file mode 100644 index 00000000000..340dc9c70ac --- /dev/null +++ b/modules/silvermobBidAdapter.js @@ -0,0 +1,76 @@ +// import { logMessage } from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; + +import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'silvermob'; +const AD_URL = 'https://{HOST}.silvermob.com/marketplace/api/dsp/prebidjs/{ZONEID}'; +const GVLID = 1058; + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 30 + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + if (!imp.bidfloor) imp.bidfloor = bidRequest.params.bidfloor || 0; + imp.ext = { + [BIDDER_CODE]: { + zoneid: bidRequest.params.zoneid, + host: bidRequest.params.host || 'us', + } + } + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + const bid = context.bidRequests[0]; + request.test = config.getConfig('debug') ? 1 : 0; + if (!request.cur) request.cur = [bid.params.currency || 'USD']; + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + bidResponse.cur = bid.cur || 'USD'; + return bidResponse; + } +}); + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid) => { + return Boolean(bid.bidId && bid.params && !isNaN(bid.params.zoneid)); + }, + + buildRequests: (validBidRequests, bidderRequest) => { + if (validBidRequests && validBidRequests.length === 0) return []; + + const host = validBidRequests[0].params.host || 'us'; + const zoneid = validBidRequests[0].params.zoneid; + + const data = converter.toORTB({ bidRequests: validBidRequests, bidderRequest }); + + return { + method: 'POST', + url: AD_URL.replace('{HOST}', host).replace('{ZONEID}', zoneid), + data: data + }; + }, + + interpretResponse: (response, request) => { + if (response?.body) { + const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + return bids; + } + return []; + } + +}; + +registerBidder(spec); diff --git a/modules/silvermobBidAdapter.md b/modules/silvermobBidAdapter.md new file mode 100644 index 00000000000..ba080ec105e --- /dev/null +++ b/modules/silvermobBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +``` +Module Name: SilverMob Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@silvermob.com +``` + +# Description + +Module that connects to SilverMob platform + +# Test Parameters +``` + var adUnits = [ + // Will return static native ad. Assets are stored through user UI for each placement separetly + { + code: 'placementId_0', + mediaTypes: { + native: {} + }, + bids: [ + { + bidder: 'silvermob', + params: { + host: 'us', + zoneid: '0' + } + } + ] + }, + // Will return static test banner + { + code: 'placementId_0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'silvermob', + params: { + host: 'us', + zoneid: '0' + } + } + ] + }, + // Will return test vast xml. All video params are stored under placement in publishers UI + { + code: 'placementId_0', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [ + { + bidder: 'silvermob', + params: { + host: 'us', + zoneid: '0' + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/silvermobBidAdapter_spec.js b/test/spec/modules/silvermobBidAdapter_spec.js new file mode 100644 index 00000000000..7d7fbacc04e --- /dev/null +++ b/test/spec/modules/silvermobBidAdapter_spec.js @@ -0,0 +1,301 @@ +import { expect } from 'chai'; +import {spec} from '../../../modules/silvermobBidAdapter.js'; +import 'modules/priceFloors.js'; +import { newBidder } from 'src/adapters/bidderFactory'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; + +// load modules that register ORTB processors +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; + +const SIMPLE_BID_REQUEST = { + bidder: 'silvermob', + params: { + zoneid: '0', + host: 'us', + }, + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [300, 600], + ], + }, + }, + adUnitCode: 'div-gpt-ad-1499748733608-0', + transactionId: 'f183e871-fbed-45f0-a427-c8a63c4c01eb', + bidId: '33e9500b21129f', + bidderRequestId: '2772c1e566670b', + auctionId: '192721e36a0239', + sizes: [[300, 250], [160, 600]], + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', + }, +} + +const BANNER_BID_REQUEST = { + bidder: 'silvermob', + params: { + zoneid: '0', + host: 'us', + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + code: 'banner_example', + timeout: 1000, +} + +const VIDEO_BID_REQUEST = { + placementCode: '/DfpAccount1/slotVideo', + bidId: 'test-bid-id-2', + mediaTypes: { + video: { + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + bidder: 'silvermob', + params: { + zoneid: '0', + host: 'us', + }, + adUnitCode: '/adunit-code/test-path', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, +} + +const NATIVE_BID_REQUEST = { + code: 'native_example', + mediaTypes: { + native: { + title: { + required: true, + len: 800 + }, + image: { + required: true, + len: 80 + }, + sponsoredBy: { + required: true + }, + clickUrl: { + required: true + }, + privacyLink: { + required: false + }, + body: { + required: true + }, + icon: { + required: true, + sizes: [50, 50] + } + } + }, + bidder: 'silvermob', + params: { + zoneid: '0', + host: 'us', + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + timeout: 1000, + uspConsent: 'uspConsent' +}; + +const bidderRequest = { + refererInfo: { + page: 'https://publisher.com/home', + ref: 'https://referrer' + } +}; + +const gdprConsent = { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, + addtlConsent: '1~1.35.41.101', +} + +describe('silvermobAdapter', 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('with user privacy regulations', function () { + it('should send the Coppa "required" flag set to "1" in the request', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(true); + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(serverRequest.data.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should send the GDPR Consent data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({ ...bidderRequest, gdprConsent })); + expect(serverRequest.data.regs.ext.gdpr).to.exist.and.to.equal(1); + expect(serverRequest.data.user.ext.consent).to.equal('CONSENT'); + }); + + it('should send the CCPA data in the request', function () { + const serverRequest = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest({...bidderRequest, ...{ uspConsent: '1YYY' }})); + expect(serverRequest.data.regs.ext.us_privacy).to.equal('1YYY'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(true); + }); + + it('should return false when zoneid is missing', function () { + let localbid = Object.assign({}, BANNER_BID_REQUEST); + delete localbid.params.zoneid; + expect(spec.isBidRequestValid(BANNER_BID_REQUEST)).to.equal(false); + }); + }); + + describe('build request', function () { + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], syncAddFPDToBidderRequest(bidderRequest)); + expect(bidRequest).to.be.an('array'); + expect(bidRequest.length).to.equal(0); + }); + + it('should return a valid bid request object', function () { + const request = spec.buildRequests([SIMPLE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request).to.not.equal('array'); + expect(request.data).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://us.silvermob.com/marketplace/api/dsp/prebidjs/0'); + + expect(request.data.site).to.have.property('page'); + expect(request.data.site).to.have.property('domain'); + expect(request.data).to.have.property('id'); + expect(request.data).to.have.property('imp'); + expect(request.data).to.have.property('device'); + }); + + it('should return a valid bid BANNER request object', function () { + const request = spec.buildRequests([BANNER_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].banner).to.exist; + expect(request.data.imp[0].banner.format[0].w).to.be.an('number'); + expect(request.data.imp[0].banner.format[0].h).to.be.an('number'); + }); + + if (FEATURES.VIDEO) { + it('should return a valid bid VIDEO request object', function () { + const request = spec.buildRequests([VIDEO_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0].video).to.exist; + expect(request.data.imp[0].video.w).to.be.an('number'); + expect(request.data.imp[0].video.h).to.be.an('number'); + }); + } + + it('should return a valid bid NATIVE request object', function () { + const request = spec.buildRequests([NATIVE_BID_REQUEST], syncAddFPDToBidderRequest(bidderRequest)); + expect(request.data.imp[0]).to.be.an('object'); + }); + }) + + describe('interpretResponse', function () { + let bidRequests, bidderRequest; + beforeEach(function () { + bidRequests = [{ + 'bidId': '28ffdk2B952532', + 'bidder': 'silvermob', + 'userId': { + 'freepassId': { + 'userIp': '172.21.0.1', + 'userId': '123', + 'commonId': 'commonIdValue' + } + }, + 'adUnitCode': 'adunit-code', + 'params': { + 'publisherId': 'publisherIdValue' + } + }]; + bidderRequest = {}; + }); + + it('Empty response must return empty array', function () { + const emptyResponse = null; + let response = spec.interpretResponse(emptyResponse, BANNER_BID_REQUEST); + + expect(response).to.be.an('array').that.is.empty; + }) + + it('Should interpret banner response', function () { + const serverResponse = { + body: { + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'impid': '28ffdk2B952532', + 'price': 97, + 'adm': '', + 'w': 300, + 'h': 250, + 'crid': 'creative0' + }] + }] + } + }; + it('should interpret server response', function () { + const bidRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(serverResponse, bidRequest); + expect(bids).to.be.an('array'); + const bid = bids[0]; + expect(bid).to.be.an('object'); + expect(bid.currency).to.equal('USD'); + expect(bid.cpm).to.equal(97); + expect(bid.ad).to.equal(ad) + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.creativeId).to.equal('creative0'); + }); + }) + }); +}); From ffd57060e9a98f3d1e5c1c7ead59041982f0b3d8 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 31 Jan 2024 11:43:11 -0500 Subject: [PATCH 392/760] Update appnexusBidAdapter.js (#11009) --- modules/appnexusBidAdapter.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index e62824cb2aa..c7d6fbd95a5 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -439,11 +439,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { function checkGppStatus(gppConsent) { - // this is a temporary measure to supress usersync in US-based GPP regions - // this logic will be revised when proper signals (akin to purpose1 from TCF2) can be determined for US GPP - if (gppConsent && Array.isArray(gppConsent.applicableSections)) { - return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); - } + // user sync suppression for adapters is handled in activity controls and not needed in adapters return true; } From 4fbb1c7434af4172c5ebad5f85e6a807e5fa0733 Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Wed, 31 Jan 2024 11:06:48 -0800 Subject: [PATCH 393/760] Topics fpd module : fix for intermitent failing test (#11013) * test pr for circleci * fix for failing topicsFpdModule tests * formatting fix * minor refactor * triggered another circle ci build --- modules/topicsFpdModule.js | 4 +++- test/spec/modules/topicsFpdModule_spec.js | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 617ce0ad6f4..a31d72fc179 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -14,9 +14,11 @@ const MODULE_NAME = 'topicsFpd'; const DEFAULT_EXPIRATION_DAYS = 21; const DEFAULT_FETCH_RATE_IN_DAYS = 1; let LOAD_TOPICS_INITIALISE = false; +let iframeLoadedURL = []; export function reset() { LOAD_TOPICS_INITIALISE = false; + iframeLoadedURL = []; } const bidderIframeList = { @@ -29,11 +31,11 @@ const bidderIframeList = { iframeURL: 'https://topics.authorizedvault.com/topicsapi.html' }] } + export const coreStorage = getCoreStorageManager(MODULE_NAME); export const topicStorageName = 'prebid:topics'; export const lastUpdated = 'lastUpdated'; -const iframeLoadedURL = []; const TAXONOMIES = { // map from topic taxonomyVersion to IAB segment taxonomy '1': 600, diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index bc7df85db0d..4a79e7f77fd 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -384,6 +384,22 @@ describe('topics', () => { }); describe('cross-frame messages', () => { + before(() => { + config.setConfig({ + userSync: { + topics: { + maxTopicCaller: 3, + bidders: [ + { + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + } + ], + }, + } + }); + }); + beforeEach(() => { // init iframe logic so that the receiveMessage origin check passes loadTopicsForBidders({ @@ -398,6 +414,10 @@ describe('topics', () => { }); }); + after(() => { + config.resetConfig(); + }) + it('should store segments if receiveMessage event is triggered with segment data', () => { receiveMessage(evt); let segments = new Map(safeJSONParse(storage.getDataFromLocalStorage(topicStorageName))); @@ -421,11 +441,13 @@ describe('handles fetch request for topics api headers', () => { beforeEach(() => { stubbedFetch = sinon.stub(window, 'fetch'); + reset(); }); afterEach(() => { stubbedFetch.restore(); storage.removeDataFromLocalStorage(topicStorageName); + config.resetConfig(); }); it('should make a fetch call when a fetchUrl is present for a selected bidder', () => { @@ -444,6 +466,7 @@ describe('handles fetch request for topics api headers', () => { }); stubbedFetch.returns(Promise.resolve(true)); + loadTopicsForBidders({ browsingTopics: true, featurePolicy: { From 1b4a1ce2121b8a267899b59323b220d228f88bfc Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Wed, 31 Jan 2024 15:45:00 -0500 Subject: [PATCH 394/760] Appnexus Bid Adapter: Update DSA field names (#11027) --- modules/appnexusBidAdapter.js | 12 +++--------- test/spec/modules/appnexusBidAdapter_spec.js | 10 +++++----- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index c7d6fbd95a5..a6dc05a101f 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -354,22 +354,16 @@ export const spec = { if (bidderRequest?.ortb2?.regs?.ext?.dsa) { const pubDsaObj = bidderRequest.ortb2.regs.ext.dsa; const dsaObj = {}; - ['required', 'pubrender', 'datatopub'].forEach((dsaKey) => { + ['dsarequired', 'pubrender', 'datatopub'].forEach((dsaKey) => { if (isNumber(pubDsaObj[dsaKey])) { - if (dsaKey === 'required') { - // our client-side endpoint has a different name for the 'required' field - // ...this is the only exception to the openrtb spec for these fields - dsaObj.dsainfo = pubDsaObj[dsaKey]; - } else { - dsaObj[dsaKey] = pubDsaObj[dsaKey]; - } + dsaObj[dsaKey] = pubDsaObj[dsaKey]; } }); if (isArray(pubDsaObj.transparency) && pubDsaObj.transparency.every((v) => isPlainObject(v))) { const tpData = []; pubDsaObj.transparency.forEach((tpObj) => { - if (isStr(tpObj.domain) && tpObj.domain != '' && isArray(tpObj.params) && tpObj.params.every((v) => isNumber(v))) { + if (isStr(tpObj.domain) && tpObj.domain != '' && isArray(tpObj.dsaparams) && tpObj.dsaparams.every((v) => isNumber(v))) { tpData.push(tpObj); } }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c44ef475290..cf6a1704bde 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1214,15 +1214,15 @@ describe('AppNexusAdapter', function () { 'regs': { 'ext': { 'dsa': { - 'required': 1, + 'dsarequired': 1, 'pubrender': 0, 'datatopub': 1, 'transparency': [{ 'domain': 'good-domain', - 'params': [1, 2] + 'dsaparams': [1, 2] }, { 'domain': 'bad-setup', - 'params': ['1', 3] + 'dsaparams': ['1', 3] }] } } @@ -1235,12 +1235,12 @@ describe('AppNexusAdapter', function () { const payload = JSON.parse(request.data); expect(payload.dsa).to.exist; - expect(payload.dsa.dsainfo).to.equal(1); + expect(payload.dsa.dsarequired).to.equal(1); expect(payload.dsa.pubrender).to.equal(0); expect(payload.dsa.datatopub).to.equal(1); expect(payload.dsa.transparency).to.deep.equal([{ 'domain': 'good-domain', - 'params': [1, 2] + 'dsaparams': [1, 2] }]); }); From d000ec89b7f284e75ca7b678884608b24021ea6c Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Thu, 1 Feb 2024 16:34:17 +0300 Subject: [PATCH 395/760] Adding gpc in the bid request (#11028) --- modules/yieldmoBidAdapter.js | 14 ++++++++- test/spec/modules/yieldmoBidAdapter_spec.js | 33 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 3dd5baf771a..7fee4af422c 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -100,6 +100,10 @@ export const spec = { if (topicsData) { serverRequest.topics = topicsData; } + const gpc = getGPCSignal(bidderRequest); + if (gpc) { + serverRequest.gpc = gpc; + } if (canAccessTopWindow()) { serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; @@ -417,7 +421,10 @@ function openRtbRequest(bidRequests, bidderRequest) { if (schain) { openRtbRequest.schain = schain; } - + const gpc = getGPCSignal(bidderRequest); + if (gpc) { + deepSetValue(openRtbRequest, 'regs.ext.gpc', gpc); + } if (bidRequests[0].auctionId) { openRtbRequest.auctionId = bidRequests[0].auctionId; } @@ -425,6 +432,11 @@ function openRtbRequest(bidRequests, bidderRequest) { return openRtbRequest; } +function getGPCSignal(bidderRequest) { + const gpc = deepAccess(bidderRequest, 'ortb2.regs.ext.gpc'); + return gpc; +} + function getTopics(bidderRequest) { const userData = deepAccess(bidderRequest, 'ortb2.user.data') || []; const topicsData = userData.filter((dataObj) => { diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 6d68ee79170..43daa9aca41 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -413,6 +413,22 @@ describe('YieldmoAdapter', function () { }); }); + it('should send gpc in the banner bid request', function () { + const biddata = build( + [mockBannerBid()], + mockBidderRequest({ + ortb2: { + regs: { + ext: { + gpc: '1' + }, + }, + }, + }) + ); + expect(biddata[0].data.gpc).to.equal('1'); + }); + it('should add eids to the banner bid request', function () { const params = { userIdAsEids: [{ @@ -667,6 +683,23 @@ describe('YieldmoAdapter', function () { }); }); + it('should send gpc in the bid request', function () { + let videoBidder = mockBidderRequest( + { + ortb2: { + regs: { + ext: { + gpc: '1', + }, + }, + }, + }, + [mockVideoBid()] + ); + let payload = buildAndGetData([mockVideoBid()], 0, videoBidder); + expect(payload.regs.ext.gpc).to.equal('1'); + }); + it('should add device info to payload if available', function () { let videoBidder = mockBidderRequest({ ortb2: { device: { From 0ad32448b86d3fbe2eb39a9e98290cf6e89cfe3f Mon Sep 17 00:00:00 2001 From: bjorn-lw <32431346+bjorn-lw@users.noreply.github.com> Date: Thu, 1 Feb 2024 14:34:24 +0100 Subject: [PATCH 396/760] Livewrapped Bidder: add support for ortb2imp (#11026) * Added support for the Price Floors Module * Use the ad server's currency when getting prices from the floors module * Default to USD if the ad server's currency can't be retrieved * Set the default currency at the right place * Added tests and made a minor change in how device width and height are calculated * Only include flrCur when ad requests contain floors * Use ortb native * Read ortb2imp --------- Co-authored-by: msmeza --- modules/livewrappedBidAdapter.js | 6 +- .../modules/livewrappedBidAdapter_spec.js | 222 +++++++++++++++--- 2 files changed, 190 insertions(+), 38 deletions(-) diff --git a/modules/livewrappedBidAdapter.js b/modules/livewrappedBidAdapter.js index cf1e690d862..cfbd2b5b3b5 100644 --- a/modules/livewrappedBidAdapter.js +++ b/modules/livewrappedBidAdapter.js @@ -4,7 +4,6 @@ import {config} from '../src/config.js'; import {find} from '../src/polyfill.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -52,9 +51,6 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - const userId = find(bidRequests, hasUserId); const pubcid = find(bidRequests, hasPubcid); const publisherId = find(bidRequests, hasPublisherId); @@ -236,9 +232,9 @@ function bidToAdRequest(bid, currency) { adUnitId: bid.params.adUnitId, callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode, bidId: bid.bidId, - transactionId: bid.ortb2Imp?.ext?.tid, formats: getSizes(bid).map(sizeToFormat), flr: getBidFloor(bid, currency), + rtbData: bid.ortb2Imp, options: bid.params.options }; diff --git a/test/spec/modules/livewrappedBidAdapter_spec.js b/test/spec/modules/livewrappedBidAdapter_spec.js index 52eaf8d7d76..5ab00859d81 100644 --- a/test/spec/modules/livewrappedBidAdapter_spec.js +++ b/test/spec/modules/livewrappedBidAdapter_spec.js @@ -38,10 +38,9 @@ describe('Livewrapped adapter tests', function () { auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', ortb2Imp: { ext: { - tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', } }, - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' } ], start: 1472239426002, @@ -120,8 +119,49 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', - formats: [{width: 980, height: 240}, {width: 980, height: 120}] + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + } + }] + }; + + expect(data).to.deep.equal(expectedQuery); + }); + + it('should send ortb2Imp', function() { + sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false); + sandbox.stub(storage, 'cookiesAreEnabled').callsFake(() => true); + let ortb2ImpRequest = clone(bidderRequest); + ortb2ImpRequest.bids[0].ortb2Imp.ext.data = {key: 'value'}; + let result = spec.buildRequests(ortb2ImpRequest.bids, ortb2ImpRequest); + let data = JSON.parse(result.data); + + expect(result.url).to.equal('https://lwadm.com/ad'); + + let expectedQuery = { + auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C', + publisherId: '26947112-2289-405D-88C1-A7340C57E63E', + userId: 'user id', + url: 'https://www.domain.com', + seats: {'dsp': ['seat 1']}, + version: '1.4', + width: 100, + height: 100, + cookieSupport: true, + adRequests: [{ + adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', + callerAdUnitId: 'panorama_d_1', + bidId: '2ffb201a808da7', + formats: [{width: 980, height: 240}, {width: 980, height: 120}], + rtbData: { + ext: { + data: {key: 'value'}, + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + } }] }; @@ -157,12 +197,20 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }, { callerAdUnitId: 'box_d_1', bidId: '3ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 300, height: 250}] }] }; @@ -194,7 +242,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'caller id 1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -225,7 +277,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -256,7 +312,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -289,7 +349,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -322,7 +386,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -352,7 +420,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], options: {keyvalues: [{key: 'key', value: 'value'}]} }] @@ -384,7 +456,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -414,7 +490,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], native: {'nativedata': 'content parsed serverside only'} }] @@ -445,7 +525,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], native: {'nativedata': 'content parsed serverside only'}, banner: true @@ -477,7 +561,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], video: {'videodata': 'content parsed serverside only'} }] @@ -525,7 +613,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -555,7 +647,11 @@ describe('Livewrapped adapter tests', function () { adRequests: [{ callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 728, height: 90}] }] }; @@ -592,7 +688,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -627,7 +727,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -660,7 +764,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -700,7 +808,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -730,7 +842,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -760,7 +876,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -810,7 +930,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -842,7 +966,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -876,7 +1004,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -910,7 +1042,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -946,7 +1082,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -982,7 +1122,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -1018,7 +1162,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}] }] }; @@ -1063,7 +1211,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], flr: 10 }] @@ -1101,7 +1253,11 @@ describe('Livewrapped adapter tests', function () { adUnitId: '9E153CED-61BC-479E-98DF-24DC0D01BA37', callerAdUnitId: 'panorama_d_1', bidId: '2ffb201a808da7', - transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D', + rtbData: { + ext: { + tid: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D' + }, + }, formats: [{width: 980, height: 240}, {width: 980, height: 120}], flr: 10 }] From 7f0732f619e5c969f7749c995e2cf9f3d2bb94f6 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Thu, 1 Feb 2024 14:39:45 +0100 Subject: [PATCH 397/760] BeOp Bid Adapter: add eids support (#11025) * BeOp Bid Adapter: Add eids support (#14) * Fix tests --- modules/beopBidAdapter.js | 2 ++ test/spec/modules/beopBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index ad52af824ee..89289654b32 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -72,6 +72,7 @@ export const spec = { is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), gdpr_applies: gdpr ? gdpr.gdprApplies : false, tc_string: (gdpr && gdpr.gdprApplies) ? gdpr.consentString : null, + eids: firstSlot.eids, }; const payloadString = JSON.stringify(payloadObject); @@ -159,6 +160,7 @@ function beOpRequestSlotsMaker(bid) { brc: getBidIdParameter('bidRequestsCount', bid), bdrc: getBidIdParameter('bidderRequestCount', bid), bwc: getBidIdParameter('bidderWinsCount', bid), + eids: bid.userIdAsEids, } } diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index c77e304e539..663d622e505 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -312,4 +312,22 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.kwds).to.include('keywords'); }) }) + + describe('Ensure eids are get', function() { + let bidRequests = []; + afterEach(function () { + bidRequests = []; + }); + + it(`should get eids from bid`, function () { + let bid = Object.assign({}, validBid); + bid.userIdAsEids = [{source: 'provider.com', uids: [{id: 'someid', atype: 1, ext: {whatever: true}}]}]; + bidRequests.push(bid); + + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + expect(payload.eids).to.exist; + expect(payload.eids[0].source).to.equal('provider.com'); + }); + }) }); From 30513cfe1d0afbb83e7c25d497216affd3aa8050 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 1 Feb 2024 14:55:18 +0100 Subject: [PATCH 398/760] AdagioBidAdapter: don't try to compute slot position if element is hidden (#11033) --- modules/adagioBidAdapter.js | 10 +++------- test/spec/modules/adagioBidAdapter_spec.js | 7 +++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 112722383a9..efa6777a0dd 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -795,16 +795,12 @@ function getSlotPosition(adUnitElementId) { const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; const elComputedStyle = wt.getComputedStyle(domElement, null); - const elComputedDisplay = elComputedStyle.display || 'block'; - const mustDisplayElement = elComputedDisplay === 'none'; + const mustDisplayElement = elComputedStyle.display === 'none'; if (mustDisplayElement) { - domElement.style = domElement.style || {}; - const originalDisplay = domElement.style.display; - domElement.style.display = 'block'; - box = domElement.getBoundingClientRect(); - domElement.style.display = originalDisplay || null; + logWarn(LOG_PREFIX, 'The element is hidden. The slot position cannot be computed.'); } + position.x = Math.round(box.left + scrollLeft - clientLeft); position.y = Math.round(box.top + scrollTop - clientTop); } catch (err) { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 744f3c69e83..38dbaf348fd 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1497,8 +1497,8 @@ describe('Adagio bid adapter', () => { expect(result.user_timestamp).to.be.a('String'); }); - it('should return `adunit_position` feature when the slot is hidden', function () { - const elem = fixtures.getElementById(); + it('should return `adunit_position` feature when the slot is hidden with value 0x0', function () { + const elem = fixtures.getElementById('0', '0', '0', '0'); sandbox.stub(window.top.document, 'getElementById').returns(elem); sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'none' }); sandbox.stub(utils, 'inIframe').returns(false); @@ -1516,8 +1516,7 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bidRequest], bidderRequest); const result = requests[0].data.adUnits[0].features; - expect(result.adunit_position).to.match(/^[\d]+x[\d]+$/); - expect(elem.style.display).to.equal(null); // set null to reset style + expect(result.adunit_position).to.equal('0x0'); }); }); From 7d3ff582753179b6fb8272122a2d6dea8b0cbab2 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 1 Feb 2024 11:05:05 -0800 Subject: [PATCH 399/760] priceFloors: do not log error on missing floor definitions (#11037) --- modules/id5IdSystem.js | 8 ++++---- modules/priceFloors.js | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index b10f3c5d2cf..c585f2fdae0 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -51,26 +51,26 @@ export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleNam * @property {string} [universal_uid] - The encrypted ID5 ID to pass to bidders * @property {Object} [ext] - The extensions object to pass to bidders * @property {Object} [ab_testing] - A/B testing configuration -*/ + */ /** * @typedef {Object} FetchCallConfig * @property {string} [url] - The URL for the fetch endpoint * @property {Object} [overrides] - Overrides to apply to fetch parameters -*/ + */ /** * @typedef {Object} ExtensionsCallConfig * @property {string} [url] - The URL for the extensions endpoint * @property {string} [method] - Overrides the HTTP method to use to make the call * @property {Object} [body] - Specifies a body to pass to the extensions endpoint -*/ + */ /** * @typedef {Object} DynamicConfig * @property {FetchCallConfig} [fetchCall] - The fetch call configuration * @property {ExtensionsCallConfig} [extensionsCall] - The extensions call configuration -*/ + */ /** * @typedef {Object} ABTestingConfig diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 9c3869c480a..c2d7db97047 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -445,10 +445,13 @@ export function continueAuction(hookConfig) { } function validateSchemaFields(fields) { - if (Array.isArray(fields) && fields.length > 0 && fields.every(field => allowedFields.indexOf(field) !== -1)) { - return true; + if (Array.isArray(fields) && fields.length > 0) { + if (fields.every(field => allowedFields.includes(field))) { + return true; + } else { + logError(`${MODULE_NAME}: Fields received do not match allowed fields`); + } } - logError(`${MODULE_NAME}: Fields received do not match allowed fields`); return false; } From 804d078ca1fe0036cae5fdff598d8bb46261c10c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 2 Feb 2024 13:46:21 +0000 Subject: [PATCH 400/760] Prebid 8.34.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 692afaa120a..df8078e4596 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.34.0-pre", + "version": "8.34.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9d4347eeab1..9f56e033c1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.34.0-pre", + "version": "8.34.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From d53dae228b4f6565fe479ef60671478fa9e977a8 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 2 Feb 2024 13:46:21 +0000 Subject: [PATCH 401/760] Increment version to 8.35.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df8078e4596..9d7bc733e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.34.0", + "version": "8.35.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9f56e033c1b..6741b36d415 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.34.0", + "version": "8.35.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 60b6adaf5fd88d70e9f1e122706384d8eb6da237 Mon Sep 17 00:00:00 2001 From: Laurentiu Badea Date: Sat, 3 Feb 2024 07:56:42 -0800 Subject: [PATCH 402/760] OpenX Bid Adapter: add ortb2Imp to PAAPI auctionSignals (#11012) * OpenX Bid Adapter: add ortb2Imp to PAAPI auctionSignals * Use imp instead of searching ortbRequest --- modules/openxBidAdapter.js | 8 +++++--- test/spec/modules/openxBidAdapter_spec.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 0f8bee213f7..a99bd1c5325 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -107,9 +107,11 @@ const converter = ortbConverter({ fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { return { bidId, - config: Object.assign({ - auctionSignals: {}, - }, cfg) + config: mergeDeep(Object.assign({}, cfg), { + auctionSignals: { + ortb2Imp: context.impContext[bidId]?.imp, + }, + }), } }); return { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 1af0fce103d..7c504bca50b 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1508,6 +1508,25 @@ describe('OpenxRtbAdapter', function () { expect(response.fledgeAuctionConfigs.length).to.equal(1); expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); }); + + it('should inject ortb2Imp in auctionSignals', function () { + const auctionConfig = response.fledgeAuctionConfigs[0].config; + expect(auctionConfig).to.deep.include({ + auctionSignals: { + ortb2Imp: { + id: 'test-bid-id', + tagid: '12345678', + banner: { + topframe: 0, + format: bidRequestConfigs[0].mediaTypes.banner.sizes.map(([w, h]) => ({w, h})) + }, + ext: { + divid: 'adunit-code', + } + } + } + }); + }) }); }); From 9961236f8dc3e6a6d25d2aa5dc96e76bd93c801b Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Sun, 4 Feb 2024 22:16:19 +0100 Subject: [PATCH 403/760] ZetaGlobalSsp Bid Adapter : cleanup object (#11049) * ZetaGlobalSsp adapter: cleanup object * Update zeta_global_sspBidAdapter.js --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko Co-authored-by: Chris Huie --- modules/zeta_global_sspBidAdapter.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 4cdda3a1105..80856462fe1 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -178,7 +178,7 @@ export const spec = { return { method: 'POST', url: url, - data: JSON.stringify(payload), + data: JSON.stringify(clearEmpties(payload)), }; }, @@ -373,4 +373,17 @@ function provideMediaType(zetaBid, bid, bidRequest) { } } +function clearEmpties(o) { + for (let k in o) { + if (!o[k] || typeof o[k] !== 'object') { + continue; + } + clearEmpties(o[k]); + if (Object.keys(o[k]).length === 0) { + delete o[k]; + } + } + return o; +} + registerBidder(spec); From 3dc8deab02336c338ea0ab905a44b9a655d9234f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=90=91=E5=86=9B?= <401154047@qq.com> Date: Mon, 5 Feb 2024 15:59:02 +0800 Subject: [PATCH 404/760] zmaticoo Bid Adapter : add support for video (#11016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:zmaticoo adapter for video Signed-off-by: adam * feat:just test ci/circleci:build failed Signed-off-by: adam * feat:add unit test Signed-off-by: adam * feat:fix response.seatbid and advertiserDomains empty Signed-off-by: adam * feat:add unit test Signed-off-by: adam --------- Signed-off-by: adam Co-authored-by: adam --- modules/zmaticooBidAdapter.js | 201 +++++++++--- modules/zmaticooBidAdapter.md | 37 ++- test/spec/modules/zmaticooBidAdapter_spec.js | 302 ++++++++++++++++--- 3 files changed, 454 insertions(+), 86 deletions(-) diff --git a/modules/zmaticooBidAdapter.js b/modules/zmaticooBidAdapter.js index 8566d58c6de..eeb130af7d5 100644 --- a/modules/zmaticooBidAdapter.js +++ b/modules/zmaticooBidAdapter.js @@ -1,6 +1,6 @@ -import {deepAccess, logWarn} from '../src/utils.js'; +import {deepAccess, isArray, isBoolean, isNumber, isStr, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; const BIDDER_CODE = 'zmaticoo'; const ENDPOINT_URL = 'https://bid.zmaticoo.com/prebid/bid'; @@ -8,9 +8,31 @@ const DEFAULT_CUR = 'USD'; const TTL = 200; const NET_REV = true; +const DATA_TYPES = { + 'NUMBER': 'number', 'STRING': 'string', 'BOOLEAN': 'boolean', 'ARRAY': 'array', 'OBJECT': 'object' +}; +const VIDEO_CUSTOM_PARAMS = { + 'mimes': DATA_TYPES.ARRAY, + 'minduration': DATA_TYPES.NUMBER, + 'maxduration': DATA_TYPES.NUMBER, + 'startdelay': DATA_TYPES.NUMBER, + 'playbackmethod': DATA_TYPES.ARRAY, + 'api': DATA_TYPES.ARRAY, + 'protocols': DATA_TYPES.ARRAY, + 'w': DATA_TYPES.NUMBER, + 'h': DATA_TYPES.NUMBER, + 'battr': DATA_TYPES.ARRAY, + 'linearity': DATA_TYPES.NUMBER, + 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, + 'minbitrate': DATA_TYPES.NUMBER, + 'maxbitrate': DATA_TYPES.NUMBER, + 'skip': DATA_TYPES.NUMBER +} + export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -20,6 +42,10 @@ export const spec = { */ isBidRequestValid: function (bid) { // check for all required bid fields + if (!(hasBannerMediaType(bid) || hasVideoMediaType(bid))) { + logWarn('Invalid bid request - missing required mediaTypes'); + return false; + } if (!(bid && bid.bidId && bid.params)) { logWarn('Invalid bid request - missing required bid data'); return false; @@ -30,7 +56,7 @@ export const spec = { return false; } - if (!(bid.params.device && bid.params.device.ip)) { + if (!(bid.params.device)) { logWarn('Invalid bid request - missing required device data'); return false; } @@ -48,19 +74,49 @@ export const spec = { const secure = 1; const request = validBidRequests[0]; const params = request.params; - let impData = { - id: request.bidId, - secure: secure, - banner: buildBanner(request), - ext: { - bidder: { - pubId: params.pubId + const imps = validBidRequests.map(request => { + const impData = { + id: request.bidId, + secure: secure, + ext: { + bidder: { + pubId: params.pubId + } } + }; + if (params.tagid) { + impData.tagid = params.tagid; } - }; + if (request.mediaTypes) { + for (const mediaType in request.mediaTypes) { + switch (mediaType) { + case BANNER: + impData.banner = buildBanner(request); + break; + case VIDEO: + impData.video = buildVideo(request); + break; + } + } + } + if (typeof bidderRequest.getFloor === 'function') { + const floorInfo = bidderRequest.getFloor({ + currency: 'USD', + mediaType: impData.video ? 'video' : 'banner', + size: [impData.video ? impData.video.w : impData.banner.w, impData.video ? impData.video.h : impData.banner.h] + }); + if (floorInfo && floorInfo.floor) { + impData.bidfloor = floorInfo.floor; + } + } + if (!impData.bidfloor && params.bidfloor) { + impData.bidfloor = params.bidfloor; + } + return impData; + }); let payload = { id: bidderRequest.bidderRequestId, - imp: [impData], + imp: imps, site: params.site ? params.site : {}, app: params.app ? params.app : {}, device: params.device ? params.device : {}, @@ -79,19 +135,21 @@ export const spec = { regs: params.regs ? params.regs : {}, ext: params.ext ? params.ext : {} }; - + payload.regs.ext = {} + payload.user.ext = {} payload.device.ua = navigator.userAgent; payload.device.ip = navigator.ip; - payload.site.page = bidderRequest.refererInfo.page; + payload.site.page = bidderRequest?.refererInfo?.page || window.location.href; + payload.site.domain = _getDomainFromURL(payload.site.page); payload.site.mobile = /(ios|ipod|ipad|iphone|android)/i.test(navigator.userAgent) ? 1 : 0; if (params.test) { payload.test = params.test; } - if (request.gdprConsent) { - payload.regs.ext = Object.assign(payload.regs.ext, {gdpr: request.gdprConsent.gdprApplies === true ? 1 : 0}); + if (bidderRequest.gdprConsent) { + payload.regs.ext = Object.assign(payload.regs.ext, {gdpr: bidderRequest.gdprConsent.gdprApplies == true ? 1 : 0}); } - if (request.gdprConsent && request.gdprConsent.gdprApplies) { - payload.user.ext = Object.assign(payload.user.ext, {consent: request.gdprConsent.consentString}); + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + payload.user.ext = Object.assign(payload.user.ext, {consent: bidderRequest.gdprConsent.consentString}); } const postUrl = ENDPOINT_URL; return { @@ -103,28 +161,37 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {ServerResponse} serverResponse A successful response from the server. - * @param bidRequest The payload from the server's response. + * @param {BidRequest} bidRequest The payload from the server's response. * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function (serverResponse, bidRequest) { - let bidResponse = []; - if (Object.keys(serverResponse.body).length !== 0) { - let zresponse = serverResponse.body; - let zbid = zresponse.seatbid[0].bid[0]; - let bid = { - requestId: zbid.impid, - cpm: zbid.price, - currency: zbid.cur, - width: zbid.w, - height: zbid.h, - ad: zbid.adm, - ttl: TTL, - creativeId: zbid.crid, - netRevenue: NET_REV - }; - bidResponse.push(bid); + let bidResponses = []; + const response = (serverResponse || {}).body; + if (response && response.seatbid && response.seatbid.length && response.seatbid[0].bid && response.seatbid[0].bid.length) { + response.seatbid.forEach(zmSeatbid => { + zmSeatbid.bid.forEach(zmBid => { + let bid = { + requestId: zmBid.impid, + cpm: zmBid.price, + currency: response.cur, + width: zmBid.w, + height: zmBid.h, + ad: zmBid.adm, + ttl: TTL, + creativeId: zmBid.crid, + netRevenue: NET_REV, + }; + bid.meta = { + advertiserDomains: (zmBid.adomain && zmBid.adomain.length) ? zmBid.adomain : [] + }; + if (zmBid.ext && zmBid.ext.vast_url) { + bid.vastXml = zmBid.ext.vast_url; + } + bidResponses.push(bid); + }) + }) } - return bidResponse; + return bidResponses; } } @@ -138,4 +205,64 @@ function buildBanner(request) { }; } +function buildVideo(request) { + let video = {}; + const videoParams = deepAccess(request, 'mediaTypes.video', {}); + for (const key in VIDEO_CUSTOM_PARAMS) { + if (videoParams.hasOwnProperty(key)) { + video[key] = checkParamDataType(key, videoParams[key], VIDEO_CUSTOM_PARAMS[key]); + } + } + if (videoParams.playerSize) { + if (isArray(videoParams.playerSize[0])) { + video.w = parseInt(videoParams.playerSize[0][0], 10); + video.h = parseInt(videoParams.playerSize[0][1], 10); + } else if (isNumber(videoParams.playerSize[0])) { + video.w = parseInt(videoParams.playerSize[0], 10); + video.h = parseInt(videoParams.playerSize[1], 10); + } + } + return video; +} + +export function checkParamDataType(key, value, datatype) { + let functionToExecute; + switch (datatype) { + case DATA_TYPES.BOOLEAN: + functionToExecute = isBoolean; + break; + case DATA_TYPES.NUMBER: + functionToExecute = isNumber; + break; + case DATA_TYPES.STRING: + functionToExecute = isStr; + break; + case DATA_TYPES.ARRAY: + functionToExecute = isArray; + break; + } + if (functionToExecute(value)) { + return value; + } + logWarn('Ignoring param key: ' + key + ', expects ' + datatype + ', found ' + typeof value); + return undefined; +} + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +/** + * @param {BidRequest} bidRequest bid request + */ +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +export function _getDomainFromURL(url) { + let anchor = document.createElement('a'); + anchor.href = url; + return anchor.hostname; +} + registerBidder(spec); diff --git a/modules/zmaticooBidAdapter.md b/modules/zmaticooBidAdapter.md index 95959ef218c..06c54b10b11 100644 --- a/modules/zmaticooBidAdapter.md +++ b/modules/zmaticooBidAdapter.md @@ -12,6 +12,8 @@ zMaticoo Bidder Adapter for Prebid.js. # Test Parameters +## banner + ``` var adUnits = [ { @@ -23,7 +25,7 @@ zMaticoo Bidder Adapter for Prebid.js. bids: [ { bidder: 'zmaticoo', - bidId: '12345', + bidId: '123456', params: { user: { uid: '12345', @@ -43,3 +45,36 @@ zMaticoo Bidder Adapter for Prebid.js. } ]; ``` + +## video + +``` + var adUnits = [{ + code: 'test1', + mediaTypes: { + video: { + playerSize: [480, 320], + mimes: ['video/mp4'], + context: 'instream', + placement: 1, // required, integer + maxduration: 30, // required, integer + minduration: 15, // optional, integer + pos: 1, // optional, integer + startdelay: 10, // required if placement == 1 + protocols: [2, 3], // required, array of integers + api: [2, 3], // required, array of integers + playbackmethod: [2, 6], // required, array of integers + skippable: true, // optional, boolean + skipafter: 10 // optional, integer + } + }, + bids: [{ + bidder: "zmaticoo", + bidId: '123456', + params: { + pubId: 'prebid-test', + site: {domain: "test.com"} + } + }] +}]; +``` diff --git a/test/spec/modules/zmaticooBidAdapter_spec.js b/test/spec/modules/zmaticooBidAdapter_spec.js index 898dccf1fde..1634340ac6a 100644 --- a/test/spec/modules/zmaticooBidAdapter_spec.js +++ b/test/spec/modules/zmaticooBidAdapter_spec.js @@ -1,4 +1,5 @@ -import {spec} from '../../../modules/zmaticooBidAdapter.js' +import {checkParamDataType, spec} from '../../../modules/zmaticooBidAdapter.js' +import {deepClone} from '../../../src/utils'; describe('zMaticoo Bidder Adapter', function () { const bannerRequest = [{ @@ -24,60 +25,265 @@ describe('zMaticoo Bidder Adapter', function () { } }, pubId: 'prebid-test', - test: 1 + test: 1, + bidfloor: 1, + tagid: 'test' + } + }]; + const bannerRequest1 = [{ + bidId: '1234511', + auctionId: '223', + mediaTypes: { + banner: { + sizes: [[320, 50]], + } + }, + refererInfo: { + page: 'testprebid.com' + }, + params: { + user: { + uid: '12345', + buyeruid: '12345' + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + pubId: 'prebid-test', + test: 1, + tagid: 'test' + }, + gdprConsent: { + gdprApplies: 1, + consentString: 'consentString' + }, + getFloor: function () { + return { + currency: 'USD', + floor: 0.5, + } + }, + }]; + const videoRequest = [{ + bidId: '1234511', + auctionId: '223', + mediaTypes: { + video: { + playerSize: [480, 320], + mimes: ['video/mp4'], + context: 'instream', + placement: 1, + maxduration: 30, + minduration: 15, + pos: 1, + startdelay: 10, + protocols: [2, 3], + api: [2, 3], + playbackmethod: [2, 6], + skip: 10, + } + }, + refererInfo: { + page: 'testprebid.com' + }, + params: { + user: { + uid: '12345', + buyeruid: '12345' + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + pubId: 'prebid-test', + test: 1, + tagid: 'test', + bidfloor: 1 } }]; - it('Test the bid validation function', function () { - const validBid = spec.isBidRequestValid(bannerRequest[0]); - const invalidBid = spec.isBidRequestValid(null); - - expect(validBid).to.be.true; - expect(invalidBid).to.be.false; - }); - - it('Test the request processing function', function () { - const request = spec.buildRequests(bannerRequest, bannerRequest[0]); - expect(request).to.not.be.empty; + const videoRequest1 = [{ + bidId: '1234511', + auctionId: '223', + mediaTypes: { + video: { + playerSize: [[480, 320]], + mimes: ['video/mp4'], + context: 'instream', + placement: 1, + maxduration: 30, + minduration: 15, + pos: 1, + startdelay: 10, + protocols: [2, 3], + api: [2, 3], + playbackmethod: [2, 6], + skip: 10, + } + }, + params: { + user: { + uid: '12345', + buyeruid: '12345' + }, + device: { + ip: '111.222.33.44', + geo: { + country: 'USA' + } + }, + pubId: 'prebid-test', + test: 1, + tagid: 'test', + bidfloor: 1 + } + }]; - const payload = request.data; - expect(payload).to.not.be.empty; - }); + describe('isBidRequestValid', function () { + it('this is valid bidrequest', function () { + const validBid = spec.isBidRequestValid(videoRequest[0]); + expect(validBid).to.be.true; + }); + it('missing required bid data {bid}', function () { + const invalidBid = spec.isBidRequestValid(null); + expect(invalidBid).to.be.false; + }); + it('missing required bid.bidId', function () { + const request = deepClone(videoRequest[0]) + delete request.bidId + const invalidBid = spec.isBidRequestValid(request); + expect(invalidBid).to.be.false; + }); + it('missing required params.pubId', function () { + const request = deepClone(videoRequest[0]) + delete request.params.pubId + const invalidBid = spec.isBidRequestValid(request); + expect(invalidBid).to.be.false; + }); + it('missing required device data', function () { + const request = deepClone(videoRequest[0]) + delete request.params.device + const invalidBid = spec.isBidRequestValid(request); + expect(invalidBid).to.be.false; + }); + }) + describe('buildRequests', function () { + it('Test the banner request processing function', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + expect(request).to.not.be.empty; + const payload = request.data; + expect(payload).to.not.be.empty; + }); + it('Test the video request processing function', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + expect(request).to.not.be.empty; + const payload = request.data; + expect(payload).to.not.be.empty; + }); + it('Test the param', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.eql(videoRequest[0].params.tagid); + expect(payload.imp[0].bidfloor).to.eql(videoRequest[0].params.bidfloor); + }); + it('Test video object', function () { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.exist; + expect(payload.imp[0].video.minduration).to.eql(videoRequest[0].mediaTypes.video.minduration); + expect(payload.imp[0].video.maxduration).to.eql(videoRequest[0].mediaTypes.video.maxduration); + expect(payload.imp[0].video.protocols).to.eql(videoRequest[0].mediaTypes.video.protocols); + expect(payload.imp[0].video.mimes).to.eql(videoRequest[0].mediaTypes.video.mimes); + expect(payload.imp[0].video.w).to.eql(480); + expect(payload.imp[0].video.h).to.eql(320); + expect(payload.imp[0].banner).to.be.undefined; + }); - const responseBody = { - id: '12345', - seatbid: [ - { - bid: [ - { - id: 'auctionId', - impid: 'impId', - price: 0.0, - adm: 'adMarkup', - crid: 'creativeId', - h: 50, - w: 320 - } - ] - } - ], - cur: 'USD' - }; + it('Test video isArray size', function () { + const request = spec.buildRequests(videoRequest1, videoRequest1[0]); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video.w).to.eql(480); + expect(payload.imp[0].video.h).to.eql(320); + }); + it('Test banner object', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + expect(payload.imp[0].video).to.be.undefined; + expect(payload.imp[0].banner).to.exist; + }); - it('Test the response parsing function', function () { - const receivedBid = responseBody.seatbid[0].bid[0]; - const response = {}; - response.body = responseBody; + it('Test provide gdpr and ccpa values in payload', function () { + const request = spec.buildRequests(bannerRequest1, bannerRequest1[0]); + const payload = JSON.parse(request.data); + expect(payload.user.ext.consent).to.eql('consentString'); + expect(payload.regs.ext.gdpr).to.eql(1); + }); - const bidResponse = spec.interpretResponse(response, null); - expect(bidResponse).to.not.be.empty; + it('Test bidfloor is function', function () { + const request = spec.buildRequests(bannerRequest1, bannerRequest1[0]); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.eql(0.5); + }); + }); + describe('checkParamDataType tests', function () { + it('return the expected datatypes', function () { + assert.isString(checkParamDataType('Right string', 'test', 'string')); + assert.isBoolean(checkParamDataType('Right bool', true, 'boolean')); + assert.isNumber(checkParamDataType('Right number', 10, 'number')); + assert.isArray(checkParamDataType('Right array', [10, 11], 'array')); + }); - const bid = bidResponse[0]; - expect(bid).to.not.be.empty; - expect(bid.ad).to.equal(receivedBid.adm); - expect(bid.cpm).to.equal(receivedBid.price); - expect(bid.height).to.equal(receivedBid.h); - expect(bid.width).to.equal(receivedBid.w); - expect(bid.requestId).to.equal(receivedBid.impid); + it('return undefined var for wrong datatypes', function () { + expect(checkParamDataType('Wrong string', 10, 'string')).to.be.undefined; + expect(checkParamDataType('Wrong bool', 10, 'boolean')).to.be.undefined; + expect(checkParamDataType('Wrong number', 'one', 'number')).to.be.undefined; + expect(checkParamDataType('Wrong array', false, 'array')).to.be.undefined; + }); + }) + describe('interpretResponse', function () { + const responseBody = { + id: '12345', + seatbid: [ + { + bid: [ + { + id: 'auctionId', + impid: 'impId', + price: 0.0, + adm: 'adMarkup', + crid: 'creativeId', + adomain: ['test.com'], + h: 50, + w: 320, + ext: { + vast_url: '' + } + } + ] + } + ], + cur: 'USD' + }; + it('Test the response parsing function', function () { + const receivedBid = responseBody.seatbid[0].bid[0]; + const response = {}; + response.body = responseBody; + const bidResponse = spec.interpretResponse(response, null); + expect(bidResponse).to.not.be.empty; + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal(receivedBid.adm); + expect(bid.cpm).to.equal(receivedBid.price); + expect(bid.height).to.equal(receivedBid.h); + expect(bid.width).to.equal(receivedBid.w); + expect(bid.requestId).to.equal(receivedBid.impid); + expect(bid.vastXml).to.equal(receivedBid.ext.vast_url); + expect(bid.meta.advertiserDomains).to.equal(receivedBid.adomain); + }); }); }); From 6f93a5b9a3c52926aa8907a997e0b416a46c6d2f Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:07:22 +0100 Subject: [PATCH 405/760] ZetaGlobalSsp: provide device.sua object (#11050) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 6 +++++ .../modules/zeta_global_sspBidAdapter_spec.js | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 80856462fe1..295108f31b4 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -143,6 +143,12 @@ export const spec = { payload.device.ua = navigator.userAgent; payload.device.language = navigator.language; + payload.device.w = screen.width; + payload.device.h = screen.height; + + if (bidderRequest?.ortb2?.device?.sua) { + payload.device.sua = bidderRequest.ortb2.device.sua; + } if (params.test) { payload.test = params.test; diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 9ef97019a98..b06a6700ebf 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -126,6 +126,16 @@ describe('Zeta Ssp Bid Adapter', function () { userIdAsEids: eids, timeout: 500, ortb2: { + device: { + sua: { + mobile: 1, + architecture: 'arm', + platform: { + brand: 'Chrome', + version: ['102'] + } + } + }, user: { data: [ { @@ -632,4 +642,19 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.user.data[0].segment[1].id).to.eql('44'); expect(payload.user.data[0].segment[2].id).to.eql('59'); }); + + it('Test provide device params', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.device.sua.mobile).to.eql(1); + expect(payload.device.sua.architecture).to.eql('arm'); + expect(payload.device.sua.platform.brand).to.eql('Chrome'); + expect(payload.device.sua.platform.version[0]).to.eql('102'); + + expect(payload.device.ua).to.not.be.undefined; + expect(payload.device.language).to.not.be.undefined; + expect(payload.device.w).to.not.be.undefined; + expect(payload.device.h).to.not.be.undefined; + }); }); From 16ee425a594e005325287fe6a08fd5338651b820 Mon Sep 17 00:00:00 2001 From: inna Date: Mon, 5 Feb 2024 16:10:48 +0200 Subject: [PATCH 406/760] Rise BidAdapter : add support for gpp (#11051) * support gpp in rise and minutemedia * modified tests * removed mm gpp support * updated rise test * fix for gpp section --------- Co-authored-by: Inna Yaretsky <> --- modules/riseBidAdapter.js | 8 ++++++++ test/spec/modules/riseBidAdapter_spec.js | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 78854129f8a..82790805303 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -470,6 +470,14 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } + if (bidderRequest && bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + if (generalBidParams.ifa) { generalParams.ifa = generalBidParams.ifa; } diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 28bd123cb5d..ec9309fd4ae 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -324,6 +324,24 @@ describe('riseAdapter', function () { 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 bidderRequestWithoutGPP = Object.assign({gppConsent: false}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithoutGPP); + 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: 'gpp-consent', applicableSections: [7]}}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithGPP); + console.log('request.data.params'); + console.log(request.data.params); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('gpp', 'gpp-consent'); + 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', From 6e13ea008558dc780a56dbcac27229f97b076e0a Mon Sep 17 00:00:00 2001 From: inna Date: Mon, 5 Feb 2024 16:36:04 +0200 Subject: [PATCH 407/760] MinuteMedia BidAdapter : add support for gpp (#11052) * add Rise adapter * fixes * change param isOrg to org * Rise adapter * change email for rise * fix circle failed * bump * bump * bump * remove space * Upgrade Rise adapter to 5.0 * added isWrapper param * addes is_wrapper parameter to documentation * added is_wrapper to test * removed isWrapper * Rise Bid Adapter: support Coppa param (#24) * MinuteMedia Bid Adapter: support Coppa param (#25) * Revert "MinuteMedia Bid Adapter: support Coppa param (#25)" (#26) This reverts commit 66c4e7b46121afc5331c8bca6e2fc972fc55f090. * bump * update coppa fetch * setting coppa param update * update Coppa tests * update test naming * Rise Bid Adapter: support plcmt and sua (#27) * update minuteMediaBidAdapter - support missing params (#29) * support gpp for minutemedia adapter * removed spaces * removed extra character --------- Co-authored-by: Noam Tzuberi Co-authored-by: noamtzu Co-authored-by: Noam Tzuberi Co-authored-by: Laslo Chechur Co-authored-by: OronW <41260031+OronW@users.noreply.github.com> Co-authored-by: lasloche <62240785+lasloche@users.noreply.github.com> Co-authored-by: YakirLavi <73641910+YakirLavi@users.noreply.github.com> Co-authored-by: YakirLavi Co-authored-by: Inna Yaretsky <> --- modules/minutemediaBidAdapter.js | 8 ++++++++ test/spec/modules/minutemediaBidAdapter_spec.js | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index a4158e2abfa..81200f28a6f 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -460,6 +460,14 @@ function generateGeneralParams(generalObject, bidderRequest) { generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; } + if (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + if (generalBidParams.ifa) { generalParams.ifa = generalBidParams.ifa; } diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index 7e22114aeed..d5d6cdc5449 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -301,6 +301,22 @@ describe('minutemediaAdapter', function () { 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', From 860924f0b31ba62d5b5236d698f52fcd694cb04b Mon Sep 17 00:00:00 2001 From: Sajid Mahmood Date: Mon, 5 Feb 2024 16:00:52 -0500 Subject: [PATCH 408/760] feat: remove dependency on EID Allowlist [ADDR-2801] (#10988) Co-authored-by: Sajid Mahmood --- modules/ixBidAdapter.js | 25 ++++-- test/spec/modules/ixBidAdapter_spec.js | 119 +++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 5 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 4cc6807b670..54cf3596dbb 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -38,6 +38,7 @@ const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NATIVE_TIME_TO_LIVE = 3600; // Since native can have video, use ttl same as video const NET_REVENUE = true; const MAX_REQUEST_LIMIT = 4; +const MAX_EID_SOURCES = 50; const OUTSTREAM_MINIMUM_PLAYER_SIZE = [144, 144]; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 @@ -111,7 +112,8 @@ export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { // Update with list of CFTs to be requested from Exchange REQUESTED_FEATURE_TOGGLES: [ - 'pbjs_enable_multiformat' + 'pbjs_enable_multiformat', + 'pbjs_allow_all_eids' ], featureToggles: {}, @@ -657,15 +659,23 @@ function getEidInfo(allEids) { let seenSources = {}; if (isArray(allEids)) { for (const eid of allEids) { - if (SOURCE_RTI_MAPPING.hasOwnProperty(eid.source) && deepAccess(eid, 'uids.0')) { + const isSourceMapped = SOURCE_RTI_MAPPING.hasOwnProperty(eid.source); + const allowAllEidsFeatureEnabled = FEATURE_TOGGLES.isFeatureEnabled('pbjs_allow_all_eids'); + const hasUids = deepAccess(eid, 'uids.0'); + + if ((isSourceMapped || allowAllEidsFeatureEnabled) && hasUids) { seenSources[eid.source] = true; - if (SOURCE_RTI_MAPPING[eid.source] != '') { + + if (isSourceMapped && SOURCE_RTI_MAPPING[eid.source] !== '') { eid.uids[0].ext = { rtiPartner: SOURCE_RTI_MAPPING[eid.source] }; } delete eid.uids[0].atype; toSend.push(eid); + if (toSend.length >= MAX_EID_SOURCES) { + break; + } } } } @@ -710,7 +720,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { // getting ixdiags for adunits of the video, outstream & multi format (MF) style const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') let ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); - for (var key in ixdiag) { + for (let key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; } @@ -796,6 +806,9 @@ function addRTI(userEids, eidInfo) { let identityInfo = window.headertag.getIdentityInfo(); if (identityInfo && typeof identityInfo === 'object') { for (const partnerName in identityInfo) { + if (userEids.length >= MAX_EID_SOURCES) { + return + } if (identityInfo.hasOwnProperty(partnerName)) { let response = identityInfo[partnerName]; if (!response.responsePending && response.data && typeof response.data === 'object' && @@ -1274,6 +1287,7 @@ function buildIXDiag(validBidRequests, fledgeEnabled) { .map(bidRequest => bidRequest.adUnitCode) .filter((value, index, arr) => arr.indexOf(value) === index); + let allEids = deepAccess(validBidRequests, '0.userIdAsEids', []) let ixdiag = { mfu: 0, bu: 0, @@ -1286,7 +1300,8 @@ function buildIXDiag(validBidRequests, fledgeEnabled) { userIds: _getUserIds(validBidRequests[0]), url: window.location.href.split('?')[0], vpd: defaultVideoPlacement, - ae: fledgeEnabled + ae: fledgeEnabled, + eidLength: allEids.length }; // create ad unit map and collect the required diag properties diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 500f2239e55..05b401fe4db 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -919,6 +919,23 @@ describe('IndexexchangeAdapter', function () { const extractPayload = function (bidRequest) { return bidRequest.data } + const generateEid = function (numEid) { + const eids = []; + + for (let i = 1; i <= numEid; i++) { + const newEid = { + source: `eid_source_${i}.com`, + uids: [{ + id: `uid_id_${i}`, + }] + }; + + eids.push(newEid); + } + + return eids; + } + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -1485,6 +1502,17 @@ describe('IndexexchangeAdapter', function () { describe('buildRequestsUserId', function () { let validIdentityResponse; let validUserIdPayload; + const serverResponse = { + body: { + ext: { + pbjs_allow_all_eids: { + test: { + activated: false + } + } + } + } + }; beforeEach(function () { window.headertag = {}; @@ -1495,6 +1523,12 @@ describe('IndexexchangeAdapter', function () { afterEach(function () { delete window.headertag; + serverResponse.body.ext.features = { + pbjs_allow_all_eids: { + activated: false + } + }; + validIdentityResponse = {} }); it('IX adapter reads supported user modules from Prebid and adds it to Video', function () { @@ -1506,6 +1540,91 @@ describe('IndexexchangeAdapter', function () { expect(payload.user.eids).to.have.deep.members(DEFAULT_USERID_PAYLOAD); }); + it('IX adapter filters eids from prebid past the maximum eid limit', function () { + serverResponse.body.ext.features = { + pbjs_allow_all_eids: { + activated: true + } + }; + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + let eid_sent_from_prebid = generateEid(55); + cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = extractPayload(request); + expect(payload.user.eids).to.have.lengthOf(50); + let eid_accepted = eid_sent_from_prebid.slice(0, 50); + expect(payload.user.eids).to.have.deep.members(eid_accepted); + expect(payload.ext.ixdiag.eidLength).to.equal(55); + }); + + it('IX adapter filters eids from IXL past the maximum eid limit', function () { + validIdentityResponse = { + MerkleIp: { + responsePending: false, + data: { + source: 'merkle.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + enc: 1 + } + }] + } + }, + LiveIntentIp: { + responsePending: false, + data: { + source: 'liveintent.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + rtiPartner: 'LDID', + enc: 1 + } + }] + } + } + }; + serverResponse.body.ext.features = { + pbjs_allow_all_eids: { + activated: true + } + }; + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + let eid_sent_from_prebid = generateEid(49); + cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = extractPayload(request); + expect(payload.user.eids).to.have.lengthOf(50); + eid_sent_from_prebid.push({ + source: 'merkle.com', + uids: [{ + id: '1234-5678-9012-3456', + ext: { + keyID: '1234-5678', + enc: 1 + } + }] + }) + expect(payload.user.eids).to.have.deep.members(eid_sent_from_prebid); + expect(payload.ext.ixdiag.eidLength).to.equal(49); + }); + + it('All incoming eids are from unsupported source with feature toggle off', function () { + FEATURE_TOGGLES.setFeatureToggles(serverResponse); + const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); + let eid_sent_from_prebid = generateEid(20); + cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); + const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; + const payload = extractPayload(request); + expect(payload.user.eids).to.be.undefined + expect(payload.ext.ixdiag.eidLength).to.equal(20); + }); + it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { validIdentityResponse = { AdserverOrgIp: { From 1dfd7eb53e0fbd5f8e8ea70c5170b9a71c1c991d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 04:47:21 -0700 Subject: [PATCH 409/760] Bump release-drafter/release-drafter from 5 to 6 (#11053) Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 5 to 6. - [Release notes](https://github.com/release-drafter/release-drafter/releases) - [Commits](https://github.com/release-drafter/release-drafter/compare/v5...v6) --- updated-dependencies: - dependency-name: release-drafter/release-drafter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index a13237f1290..a14e12664b6 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 with: config-name: release-drafter.yml env: From 643930f7d2943689da7df5985b2a2c98fc3734f4 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 6 Feb 2024 13:50:50 +0100 Subject: [PATCH 410/760] Criteo bid adatper: add logic to handle dsa in bid request/response (#11043) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 12 +-- test/spec/modules/criteoBidAdapter_spec.js | 86 +++++++++++++++++++++- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a8a1416e1f3..fcf8d2ad953 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -258,6 +258,9 @@ export const spec = { if (slot.ext?.meta?.networkName) { bid.meta = Object.assign({}, bid.meta, { networkName: slot.ext.meta.networkName }) } + if (slot.ext?.dsa?.adrender) { + bid.meta = Object.assign({}, bid.meta, { adrender: slot.ext.dsa.adrender }) + } if (slot.native) { if (bidRequest.params.nativeCallback) { bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); @@ -503,17 +506,16 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; let schain; let userIdAsEids; + let regs = Object.assign({}, { + coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) + }, bidderRequest.ortb2?.regs); const request = { id: generateUUID(), publisher: { url: context.url, ext: bidderRequest.publisherExt, }, - regs: { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined), - gpp: bidderRequest.ortb2?.regs?.gpp, - gpp_sid: bidderRequest.ortb2?.regs?.gpp_sid - }, + regs: regs, slots: bidRequests.map(bidRequest => { if (!userIdAsEids) { userIdAsEids = bidRequest.userIdAsEids; diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 5dc6d74d90e..ce07c6e49bc 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1110,7 +1110,7 @@ describe('The Criteo bidding adapter', function () { const ortb2 = { regs: { gpp: 'gpp_consent_string', - gpp_sid: [0, 1, 2] + gpp_sid: [0, 1, 2], } }; @@ -1120,6 +1120,48 @@ describe('The Criteo bidding adapter', function () { expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); }); + it('should properly build a request with dsa object', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; + let dsa = { + required: 3, + pubrender: 0, + datatopub: 2, + transparency: [{ + domain: 'platform1domain.com', + params: [1] + }, { + domain: 'SSP2domain.com', + params: [1, 2] + }] + }; + const ortb2 = { + regs: { + ext: { + dsa: dsa + } + } + }; + + const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); + expect(request.data.regs).to.not.be.null; + expect(request.data.regs.ext).to.not.be.null; + expect(request.data.regs.ext.dsa).to.deep.equal(dsa); + }); + it('should properly build a request with schain object', function () { const expectedSchain = { someProperty: 'someValue' @@ -1979,6 +2021,48 @@ describe('The Criteo bidding adapter', function () { expect(bids[0].meta.networkName).to.equal('Criteo'); }); + it('should properly parse a bid response with dsa', function () { + const response = { + body: { + slots: [{ + impid: 'test-requestId', + cpm: 1.23, + creative: 'test-ad', + creativecode: 'test-crId', + width: 728, + height: 90, + deal: 'myDealCode', + adomain: ['criteo.com'], + ext: { + dsa: { + adrender: 1 + }, + meta: { + networkName: 'Criteo' + } + } + }], + }, + }; + const request = { + bidRequests: [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + networkId: 456, + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].meta.adrender).to.equal(1); + }); + it('should properly parse a bid response with a networkId with twin ad unit banner win', function () { const response = { body: { From 6733fe8bf0640fc9237a1051ae9729840cb95594 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Tue, 6 Feb 2024 13:57:15 +0100 Subject: [PATCH 411/760] Onetag Bid Adapter: add DSA support (#11036) Co-authored-by: federico --- modules/onetagBidAdapter.js | 3 + test/spec/modules/onetagBidAdapter_spec.js | 119 ++++++++++++++------- 2 files changed, 84 insertions(+), 38 deletions(-) diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index eb9fa2eb536..7a7cbbadd82 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -127,6 +127,9 @@ function interpretResponse(serverResponse, bidderRequest) { }, ttl: bid.ttl || 300 }; + if (bid.dsa) { + responseBid.meta.dsa = bid.dsa; + } if (bid.mediaType === BANNER) { responseBid.ad = bid.ad; } else if (bid.mediaType === VIDEO) { diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 7960574c1a4..c3d8a4ee0e1 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -396,47 +396,75 @@ describe('onetag', function () { expect(payload.ortb2).to.exist; expect(payload.ortb2).to.exist.and.to.deep.equal(firtPartyData); }); - }); - it('Should send FLEDGE eligibility flag when FLEDGE is enabled', function () { - let bidderRequest = { - 'bidderCode': 'onetag', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'fledgeEnabled': true - }; - let serverRequest = spec.buildRequests([bannerBid], bidderRequest); - const payload = JSON.parse(serverRequest.data); + it('Should send DSA (ortb2 field)', function () { + const dsa = { + 'regs': { + 'ext': { + 'dsa': { + 'required': 1, + 'pubrender': 0, + 'datatopub': 1, + 'transparency': [{ + 'domain': 'dsa-domain', + 'params': [1, 2] + }] + } + } + } + }; + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'ortb2': dsa + } + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); + expect(payload.ortb2).to.exist; + expect(payload.ortb2).to.exist.and.to.deep.equal(dsa); + }); + it('Should send FLEDGE eligibility flag when FLEDGE is enabled', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'fledgeEnabled': true + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); - expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); - }); - it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { - let bidderRequest = { - 'bidderCode': 'onetag', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'fledgeEnabled': false - }; - let serverRequest = spec.buildRequests([bannerBid], bidderRequest); - const payload = JSON.parse(serverRequest.data); + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + }); + it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'fledgeEnabled': false + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); - expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); - }); - it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { - let bidderRequest = { - 'bidderCode': 'onetag', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - }; - let serverRequest = spec.buildRequests([bannerBid], bidderRequest); - const payload = JSON.parse(serverRequest.data); + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + }); + it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { + let bidderRequest = { + 'bidderCode': 'onetag', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + }; + let serverRequest = spec.buildRequests([bannerBid], bidderRequest); + const payload = JSON.parse(serverRequest.data); - expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(false); + expect(payload.fledgeEnabled).to.exist; + expect(payload.fledgeEnabled).to.exist.and.to.equal(false); + }); }); describe('interpretResponse', function () { const request = getBannerVideoRequest(); @@ -486,6 +514,21 @@ describe('onetag', function () { const serverResponses = spec.interpretResponse('invalid_response', { data: '{}' }); expect(serverResponses).to.be.an('array').that.is.empty; }); + it('Returns meta dsa field if dsa field is present in response', function () { + const dsaResponseObj = { + 'behalf': 'Advertiser', + 'paid': 'Advertiser', + 'transparency': { + 'domain': 'dsp1domain.com', + 'params': [1, 2] + }, + 'adrender': 1 + }; + const responseWithDsa = {...response}; + responseWithDsa.body.bids.forEach(bid => bid.dsa = {...dsaResponseObj}); + const serverResponse = spec.interpretResponse(responseWithDsa, request); + serverResponse.forEach(bid => expect(bid.meta.dsa).to.deep.equals(dsaResponseObj)); + }); }); describe('getUserSyncs', function () { const sync_endpoint = 'https://onetag-sys.com/usync/'; From 00fd7c18bff32e42312a1bf883f2de2dc708bdf7 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Tue, 6 Feb 2024 17:29:40 +0200 Subject: [PATCH 412/760] Add GVLID to illuminBidAdapter module. (#11060) --- modules/illuminBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/illuminBidAdapter.js b/modules/illuminBidAdapter.js index 36ff7bea0a3..45b74bec5c9 100644 --- a/modules/illuminBidAdapter.js +++ b/modules/illuminBidAdapter.js @@ -7,6 +7,7 @@ import {config} from '../src/config.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'illumin'; const BIDDER_VERSION = '1.0.0'; +const GVLID = 149; const CURRENCY = 'USD'; const TTL_SECONDS = 60 * 5; const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; @@ -327,6 +328,7 @@ export const spec = { code: BIDDER_CODE, version: BIDDER_VERSION, supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, isBidRequestValid, buildRequests, interpretResponse, From 7260a2d9ccd63dab347e679bbbdb286ba0540991 Mon Sep 17 00:00:00 2001 From: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:52:21 +0200 Subject: [PATCH 413/760] PGAMSSP Bidder Adapter: add id5id (#11024) * new adapter PGAMSSP * upd * support UID 2.0 * del obj * Add id5id --- modules/pgamsspBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index b38131c1c18..f3062fa4ff0 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -40,7 +40,8 @@ function getPlacementReqData(bid) { }; if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); + getUserId(placement.eids, bid.userId.uid2?.id, 'uidapi.com'); + getUserId(placement.eids, bid.userId.id5id?.uid, 'id5-sync.com'); } if (placementId) { From f8858ae9f4553928fbece2239e299c58be1d28a8 Mon Sep 17 00:00:00 2001 From: Petre Damoc Date: Tue, 6 Feb 2024 21:12:15 +0200 Subject: [PATCH 414/760] Missena Bid Adapter: add session identifier (#11058) --- modules/missenaBidAdapter.js | 3 +++ test/spec/modules/missenaBidAdapter_spec.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 527b6704146..99cad1c7bc6 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -1,6 +1,7 @@ import { buildUrl, formatQS, + generateUUID, isFn, logInfo, safeJSONParse, @@ -24,6 +25,7 @@ const EVENTS_DOMAIN = 'events.missena.io'; const EVENTS_DOMAIN_DEV = 'events.staging.missena.xyz'; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +window.msna_ik = window.msna_ik || generateUUID(); /* Get Floor price information */ function getFloor(bidRequest) { @@ -79,6 +81,7 @@ export const spec = { return validBidRequests.map((bidRequest) => { const payload = { adunit: bidRequest.adUnitCode, + ik: window.msna_ik, request_id: bidRequest.bidId, timeout: bidderRequest.timeout, }; diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index f84981352ab..ab1fbdcc074 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -137,6 +137,10 @@ describe('Missena Adapter', function () { expect(payloadNoFloor.floor).to.equal(undefined); expect(payloadNoFloor.floor_currency).to.equal(undefined); }); + it('should send the idempotency key', function () { + expect(window.msna_ik).to.not.equal(undefined); + expect(payload.ik).to.equal(window.msna_ik); + }); getDataFromLocalStorageStub.restore(); getDataFromLocalStorageStub = sinon.stub( From 88b7477f4c7359522c86c8fd690189b7797d1c61 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 6 Feb 2024 20:45:07 +0100 Subject: [PATCH 415/760] Grid bid adapter: add logic to handle dsa in bid request/response (#11042) Co-authored-by: v.raybaud --- modules/gridBidAdapter.js | 16 +++++++++++++++- test/spec/modules/gridBidAdapter_spec.js | 3 ++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index 7147864a2e6..d56639ed714 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -361,6 +361,16 @@ export const spec = { request.regs.coppa = 1; } + if (ortb2Regs?.ext?.dsa) { + if (!request.regs) { + request.regs = {ext: {}}; + } + if (!request.regs.ext) { + request.regs.ext = {}; + } + request.regs.ext.dsa = ortb2Regs.ext.dsa; + } + const site = deepAccess(bidderRequest, 'ortb2.site'); if (site) { const pageCategory = [...(site.cat || []), ...(site.pagecat || [])].filter((category) => { @@ -532,7 +542,7 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid netRevenue: true, ttl: TIME_TO_LIVE, meta: { - advertiserDomains: serverBid.adomain ? serverBid.adomain : [] + advertiserDomains: serverBid.adomain ? serverBid.adomain : [], }, dealId: serverBid.dealid }; @@ -544,6 +554,10 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid bidResponse.meta.demandSource = serverBid.ext.bidder.grid.demandSource; } + if (serverBid.ext && serverBid.ext.dsa && serverBid.ext.dsa.adrender) { + bidResponse.meta.adrender = serverBid.ext.dsa.adrender; + } + if (serverBid.content_type === 'video') { if (serverBid.adm) { bidResponse.vastXml = serverBid.adm; diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 0611fa68bf8..abaa4b37fcd 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1352,7 +1352,7 @@ describe('TheMediaGrid Adapter', function () { it('complicated case', function () { const fullResponse = [ - {'bid': [{'impid': '2164be6358b9', 'adid': '32_52_7543', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11}], 'seat': '1'}, + {'bid': [{'impid': '2164be6358b9', 'adid': '32_52_7543', 'price': 1.15, 'adm': '
test content 1
', 'auid': 1, 'h': 250, 'w': 300, dealid: 11, 'ext': {'dsa': {'adrender': 1}}}], 'seat': '1'}, {'bid': [{'impid': '4e111f1b66e4', 'adid': '32_54_4535', 'price': 0.5, 'adm': '
test content 2
', 'auid': 2, 'h': 600, 'w': 300, dealid: 12}], 'seat': '1'}, {'bid': [{'impid': '26d6f897b516', 'adid': '32_53_75467', 'price': 0.15, 'adm': '
test content 3
', 'auid': 1, 'h': 90, 'w': 728}], 'seat': '1'}, {'bid': [{'impid': '326bde7fbf69', 'adid': '32_54_12342', 'price': 0.15, 'adm': '
test content 4
', 'auid': 1, 'h': 600, 'w': 300}], 'seat': '1'}, @@ -1430,6 +1430,7 @@ describe('TheMediaGrid Adapter', function () { 'netRevenue': true, 'ttl': 360, 'meta': { + adrender: 1, advertiserDomains: [] }, }, From 36906f77daa6a723e95a3897224e045ca11c503e Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Tue, 6 Feb 2024 21:03:24 -0500 Subject: [PATCH 416/760] Sharethrough Bid Adapter: support Fledge + refine video-placement req logic (#11048) * Modify value-setting logic for video-placement reqs * Updating value-setting logic in `buildRequests()` method so that `placement` property in video requests does not possibly get overridden to 1 as a value if `plcmt` is also present as a property. (Current logic sets `placement` at 1 if `context` is "instream".) * Support for Fledge * Adding updates to our unit tests and the relevant methods in our bid adapter to make it ready, eventually, for Fledge auctions. --- modules/sharethroughBidAdapter.js | 30 +++++++++++++-- .../modules/sharethroughBidAdapter_spec.js | 37 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 53cb67c4e6d..ae1fb131966 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,7 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { deepAccess, generateUUID, inIframe } from '../src/utils.js'; +import { deepAccess, generateUUID, inIframe, mergeDeep } from '../src/utils.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; @@ -104,6 +104,10 @@ export const sharethroughAdapterSpec = { const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); + if (bidderRequest.fledgeEnabled && bidReq.mediaTypes.banner) { + mergeDeep(impression, { ext: { ae: 1 } }); // ae = auction environment; if this is 1, ad server knows we have a fledge auction + } + if (videoRequest) { // default playerSize, only change this if we know width and height are properly defined in the request let [w, h] = [640, 360]; @@ -116,6 +120,14 @@ export const sharethroughAdapterSpec = { [w, h] = videoRequest.playerSize[0]; } + const getVideoPlacementValue = (vidReq) => { + if (vidReq.plcmt) { + return vidReq.placement; + } else { + return vidReq.context === 'instream' ? 1 : +deepAccess(vidReq, 'placement', 4); + } + }; + impression.video = { pos: nullish(videoRequest.pos, 0), topframe: inIframe() ? 0 : 1, @@ -132,7 +144,8 @@ export const sharethroughAdapterSpec = { startdelay: nullish(videoRequest.startdelay, 0), skipmin: nullish(videoRequest.skipmin, 0), skipafter: nullish(videoRequest.skipafter, 0), - placement: videoRequest.context === 'instream' ? 1 : +deepAccess(videoRequest, 'placement', 4), + placement: getVideoPlacementValue(videoRequest), + plcmt: videoRequest.plcmt ? videoRequest.plcmt : null, }; if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; @@ -179,7 +192,9 @@ export const sharethroughAdapterSpec = { return []; } - return body.seatbid[0].bid.map((bid) => { + const fledgeAuctionEnabled = body.ext?.auctionConfigs; + + const bidsFromExchange = body.seatbid[0].bid.map((bid) => { // Spec: https://docs.prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response const response = { requestId: bid.impid, @@ -219,6 +234,15 @@ export const sharethroughAdapterSpec = { return response; }); + + if (fledgeAuctionEnabled) { + return { + bids: bidsFromExchange, + fledgeAuctionConfigs: body.ext?.auctionConfigs || {}, + }; + } else { + return bidsFromExchange; + } }, getUserSyncs: (syncOptions, serverResponses) => { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 6a63ae681e7..1bb6f898b81 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -585,6 +585,27 @@ describe('sharethrough adapter spec', function () { expect(videoImp.placement).to.equal(4); }); + + it('should not override "placement" value if "plcmt" prop is present', () => { + // ASSEMBLE + const ARBITRARY_PLACEMENT_VALUE = 99; + const ARBITRARY_PLCMT_VALUE = 100; + + bidRequests[1].mediaTypes.video.context = 'instream'; + bidRequests[1].mediaTypes.video.placement = ARBITRARY_PLACEMENT_VALUE; + + // adding "plcmt" property - this should prevent "placement" prop + // from getting overridden to 1 + bidRequests[1].mediaTypes.video['plcmt'] = ARBITRARY_PLCMT_VALUE; + + // ACT + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; + + // ASSERT + expect(videoImp.placement).to.equal(ARBITRARY_PLACEMENT_VALUE); + expect(videoImp.plcmt).to.equal(ARBITRARY_PLCMT_VALUE); + }); }); }); @@ -692,6 +713,22 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.regs.ext.gpp_sid).to.equal(firstPartyData.regs.gpp_sid); }); }); + + describe('fledge', () => { + it('should attach "ae" as a property to the request if 1) fledge auctions are enabled, and 2) request is display (only supporting display for now)', () => { + // ASSEMBLE + const EXPECTED_AE_VALUE = 1; + + // ACT + bidderRequest['fledgeEnabled'] = true; + const builtRequests = spec.buildRequests(bidRequests, bidderRequest); + const ACTUAL_AE_VALUE = builtRequests[0].data.imp[0].ext.ae; + + // ASSERT + expect(ACTUAL_AE_VALUE).to.equal(EXPECTED_AE_VALUE); + expect(builtRequests[1].data.imp[0].ext.ae).to.be.undefined; + }); + }); }); describe('interpretResponse', function () { From 8fbd6a346737111377b11899aa6ad3eae9a9d281 Mon Sep 17 00:00:00 2001 From: khonghau-cha <68848958+khonghau-cha@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:46:10 +0800 Subject: [PATCH 417/760] Innity Bid Adapter: handle empty response from server (#10960) * Handle empty response from server * Add space before blocks and add unit test for result with no bids --- modules/innityBidAdapter.js | 3 +++ test/spec/modules/innityBidAdapter_spec.js | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/modules/innityBidAdapter.js b/modules/innityBidAdapter.js index 99eec210193..9bd0538ff0a 100644 --- a/modules/innityBidAdapter.js +++ b/modules/innityBidAdapter.js @@ -38,6 +38,9 @@ export const spec = { }, interpretResponse: function(serverResponse, request) { const res = serverResponse.body; + if (Object.keys(res).length === 0) { + return []; + } const bidResponse = { requestId: res.callback_uid, cpm: parseFloat(res.cpm) / 100, diff --git a/test/spec/modules/innityBidAdapter_spec.js b/test/spec/modules/innityBidAdapter_spec.js index 192ab4911ee..820f535ba72 100644 --- a/test/spec/modules/innityBidAdapter_spec.js +++ b/test/spec/modules/innityBidAdapter_spec.js @@ -120,5 +120,11 @@ describe('innityAdapterTest', () => { expect(result[0].meta.advertiserDomains.length).to.equal(0); expect(result[0].meta.advertiserDomains).to.deep.equal([]); }); + + it('result with no bids', () => { + bidResponse.body = {}; + const result = spec.interpretResponse(bidResponse, bidRequest); + expect(result).to.deep.equal([]); + }); }); }); From 66bab278f45526cb3339afa24234fc428d195022 Mon Sep 17 00:00:00 2001 From: bbaresic <153560835+bbaresic@users.noreply.github.com> Date: Wed, 7 Feb 2024 06:51:22 -0600 Subject: [PATCH 418/760] Add skippedReason property to floorData. (#11040) --- modules/priceFloors.js | 3 +++ src/constants.json | 4 ++++ test/spec/modules/priceFloors_spec.js | 17 +++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/modules/priceFloors.js b/modules/priceFloors.js index c2d7db97047..277b6839c9b 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -365,6 +365,7 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) { noFloorSignaled: isNoFloorSignaled, skipped: floorData.skipped, skipRate: deepAccess(floorData, 'data.skipRate') ?? floorData.skipRate, + skippedReason: floorData.skippedReason, floorMin: floorData.floorMin, modelVersion: deepAccess(floorData, 'data.modelVersion'), modelWeight: deepAccess(floorData, 'data.modelWeight'), @@ -411,11 +412,13 @@ export function createFloorsDataForAuction(adUnits, auctionId) { // if we still do not have a valid floor data then floors is not on for this auction, so skip if (Object.keys(deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) { resolvedFloorsData.skipped = true; + resolvedFloorsData.skippedReason = CONSTANTS.FLOOR_SKIPPED_REASON.NOT_FOUND } else { // determine the skip rate now const auctionSkipRate = getParameterByName('pbjs_skipRate') || (deepAccess(resolvedFloorsData, 'data.skipRate') ?? resolvedFloorsData.skipRate); const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; + if (isSkipped) resolvedFloorsData.skippedReason = CONSTANTS.FLOOR_SKIPPED_REASON.RANDOM } // copy FloorMin to floorData.data if (resolvedFloorsData.hasOwnProperty('floorMin')) resolvedFloorsData.data.floorMin = resolvedFloorsData.floorMin; diff --git a/src/constants.json b/src/constants.json index 24882c160f8..53ee407b772 100644 --- a/src/constants.json +++ b/src/constants.json @@ -9,6 +9,10 @@ "ADSERVER_TARGETING": "adserverTargeting", "BD_SETTING_STANDARD": "standard" }, + "FLOOR_SKIPPED_REASON": { + "NOT_FOUND": "not_found", + "RANDOM": "random" + }, "DEBUG_MODE": "pbjs_debug", "STATUS": { "GOOD": 1 diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index a31f07fecef..fa31ae0ff81 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -730,6 +730,23 @@ describe('the price floors module', function () { expect(floorData.skipRate).to.equal(101); expect(floorData.skipped).to.equal(true); }); + + it('should have skippedReason set to "not_found" if there is no valid floor data', function() { + floorConfig.data = {} + handleSetFloorsConfig(floorConfig); + + const floorData = createFloorsDataForAuction(adUnits, 'id'); + expect(floorData.skippedReason).to.equal(CONSTANTS.FLOOR_SKIPPED_REASON.NOT_FOUND); + }); + + it('should have skippedReason set to "random" if there is floor data and skipped is true', function() { + // this will force skipped to be true + floorConfig.skipRate = 101; + handleSetFloorsConfig(floorConfig); + + const floorData = createFloorsDataForAuction(adUnits, 'id'); + expect(floorData.skippedReason).to.equal(CONSTANTS.FLOOR_SKIPPED_REASON.RANDOM); + }); }); describe('pre-auction tests', function () { From bfbce73f22a17ad5a3732680cb63ce9b83de9594 Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Wed, 7 Feb 2024 05:17:41 -0800 Subject: [PATCH 419/760] GumGum Bid Adapter: fix size in the bid response for multi size slot sizes (#11064) * Fix for the multi-size in- slot * added comment --- modules/gumgumBidAdapter.js | 5 +++-- test/spec/modules/gumgumBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 15af94494a3..7f8627ec5f7 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -553,10 +553,11 @@ function interpretResponse(serverResponse, bidRequest) { sizes = [`${maxw}x${maxh}`]; } else if (product === 5 && includes(sizes, '1x1')) { sizes = ['1x1']; - } else if (product === 2 && includes(sizes, '1x1')) { + // added logic for in-slot multi-szie + } else if ((product === 2 && includes(sizes, '1x1')) || product === 3) { const requestSizesThatMatchResponse = (bidRequest.sizes && bidRequest.sizes.reduce((result, current) => { const [ width, height ] = current; - if (responseWidth === width || responseHeight === height) result.push(current.join('x')); + if (responseWidth === width && responseHeight === height) result.push(current.join('x')); return result }, [])) || []; sizes = requestSizesThatMatchResponse.length ? requestSizesThatMatchResponse : parseSizesInput(bidRequest.sizes) diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index e0529a895f5..52cfd0294e7 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -880,6 +880,19 @@ describe('gumgumAdapter', function () { expect(result.height = expectedSize[1]); }) + it('request size that matches response size for in-slot', function () { + const request = { ...bidRequest }; + const body = { ...serverResponse }; + const expectedSize = [[ 320, 50 ], [300, 600], [300, 250]]; + let result; + request.pi = 3; + body.ad.width = 300; + body.ad.height = 600; + result = spec.interpretResponse({ body }, request)[0]; + expect(result.width = expectedSize[1][0]); + expect(result.height = expectedSize[1][1]); + }) + it('defaults to use bidRequest sizes', function () { const { ad, jcsi, pag, thms, meta } = serverResponse const noAdSizes = { ...ad } From f3809120c258094c85c486e1c57b2a763e75c8d0 Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Wed, 7 Feb 2024 15:18:15 +0200 Subject: [PATCH 420/760] Addition of ImproveDigital's Topics API iframe (#10986) --- modules/topicsFpdModule.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index a31d72fc179..dd240a929a7 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -29,6 +29,9 @@ const bidderIframeList = { }, { bidder: 'rtbhouse', iframeURL: 'https://topics.authorizedvault.com/topicsapi.html' + }, { + bidder: 'improvedigital', + iframeURL: 'https://hb.360yield.com/privacy-sandbox/topics.html' }] } From 8931850d3d63700b933fe02aeced0c79d77dda3a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 7 Feb 2024 05:27:41 -0800 Subject: [PATCH 421/760] Core: use same transaction ID for twin ad units (#10962) * swith transactionId to adUnitId * use same TID for ad units with the same code * fix appnexus clones --- modules/craftBidAdapter.js | 6 +- modules/goldbachBidAdapter.js | 5 +- modules/hybridBidAdapter.js | 14 +-- modules/mediafuseBidAdapter.js | 5 +- .../prebidServerBidAdapter/ortbConverter.js | 1 + modules/priceFloors.js | 6 +- modules/voxBidAdapter.js | 17 +-- package-lock.json | 2 +- src/adapterManager.js | 1 + src/auction.js | 4 +- src/auctionIndex.js | 18 +-- src/bidfactory.js | 26 ++-- src/prebid.js | 26 ++-- test/spec/auctionmanager_spec.js | 24 ++-- .../modules/prebidServerBidAdapter_spec.js | 1 + test/spec/modules/priceFloors_spec.js | 15 +-- test/spec/native_spec.js | 32 ++--- test/spec/unit/core/auctionIndex_spec.js | 30 ++--- test/spec/unit/core/bidderFactory_spec.js | 4 +- test/spec/unit/pbjs_api_spec.js | 113 +++++++++++++++++- test/spec/video_spec.js | 24 ++-- 21 files changed, 242 insertions(+), 132 deletions(-) diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index a2a054d7659..74e732d313f 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,7 +1,6 @@ import {getBidRequest, logError} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; @@ -186,12 +185,9 @@ function bidToTag(bid) { if (keywords.length) { tag.keywords = keywords; } - // TODO: why does this need to iterate through every ad unit? - let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); - if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + if (bid.mediaTypes?.banner) { tag.ad_types.push(BANNER); } - if (tag.ad_types.length === 0) { delete tag.ad_types; } diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index ff394e69e54..9f9913b7023 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -19,7 +19,6 @@ import { import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; @@ -882,9 +881,7 @@ function bidToTag(bid) { tag['banner_frameworks'] = bid.params.frameworks; } - // TODO: why does this need to iterate through every ad unit? - let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); - if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + if (bid.mediaTypes?.banner) { tag.ad_types.push(BANNER); } diff --git a/modules/hybridBidAdapter.js b/modules/hybridBidAdapter.js index 00e109516e5..01d29ee0126 100644 --- a/modules/hybridBidAdapter.js +++ b/modules/hybridBidAdapter.js @@ -1,6 +1,5 @@ import {_map, deepAccess, isArray, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {auctionManager} from '../src/auctionManager.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; @@ -95,16 +94,13 @@ function buildBid(bidData) { bid.vastXml = bidData.content; bid.mediaType = VIDEO; - // TODO: why does this need to iterate through every ad unit? - let adUnit = find(auctionManager.getAdUnits(), function (unit) { - return unit.transactionId === bidData.transactionId; - }); + const video = bidData.mediaTypes?.video; - if (adUnit) { - bid.width = adUnit.mediaTypes.video.playerSize[0][0]; - bid.height = adUnit.mediaTypes.video.playerSize[0][1]; + if (video) { + bid.width = video.playerSize[0][0]; + bid.height = video.playerSize[0][1]; - if (adUnit.mediaTypes.video.context === 'outstream') { + if (video.context === 'outstream') { bid.renderer = createRenderer(bid); } } diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 5e7221583a8..5e31f60d3b5 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -20,7 +20,6 @@ import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {auctionManager} from '../src/auctionManager.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -870,9 +869,7 @@ function bidToTag(bid) { tag['banner_frameworks'] = bid.params.frameworks; } - // TODO: why does this need to iterate through every ad unit? - let adUnit = find(auctionManager.getAdUnits(), au => bid.transactionId === au.transactionId); - if (adUnit && adUnit.mediaTypes && adUnit.mediaTypes.banner) { + if (bid.mediaTypes?.banner) { tag.ad_types.push(BANNER); } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 7aeb4302280..1dd1532f423 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -118,6 +118,7 @@ const PBS_CONVERTER = ortbConverter({ src: CONSTANTS.S2S.SRC, bidId: bidRequest ? (bidRequest.bidId || bidRequest.bid_Id) : null, transactionId: context.adUnit.transactionId, + adUnitId: context.adUnit.adUnitId, auctionId: context.bidderRequest.auctionId, }), bidResponse), adUnit: context.adUnit.code diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 277b6839c9b..70a0f9b9a14 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -94,8 +94,8 @@ const getHostname = (() => { })(); // First look into bidRequest! -function getGptSlotFromAdUnit(transactionId, {index = auctionManager.index} = {}) { - const adUnit = index.getAdUnit({transactionId}); +function getGptSlotFromAdUnit(adUnitId, {index = auctionManager.index} = {}) { + const adUnit = index.getAdUnit({adUnitId}); const isGam = deepAccess(adUnit, 'ortb2Imp.ext.data.adserver.name') === 'gam'; return isGam && adUnit.ortb2Imp.ext.data.adserver.adslot; } @@ -111,7 +111,7 @@ export let fieldMatchingFunctions = { [SYN_FIELD]: () => '*', 'size': (bidRequest, bidResponse) => parseGPTSingleSizeArray(bidResponse.size) || '*', 'mediaType': (bidRequest, bidResponse) => bidResponse.mediaType || 'banner', - 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).transactionId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, + 'gptSlot': (bidRequest, bidResponse) => getGptSlotFromAdUnit((bidRequest || bidResponse).adUnitId) || getGptSlotInfoForAdUnitCode(getAdUnitCode(bidRequest, bidResponse)).gptSlot, 'domain': getHostname, 'adUnitCode': (bidRequest, bidResponse) => getAdUnitCode(bidRequest, bidResponse) } diff --git a/modules/voxBidAdapter.js b/modules/voxBidAdapter.js index f1670de76d0..da72b975717 100644 --- a/modules/voxBidAdapter.js +++ b/modules/voxBidAdapter.js @@ -2,9 +2,8 @@ import {_map, deepAccess, isArray, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; -import {auctionManager} from '../src/auctionManager.js'; import {Renderer} from '../src/Renderer.js'; -import {config} from '../src/config.js' +import {config} from '../src/config.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -99,17 +98,13 @@ function buildBid(bidData) { if (bidData.placement === 'video') { bid.vastXml = bidData.content; bid.mediaType = VIDEO; + const video = bidData.mediaTypes?.video; - // TODO: why does this need to iterate through every ad unit? - let adUnit = find(auctionManager.getAdUnits(), function (unit) { - return unit.transactionId === bidData.transactionId; - }); - - if (adUnit) { - bid.width = adUnit.mediaTypes.video.playerSize[0][0]; - bid.height = adUnit.mediaTypes.video.playerSize[0][1]; + if (video) { + bid.width = video.playerSize[0][0]; + bid.height = video.playerSize[0][1]; - if (adUnit.mediaTypes.video.context === 'outstream') { + if (video.context === 'outstream') { bid.renderer = createRenderer(bid); } } diff --git a/package-lock.json b/package-lock.json index 9d7bc733e29..4821d711887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.33.0-pre", + "version": "8.35.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", diff --git a/src/adapterManager.js b/src/adapterManager.js index 189fbf31f2e..72695be0946 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -115,6 +115,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} bids.push(Object.assign({}, bid, { adUnitCode: adUnit.code, transactionId: adUnit.transactionId, + adUnitId: adUnit.adUnitId, sizes: deepAccess(mediaTypes, 'banner.sizes') || deepAccess(mediaTypes, 'video.playerSize') || [], bidId: bid.bid_id || getUniqueIdentifierStr(), bidderRequestId, diff --git a/src/auction.js b/src/auction.js index 37bb2604167..cc42e54a2b1 100644 --- a/src/auction.js +++ b/src/auction.js @@ -369,7 +369,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } function addWinningBid(winningBid) { - const winningAd = adUnits.find(adUnit => adUnit.transactionId === winningBid.transactionId); + const winningAd = adUnits.find(adUnit => adUnit.adUnitId === winningBid.adUnitId); _winningBids = _winningBids.concat(winningBid); callBurl(winningBid); adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits); @@ -557,7 +557,7 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au const videoMediaType = deepAccess( index.getMediaTypes({ requestId: bidResponse.originalRequestId || bidResponse.requestId, - transactionId: bidResponse.transactionId + adUnitId: bidResponse.adUnitId }), 'video'); const context = videoMediaType && deepAccess(videoMediaType, 'context'); const useCacheKey = videoMediaType && deepAccess(videoMediaType, 'useCacheKey'); diff --git a/src/auctionIndex.js b/src/auctionIndex.js index bb3f535b41a..afae2089518 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -2,9 +2,9 @@ * @typedef {Object} AuctionIndex * * @property {function({ auctionId: * }): *} getAuction Returns auction instance for `auctionId` - * @property {function({ transactionId: * }): *} getAdUnit Returns `adUnit` object for `transactionId`. + * @property {function({ adUnitId: * }): *} getAdUnit Returns `adUnit` object for `transactionId`. * You should prefer `getMediaTypes` for looking up bid media types. - * @property {function({ transactionId: *, requestId: * }): *} getMediaTypes Returns mediaTypes object from bidRequest (through `requestId`) falling back to the adUnit (through `transactionId`). + * @property {function({ adUnitId: *, requestId: * }): *} getMediaTypes Returns mediaTypes object from bidRequest (through `requestId`) falling back to the adUnit (through `transactionId`). * The bidRequest is given precedence because its mediaTypes can differ from the adUnit's (if bidder-specific labels are in use). * Bids that have no associated request do not have labels either, and use the adUnit's mediaTypes. * @property {function({ requestId: *, bidderRequestId: * }): *} getBidderRequest Returns bidderRequest that matches both requestId and bidderRequestId (if either or both are provided). @@ -25,21 +25,21 @@ export function AuctionIndex(getAuctions) { .find(auction => auction.getAuctionId() === auctionId); } }, - getAdUnit({transactionId}) { - if (transactionId != null) { + getAdUnit({adUnitId}) { + if (adUnitId != null) { return getAuctions() .flatMap(a => a.getAdUnits()) - .find(au => au.transactionId === transactionId); + .find(au => au.adUnitId === adUnitId); } }, - getMediaTypes({transactionId, requestId}) { + getMediaTypes({adUnitId, requestId}) { if (requestId != null) { const req = this.getBidRequest({requestId}); - if (req != null && (transactionId == null || req.transactionId === transactionId)) { + if (req != null && (adUnitId == null || req.adUnitId === adUnitId)) { return req.mediaTypes; } - } else if (transactionId != null) { - const au = this.getAdUnit({transactionId}); + } else if (adUnitId != null) { + const au = this.getAdUnit({adUnitId}); if (au != null) { return au.mediaTypes; } diff --git a/src/bidfactory.js b/src/bidfactory.js index 4c2e4cf3ffb..d3bac4a0030 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -14,20 +14,23 @@ import { getUniqueIdentifierStr } from './utils.js'; dealId, priceKeyString; */ -function Bid(statusCode, {src = 'client', bidder = '', bidId, transactionId, auctionId} = {}) { +function Bid(statusCode, {src = 'client', bidder = '', bidId, transactionId, adUnitId, auctionId} = {}) { var _bidSrc = src; var _statusCode = statusCode || 0; - this.bidderCode = bidder; - this.width = 0; - this.height = 0; - this.statusMessage = _getStatus(); - this.adId = getUniqueIdentifierStr(); - this.requestId = bidId; - this.transactionId = transactionId; - this.auctionId = auctionId; - this.mediaType = 'banner'; - this.source = _bidSrc; + Object.assign(this, { + bidderCode: bidder, + width: 0, + height: 0, + statusMessage: _getStatus(), + adId: getUniqueIdentifierStr(), + requestId: bidId, + transactionId, + adUnitId, + auctionId, + mediaType: 'banner', + source: _bidSrc + }) function _getStatus() { switch (_statusCode) { @@ -57,6 +60,7 @@ function Bid(statusCode, {src = 'client', bidder = '', bidId, transactionId, auc bidder: this.bidderCode, bidId: this.requestId, transactionId: this.transactionId, + adUnitId: this.adUnitId, auctionId: this.auctionId } }; diff --git a/src/prebid.js b/src/prebid.js index df224ab83cf..c06fb6c1979 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -552,6 +552,8 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: defer.resolve({bids, timedOut, auctionId}) } + const tids = {}; + /* * for a given adunit which supports a set of mediaTypes * and a given bidder which supports a set of mediaTypes @@ -567,15 +569,18 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: const bidderRegistry = adapterManager.bidderRegistry; const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); - - const tid = adUnit.ortb2Imp?.ext?.tid || generateUUID(); - adUnit.transactionId = tid; + adUnit.adUnitId = generateUUID(); + const tid = adUnit.ortb2Imp?.ext?.tid; + if (tid) { + if (tids.hasOwnProperty(adUnit.code)) { + logWarn(`Multiple distinct ortb2Imp.ext.tid were provided for twin ad units '${adUnit.code}'`) + } else { + tids[adUnit.code] = tid; + } + } if (ttlBuffer != null && !adUnit.hasOwnProperty('ttlBuffer')) { adUnit.ttlBuffer = ttlBuffer; } - // Populate ortb2Imp.ext.tid with transactionId. Specifying a transaction ID per item in the ortb impression array, lets multiple transaction IDs be transmitted in a single bid request. - deepSetValue(adUnit, 'ortb2Imp.ext.tid', tid); - bidders.forEach(bidder => { const adapter = bidderRegistry[bidder]; const spec = adapter && adapter.getSpec && adapter.getSpec(); @@ -594,11 +599,18 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: }); adunitCounter.incrementRequestsCounter(adUnit.code); }); - if (!adUnits || adUnits.length === 0) { logMessage('No adUnits configured. No bids requested.'); auctionDone(); } else { + adUnits.forEach(au => { + const tid = au.ortb2Imp?.ext?.tid || tids[au.code] || generateUUID(); + if (!tids.hasOwnProperty(au.code)) { + tids[au.code] = tid; + } + au.transactionId = tid; + deepSetValue(au, 'ortb2Imp.ext.tid', tid); + }); const auction = auctionManager.createAuction({ adUnits, adUnitCodes, diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 137d009bd18..c78a5e51f17 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -57,6 +57,7 @@ function mockBid(opts) { 'bidderCode': bidderCode || BIDDER_CODE, 'requestId': utils.getUniqueIdentifierStr(), 'transactionId': (opts && opts.transactionId) || ADUNIT_CODE, + adUnitId: (opts && opts.adUnitId) || ADUNIT_CODE, 'creativeId': 'id', 'currency': 'USD', 'netRevenue': true, @@ -114,6 +115,7 @@ function mockBidRequest(bid, opts) { }, 'adUnitCode': adUnitCode || ADUNIT_CODE, 'transactionId': bid.transactionId, + adUnitId: bid.adUnitId, 'sizes': [[300, 250], [300, 600]], 'bidId': bid.requestId, 'bidderRequestId': requestId, @@ -791,7 +793,7 @@ describe('auctionmanager.js', function () { }); adUnits = [{ code: ADUNIT_CODE, - transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE}, ] @@ -849,11 +851,11 @@ describe('auctionmanager.js', function () { bids = [ { adUnitCode: ADUNIT_CODE, - transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, ttl: 10 }, { adUnitCode: ADUNIT_CODE, - transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, ttl: 100 } ]; @@ -882,7 +884,7 @@ describe('auctionmanager.js', function () { 'bids': { bd: [{ adUnitCode: ADUNIT_CODE, - transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, ttl: 10 }], entries: () => auctionManager.getBidsReceived() @@ -933,7 +935,7 @@ describe('auctionmanager.js', function () { } }, code: ADUNIT_CODE, - transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] @@ -1134,7 +1136,7 @@ describe('auctionmanager.js', function () { }, bidRequests[0].bids[0]); Object.assign(bidRequests[0].bids[0], { adUnitCode: ADUNIT_CODE1, - transactionId: ADUNIT_CODE1, + adUnitId: ADUNIT_CODE1, }); makeRequestsStub.returns(bidRequests); @@ -1186,6 +1188,7 @@ describe('auctionmanager.js', function () { adUnits = [{ code: ADUNIT_CODE, transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, @@ -1357,12 +1360,14 @@ describe('auctionmanager.js', function () { adUnits = [{ code: ADUNIT_CODE, transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] }, { code: ADUNIT_CODE1, transactionId: ADUNIT_CODE1, + adUnitId: ADUNIT_CODE1, bids: [ {bidder: BIDDER_CODE1, params: {placementId: 'id'}}, ] @@ -1570,6 +1575,7 @@ describe('auctionmanager.js', function () { const adUnits = [{ code: ADUNIT_CODE, transactionId: ADUNIT_CODE, + adUnitId: ADUNIT_CODE, bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ], @@ -1702,7 +1708,7 @@ describe('auctionmanager.js', function () { function mockAuction(getBidRequests, start = 1) { return { getBidRequests: getBidRequests, - getAdUnits: () => getBidRequests().flatMap(br => br.bids).map(br => ({ code: br.adUnitCode, transactionId: br.transactionId, mediaTypes: br.mediaTypes })), + getAdUnits: () => getBidRequests().flatMap(br => br.bids).map(br => ({ code: br.adUnitCode, transactionId: br.transactionId, adUnitId: br.adUnitId, mediaTypes: br.mediaTypes })), getAuctionId: () => '1', addBidReceived: () => true, addBidRejected: () => true, @@ -1773,8 +1779,8 @@ describe('auctionmanager.js', function () { let ADUNIT_CODE2 = 'adUnitCode2'; let BIDDER_CODE2 = 'sampleBidder2'; - let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, transactionId: ADUNIT_CODE1 })]; - let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, transactionId: ADUNIT_CODE2 })]; + let bids1 = [mockBid({ bidderCode: BIDDER_CODE1, adUnitId: ADUNIT_CODE1 })]; + let bids2 = [mockBid({ bidderCode: BIDDER_CODE2, adUnitId: ADUNIT_CODE2 })]; bidRequests = [ mockBidRequest(bids[0]), mockBidRequest(bids1[0], { adUnitCode: ADUNIT_CODE1 }), diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index d88d4411e33..2bab144dae7 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -91,6 +91,7 @@ const REQUEST = { } }, 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', + 'adUnitId': 'au-id-1', 'bids': [ { 'bid_id': '123', diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index fa31ae0ff81..7ea7722b12a 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -116,7 +116,8 @@ describe('the price floors module', function () { bidder: 'rubicon', adUnitCode: 'test_div_1', auctionId: '1234-56-789', - transactionId: 'tr_test_div_1' + transactionId: 'tr_test_div_1', + adUnitId: 'tr_test_div_1', }; function getAdUnitMock(code = 'adUnit-code') { @@ -618,8 +619,8 @@ describe('the price floors module', function () { }); }); it('picks the gptSlot from the adUnit and does not call the slotMatching', function () { - const newBidRequest1 = { ...basicBidRequest, transactionId: 'au1' }; - adUnits = [{code: newBidRequest1.code, transactionId: 'au1'}]; + const newBidRequest1 = { ...basicBidRequest, adUnitId: 'au1' }; + adUnits = [{code: newBidRequest1.adUnitCode, adUnitId: 'au1'}]; utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { name: 'gam', adslot: '/12345/news/politics' @@ -632,8 +633,8 @@ describe('the price floors module', function () { matchingRule: '/12345/news/politics' }); - const newBidRequest2 = { ...basicBidRequest, adUnitCode: 'test_div_2', transactionId: 'au2' }; - adUnits = [{code: newBidRequest2.adUnitCode, transactionId: newBidRequest2.transactionId}]; + const newBidRequest2 = { ...basicBidRequest, adUnitCode: 'test_div_2', adUnitId: 'au2' }; + adUnits = [{code: newBidRequest2.adUnitCode, adUnitId: newBidRequest2.adUnitId}]; utils.deepSetValue(adUnits[0], 'ortb2Imp.ext.data.adserver', { name: 'gam', adslot: '/12345/news/weather' @@ -2362,7 +2363,7 @@ describe('the price floors module', function () { } const resp = { - transactionId: req.transactionId, + adUnitId: req.adUnitId, size: [100, 100], mediaType: 'banner', } @@ -2373,7 +2374,7 @@ describe('the price floors module', function () { adUnits: [ { code: req.adUnitCode, - transactionId: req.transactionId, + adUnitId: req.adUnitId, ortb2Imp: {ext: {data: {adserver: {name: 'gam', adslot: 'slot'}}}} } ] diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 9cfee6f5cd8..bf1c745d8cc 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -19,7 +19,7 @@ const utils = require('src/utils'); const bid = { adId: '123', - transactionId: 'au', + adUnitId: 'au', native: { title: 'Native Creative', body: 'Cool description great stuff', @@ -49,7 +49,7 @@ const bid = { const ortbBid = { adId: '123', - transactionId: 'au', + adUnitId: 'au', native: { ortb: { assets: [ @@ -106,7 +106,7 @@ const ortbBid = { const completeNativeBid = { adId: '123', - transactionId: 'au', + adUnitId: 'au', native: { ...bid.native, ...ortbBid.native @@ -157,7 +157,7 @@ const ortbRequest = { } const bidWithUndefinedFields = { - transactionId: 'au', + adUnitId: 'au', native: { title: 'Native Creative', body: undefined, @@ -209,7 +209,7 @@ describe('native.js', function () { it('sends placeholders for configured assets', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { body: { sendId: true }, clickUrl: { sendId: true }, @@ -246,7 +246,7 @@ describe('native.js', function () { it('should only include native targeting keys with values', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { body: { sendId: true }, clickUrl: { sendId: true }, @@ -273,7 +273,7 @@ describe('native.js', function () { it('should only include targeting that has sendTargetingKeys set to true', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { image: { required: true, @@ -294,7 +294,7 @@ describe('native.js', function () { it('should only include targeting if sendTargetingKeys not set to false', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { image: { required: true, @@ -347,7 +347,7 @@ describe('native.js', function () { it('should copy over rendererUrl to bid object and include it in targeting', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { image: { required: true, @@ -382,7 +382,7 @@ describe('native.js', function () { it('should copy over adTemplate to bid object and include it in targeting', function () { const adUnit = { - transactionId: 'au', + adUnitId: 'au', nativeParams: { image: { required: true, @@ -724,7 +724,7 @@ describe('validate native openRTB', function () { describe('validate native', function () { const adUnit = { - transactionId: 'test_adunit', + adUnitId: 'test_adunit', mediaTypes: { native: { title: { @@ -749,7 +749,7 @@ describe('validate native', function () { let validBid = { adId: 'abc123', requestId: 'test_bid_id', - transactionId: 'test_adunit', + adUnitId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -776,7 +776,7 @@ describe('validate native', function () { let noIconDimBid = { adId: 'abc234', requestId: 'test_bid_id', - transactionId: 'test_adunit', + adUnitId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -799,7 +799,7 @@ describe('validate native', function () { let noImgDimBid = { adId: 'abc345', requestId: 'test_bid_id', - transactionId: 'test_adunit', + adUnitId: 'test_adunit', adUnitCode: '123/prebid_native_adunit', bidder: 'test_bidder', native: { @@ -836,7 +836,7 @@ describe('validate native', function () { it('should convert from old-style native to OpenRTB request', () => { const adUnit = { - transactionId: 'test_adunit', + adUnitId: 'test_adunit', mediaTypes: { native: { title: { @@ -1043,7 +1043,7 @@ describe('validate native', function () { const validBidRequests = [{ bidId: 'bidId3', adUnitCode: 'adUnitCode3', - transactionId: 'transactionId3', + adUnitId: 'transactionId3', mediaTypes: { banner: {} }, diff --git a/test/spec/unit/core/auctionIndex_spec.js b/test/spec/unit/core/auctionIndex_spec.js index f00e2cd281f..df29ed1a6cb 100644 --- a/test/spec/unit/core/auctionIndex_spec.js +++ b/test/spec/unit/core/auctionIndex_spec.js @@ -38,22 +38,22 @@ describe('auction index', () => { let adUnits; beforeEach(() => { - adUnits = [{transactionId: 'au1'}, {transactionId: 'au2'}]; + adUnits = [{adUnitId: 'au1'}, {adUnitId: 'au2'}]; auctions = [ mockAuction('a1', [adUnits[0], {}]), mockAuction('a2', [adUnits[1]]) ]; }); - it('should find adUnits by transactionId', () => { - expect(index.getAdUnit({transactionId: 'au2'})).to.equal(adUnits[1]); + it('should find adUnits by adUnitId', () => { + expect(index.getAdUnit({adUnitId: 'au2'})).to.equal(adUnits[1]); }); it('should return undefined if adunit is missing', () => { - expect(index.getAdUnit({transactionId: 'missing'})).to.be.undefined; + expect(index.getAdUnit({adUnitId: 'missing'})).to.be.undefined; }); - it('should return undefined if no transactionId is provided', () => { + it('should return undefined if no adUnitId is provided', () => { expect(index.getAdUnit({})).to.be.undefined; }); }); @@ -87,12 +87,12 @@ describe('auction index', () => { beforeEach(() => { mediaTypes = [{mockMT: '1'}, {mockMT: '2'}, {mockMT: '3'}, {mockMT: '4'}] adUnits = [ - {transactionId: 'au1', mediaTypes: mediaTypes[0]}, - {transactionId: 'au2', mediaTypes: mediaTypes[1]} + {adUnitId: 'au1', mediaTypes: mediaTypes[0]}, + {adUnitId: 'au2', mediaTypes: mediaTypes[1]} ] bidderRequests = [ - {bidderRequestId: 'ber1', bids: [{bidId: 'b1', mediaTypes: mediaTypes[2], transactionId: 'au1'}, {}]}, - {bidderRequestId: 'ber2', bids: [{bidId: 'b2', mediaTypes: mediaTypes[3], transactionId: 'au2'}]} + {bidderRequestId: 'ber1', bids: [{bidId: 'b1', mediaTypes: mediaTypes[2], adUnitId: 'au1'}, {}]}, + {bidderRequestId: 'ber2', bids: [{bidId: 'b2', mediaTypes: mediaTypes[3], adUnitId: 'au2'}]} ] auctions = [ mockAuction('a1', [adUnits[0]], [bidderRequests[0], {}]), @@ -100,8 +100,8 @@ describe('auction index', () => { ] }); - it('should find mediaTypes by transactionId', () => { - expect(index.getMediaTypes({transactionId: 'au2'})).to.equal(mediaTypes[1]); + it('should find mediaTypes by adUnitId', () => { + expect(index.getMediaTypes({adUnitId: 'au2'})).to.equal(mediaTypes[1]); }); it('should find mediaTypes by requestId', () => { @@ -109,18 +109,18 @@ describe('auction index', () => { }); it('should give precedence to request.mediaTypes over adUnit.mediaTypes', () => { - expect(index.getMediaTypes({requestId: 'b2', transactionId: 'au2'})).to.equal(mediaTypes[3]); + expect(index.getMediaTypes({requestId: 'b2', adUnitId: 'au2'})).to.equal(mediaTypes[3]); }); - it('should return undef if requestId and transactionId do not match', () => { - expect(index.getMediaTypes({requestId: 'b1', transactionId: 'au2'})).to.be.undefined; + it('should return undef if requestId and adUnitId do not match', () => { + expect(index.getMediaTypes({requestId: 'b1', adUnitId: 'au2'})).to.be.undefined; }); it('should return undef if no params are provided', () => { expect(index.getMediaTypes({})).to.be.undefined; }); - ['requestId', 'transactionId'].forEach(param => { + ['requestId', 'adUnitId'].forEach(param => { it(`should return undef if ${param} is missing`, () => { expect(index.getMediaTypes({[param]: 'missing'})).to.be.undefined; }); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 16042ca52dd..aba64733f90 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1109,7 +1109,7 @@ describe('bidderFactory', () => { if (FEATURES.NATIVE) { it('should add native bids that do have required assets', function () { adUnits = [{ - transactionId: 'au', + adUnitId: 'au', nativeParams: { title: {'required': true}, } @@ -1120,7 +1120,7 @@ describe('bidderFactory', () => { bidId: '1', auctionId: 'first-bid-id', adUnitCode: 'mock/placement', - transactionId: 'au', + adUnitId: 'au', params: { param: 5 }, diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 39123d4aa41..e61171c3a22 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -556,6 +556,7 @@ describe('Unit: Prebid Module', function () { 'bidderRequestId': '331f3cf3f1d9c8', 'auctionId': '20882439e3238c', 'transactionId': 'trdiv-gpt-ad-1460505748561-0', + 'adUnitId': 'audiv-gpt-ad-1460505748561-0', } ], 'auctionStart': 1505250713622, @@ -573,6 +574,7 @@ describe('Unit: Prebid Module', function () { let auctionManagerInstance = newAuctionManager(); targeting = newTargeting(auctionManagerInstance); let adUnits = [{ + adUnitId: 'audiv-gpt-ad-1460505748561-0', transactionId: 'trdiv-gpt-ad-1460505748561-0', code: 'div-gpt-ad-1460505748561-0', sizes: [[300, 250], [300, 600]], @@ -718,6 +720,7 @@ describe('Unit: Prebid Module', function () { const adUnit = { transactionId: `tr${code}`, + adUnitId: `au${code}`, code: code, sizes: [[300, 250], [300, 600]], bids: [{ @@ -820,6 +823,7 @@ describe('Unit: Prebid Module', function () { }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': 'trdiv-gpt-ad-1460505748561-0', + 'adUnitId': 'audiv-gpt-ad-1460505748561-0', 'sizes': [ [ 300, @@ -1532,6 +1536,7 @@ describe('Unit: Prebid Module', function () { } }, transactionId: 'mock-tid', + adUnitId: 'mock-au', bids: [ {bidder: BIDDER_CODE, params: {placementId: 'id'}}, ] @@ -1606,6 +1611,7 @@ describe('Unit: Prebid Module', function () { height: 250, adUnitCode: bidRequests[0].bids[0].adUnitCode, transactionId: 'mock-tid', + adUnitId: 'mock-au', adserverTargeting: { 'hb_bidder': BIDDER_CODE, 'hb_adid': bidId, @@ -1684,7 +1690,8 @@ describe('Unit: Prebid Module', function () { const bid = { bidder: 'mock-bidder', adUnitCode: adUnits[0].code, - transactionId: adUnits[0].transactionId + transactionId: adUnits[0].transactionId, + adUnitId: adUnits[0].adUnitId, } requestBids({ adUnits, @@ -1970,6 +1977,102 @@ describe('Unit: Prebid Module', function () { .and.to.match(/[a-f0-9\-]{36}/i); }); + it('should use the same transactionID for ad units with the same code', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + }, { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + } + ] + }); + const tid = auctionArgs.adUnits[0].transactionId; + expect(tid).to.exist; + expect(auctionArgs.adUnits[1].transactionId).to.eql(tid); + }); + + it('should re-use pub-provided transaction ID for ad units with the same code', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [], + }, { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [], + ortb2Imp: { + ext: { + tid: 'pub-tid' + } + } + } + ] + }); + expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['pub-tid', 'pub-tid']); + }); + + it('should use pub-provided TIDs when they conflict for ad units with the same code', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [], + ortb2Imp: { + ext: { + tid: 't1' + } + } + }, { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [], + ortb2Imp: { + ext: { + tid: 't2' + } + } + } + ] + }); + expect(auctionArgs.adUnits.map(au => au.transactionId)).to.eql(['t1', 't2']); + }); + + it('should generate unique adUnitId', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [ + { + code: 'single', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + }, { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + }, + { + code: 'twin', + mediaTypes: { banner: { sizes: [] } }, + bids: [] + } + ] + }); + + const ids = new Set(); + auctionArgs.adUnits.forEach(au => { + expect(au.adUnitId).to.exist; + ids.add(au.adUnitId); + }); + expect(ids.size).to.eql(3); + }); + describe('transactionId', () => { let adUnit; beforeEach(() => { @@ -3546,7 +3649,7 @@ describe('Unit: Prebid Module', function () { { code: 'adUnit-code-1', mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - transactionId: '1234567890', + adUnitId: '1234567890', bids: [ { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1' } ] @@ -3555,15 +3658,15 @@ describe('Unit: Prebid Module', function () { code: 'adUnit-code-2', deferBilling: true, mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - transactionId: '0987654321', + adUnitId: '0987654321', bids: [ { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2' } ] } ]; - let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', transactionId: '1234567890', adId: 'abcdefg' } - let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', transactionId: '0987654321' } + let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' } + let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', adUnitId: '0987654321' } let adUnitCodes = ['adUnit-code-1', 'adUnit-code-2']; let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000}); diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 35d0a4fef24..3252c58c687 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -82,10 +82,10 @@ describe('video.js', function () { const bid = { adId: '456xyz', vastUrl: 'http://www.example.com/vastUrl', - transactionId: 'au' + adUnitId: 'au' }; const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', mediaTypes: { video: {context: 'instream'} } @@ -96,10 +96,10 @@ describe('video.js', function () { it('catches invalid instream bids', function () { const bid = { - transactionId: 'au' + adUnitId: 'au' }; const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', mediaTypes: { video: {context: 'instream'} } @@ -110,26 +110,26 @@ describe('video.js', function () { it('catches invalid bids when prebid-cache is disabled', function () { const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', bidder: 'vastOnlyVideoBidder', mediaTypes: {video: {}}, }]; - const valid = isValidVideoBid({ transactionId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); + const valid = isValidVideoBid({ adUnitId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); expect(valid).to.equal(false); }); it('validates valid outstream bids', function () { const bid = { - transactionId: 'au', + adUnitId: 'au', renderer: { url: 'render.url', render: () => true, } }; const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', mediaTypes: { video: {context: 'outstream'} } @@ -140,10 +140,10 @@ describe('video.js', function () { it('validates valid outstream bids with a publisher defined renderer', function () { const bid = { - transactionId: 'au', + adUnitId: 'au', }; const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', mediaTypes: { video: { context: 'outstream', @@ -160,10 +160,10 @@ describe('video.js', function () { it('catches invalid outstream bids', function () { const bid = { - transactionId: 'au', + adUnitId: 'au', }; const adUnits = [{ - transactionId: 'au', + adUnitId: 'au', mediaTypes: { video: {context: 'outstream'} } From 63429427dfae6f8aec221ac962282578f57bff67 Mon Sep 17 00:00:00 2001 From: Snigel <108489367+snigelweb@users.noreply.github.com> Date: Wed, 7 Feb 2024 14:52:10 +0100 Subject: [PATCH 422/760] snigel Bid Adapter : pass more information to backend (#10987) * snigelBidAdapter: pass more information to backend * snigelBidAdapter: add unit tests for the new functionality --- modules/snigelBidAdapter.js | 47 ++++++++++++++-- test/spec/modules/snigelBidAdapter_spec.js | 64 ++++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 6d32d8f97a2..5a327b05cd0 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -1,9 +1,10 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT} from '../src/utils.js'; +import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT, generateUUID} from '../src/utils.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; @@ -11,9 +12,14 @@ const DEFAULT_URL = 'https://adserv.snigelweb.com/bp/v1/prebid'; const DEFAULT_TTL = 60; const DEFAULT_CURRENCIES = ['USD']; const FLOOR_MATCH_ALL_SIZES = '*'; +const SESSION_ID_KEY = '_sn_session_pba'; const getConfig = config.getConfig; +const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const refreshes = {}; +const pageViewId = generateUUID(); +const pageViewStart = new Date().getTime(); +let auctionCounter = 0; export const spec = { code: BIDDER_CODE, @@ -33,6 +39,11 @@ export const spec = { id: bidderRequest.auctionId, accountId: deepAccess(bidRequests, '0.params.accountId'), site: deepAccess(bidRequests, '0.params.site'), + sessionId: getSessionId(), + counter: auctionCounter++, + pageViewId: pageViewId, + pageViewStart: pageViewStart, + gdprConsent: gdprApplies === true ? hasFullGdprConsent(deepAccess(bidderRequest, 'gdprConsent')) : false, cur: getCurrencies(), test: getTestFlag(), version: getGlobal().version, @@ -104,9 +115,7 @@ export const spec = { registerBidder(spec); function getPage(bidderRequest) { - return ( - getConfig(`${BIDDER_CODE}.page`) || deepAccess(bidderRequest, 'refererInfo.page') || window.location.href - ); + return getConfig(`${BIDDER_CODE}.page`) || deepAccess(bidderRequest, 'refererInfo.page') || window.location.href; } function getEndpoint() { @@ -193,6 +202,19 @@ function hasSyncConsent(gdprConsent, uspConsent, gppConsent) { return hasPurpose1Consent(gdprConsent) && hasUspConsent(uspConsent) && hasGppConsent(gppConsent); } +function hasFullGdprConsent(gdprConsent) { + try { + const purposeConsents = Object.values(gdprConsent.vendorData.purpose.consents); + return ( + purposeConsents.length > 0 && + purposeConsents.every((value) => value === true) && + gdprConsent.vendorData.vendor.consents[GVLID] === true + ); + } catch (e) { + return false; + } +} + function getSyncUrl(responses) { return getConfig(`${BIDDER_CODE}.syncUrl`) || deepAccess(responses[0], 'body.syncUrl'); } @@ -202,3 +224,20 @@ function getSyncEndpoint(url, gdprConsent) { gdprConsent?.consentString || '' )}`; } + +function getSessionId() { + try { + if (storageManager.localStorageIsEnabled()) { + let sessionId = storageManager.getDataFromLocalStorage(SESSION_ID_KEY); + if (sessionId == null) { + sessionId = generateUUID(); + storageManager.setDataInLocalStorage(SESSION_ID_KEY, sessionId); + } + return sessionId; + } else { + return undefined; + } + } catch (e) { + return undefined; + } +} diff --git a/test/spec/modules/snigelBidAdapter_spec.js b/test/spec/modules/snigelBidAdapter_spec.js index 3ba84228872..828aec9491c 100644 --- a/test/spec/modules/snigelBidAdapter_spec.js +++ b/test/spec/modules/snigelBidAdapter_spec.js @@ -2,6 +2,8 @@ import {expect} from 'chai'; import {spec} from 'modules/snigelBidAdapter.js'; import {config} from 'src/config.js'; import {isValid} from 'src/adapters/bidderFactory.js'; +import {registerActivityControl} from 'src/activities/rules.js'; +import {ACTIVITY_ACCESS_DEVICE} from 'src/activities/activities.js'; const BASE_BID_REQUEST = { adUnitCode: 'top_leaderboard', @@ -344,5 +346,67 @@ describe('snigelBidAdapter', function () { expect(sync).to.have.property('url'); expect(sync.url).to.equal(`https://somesyncurl?gdpr=1&gdpr_consent=${DUMMY_GDPR_CONSENT_STRING}`); }); + + it('should omit session ID if no device access', function() { + const bidderRequest = makeBidderRequest(); + const unregisterRule = registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'denyAccess', () => { + return {allow: false, reason: 'no consent'}; + }); + + try { + const request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + const data = JSON.parse(request.data); + expect(data.sessionId).to.be.undefined; + } finally { + unregisterRule(); + } + }); + + it('should determine full GDPR consent correctly', function () { + const baseBidderRequest = makeBidderRequest({ + gdprConsent: { + gdprApplies: true, + vendorData: { + purpose: { + consents: {1: true, 2: true, 3: true, 4: true, 5: true}, + }, + vendor: { + consents: {[spec.gvlid]: true}, + } + }, + } + }); + let request = spec.buildRequests([], baseBidderRequest); + expect(request).to.have.property('data'); + let data = JSON.parse(request.data); + expect(data.gdprConsent).to.be.true; + + let bidderRequest = {...baseBidderRequest, ...{gdprConsent: {vendorData: {purpose: {consents: {1: false}}}}}}; + request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + data = JSON.parse(request.data); + expect(data.gdprConsent).to.be.false; + + bidderRequest = {...baseBidderRequest, ...{gdprConsent: {vendorData: {vendor: {consents: {[spec.gvlid]: false}}}}}}; + request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + data = JSON.parse(request.data); + expect(data.gdprConsent).to.be.false; + }); + + it('should increment auction counter upon every request', function() { + const bidderRequest = makeBidderRequest({}); + + let request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + let data = JSON.parse(request.data); + const previousCounter = data.counter; + + request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + data = JSON.parse(request.data); + expect(data.counter).to.equal(previousCounter + 1); + }); }); }); From 35eb3d4c589074c25fecb4623274d50f5ca793b3 Mon Sep 17 00:00:00 2001 From: aleksandar-rayn <155530509+aleksandar-rayn@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:24:45 +0100 Subject: [PATCH 423/760] rayn rtd provider module (#11054) Co-authored-by: Berislav Kovacki --- .../gpt/raynRtdProvider_example.html | 167 ++++++++++ modules/raynRtdProvider.js | 198 +++++++++++ modules/raynRtdProvider.md | 118 +++++++ test/spec/modules/raynRtdProvider_spec.js | 308 ++++++++++++++++++ 4 files changed, 791 insertions(+) create mode 100644 integrationExamples/gpt/raynRtdProvider_example.html create mode 100644 modules/raynRtdProvider.js create mode 100644 modules/raynRtdProvider.md create mode 100644 test/spec/modules/raynRtdProvider_spec.js diff --git a/integrationExamples/gpt/raynRtdProvider_example.html b/integrationExamples/gpt/raynRtdProvider_example.html new file mode 100644 index 00000000000..2d43c37513a --- /dev/null +++ b/integrationExamples/gpt/raynRtdProvider_example.html @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + +

Rayn RTD Prebid

+ +
+ +
+ + Rayn Segments: +
+ + diff --git a/modules/raynRtdProvider.js b/modules/raynRtdProvider.js new file mode 100644 index 00000000000..d558c360c4a --- /dev/null +++ b/modules/raynRtdProvider.js @@ -0,0 +1,198 @@ +/** + * This module adds the Rayn provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time audience and context data from Rayn + * @module modules/raynRtdProvider + * @requires module:modules/realTimeData + */ + +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepAccess, deepSetValue, logError, logMessage, mergeDeep } from '../src/utils.js'; + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'rayn'; +const RAYN_TCF_ID = 1220; +const LOG_PREFIX = 'RaynJS: '; +export const SEGMENTS_RESOLVER = 'rayn.io'; +export const RAYN_LOCAL_STORAGE_KEY = 'rayn-segtax'; + +const defaultIntegration = { + iabAudienceCategories: { + v1_1: { + tier: 6, + enabled: true, + }, + }, + iabContentCategories: { + v3_0: { + tier: 4, + enabled: true, + }, + v2_2: { + tier: 4, + enabled: true, + }, + }, +}; + +export const storage = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: SUBMODULE_NAME, +}); + +function init(moduleConfig, userConsent) { + return true; +} + +/** + * Create and return ORTB2 object with segtax and segments + * @param {number} segtax + * @param {Array} segmentIds + * @param {number} maxTier + * @return {Array} + */ +export function generateOrtbDataObject(segtax, segment, maxTier) { + const segmentIds = []; + + try { + Object.keys(segment).forEach(tier => { + if (tier <= maxTier) { + segmentIds.push(...segment[tier].map((id) => { + return { id }; + })) + } + }); + } catch (error) { + logError(LOG_PREFIX, error); + } + + return { + name: SEGMENTS_RESOLVER, + ext: { + segtax, + }, + segment: segmentIds, + }; +} + +/** + * Generates checksum + * @param {string} url + * @returns {string} + */ +export function generateChecksum(stringValue) { + const l = stringValue.length; + let i = 0; + let h = 0; + if (l > 0) while (i < l) h = ((h << 5) - h + stringValue.charCodeAt(i++)) | 0; + return h.toString(); +}; + +/** + * Gets an object of segtax and segment IDs from LocalStorage + * or return the default value provided. + * @param {string} key + * @return {Object} + */ +export function readSegments(key) { + try { + return JSON.parse(storage.getDataFromLocalStorage(key)); + } catch (error) { + logError(LOG_PREFIX, error); + return null; + } +} + +/** + * Pass segments to configured bidders, using ORTB2 + * @param {Object} bidConfig + * @param {Array} bidders + * @param {Object} integrationConfig + * @param {Array} segments + * @return {void} + */ +export function setSegmentsAsBidderOrtb2(bidConfig, bidders, integrationConfig, segments, checksum) { + const raynOrtb2 = {}; + + const raynContentData = []; + if (integrationConfig.iabContentCategories.v2_2.enabled && segments[checksum] && segments[checksum][6]) { + raynContentData.push(generateOrtbDataObject(6, segments[checksum][6], integrationConfig.iabContentCategories.v2_2.tier)); + } + if (integrationConfig.iabContentCategories.v3_0.enabled && segments[checksum] && segments[checksum][7]) { + raynContentData.push(generateOrtbDataObject(7, segments[checksum][7], integrationConfig.iabContentCategories.v3_0.tier)); + } + if (raynContentData.length > 0) { + deepSetValue(raynOrtb2, 'site.content.data', raynContentData); + } + + if (integrationConfig.iabAudienceCategories.v1_1.enabled && segments[4]) { + const raynUserData = [generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)]; + deepSetValue(raynOrtb2, 'user.data', raynUserData); + } + + if (!bidders || bidders.length === 0 || !segments || Object.keys(segments).length <= 0) { + mergeDeep(bidConfig?.ortb2Fragments?.global, raynOrtb2); + } else { + const bidderConfig = Object.fromEntries( + bidders.map((bidder) => [bidder, raynOrtb2]), + ); + mergeDeep(bidConfig?.ortb2Fragments?.bidder, bidderConfig); + } +} + +/** + * Real-time data retrieval from Rayn + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {Object} config + * @param {Object} userConsent + * @return {void} + */ +function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { + try { + const checksum = generateChecksum(window.location.href); + + const segments = readSegments(RAYN_LOCAL_STORAGE_KEY); + + const bidders = deepAccess(config, 'params.bidders'); + const integrationConfig = mergeDeep(defaultIntegration, deepAccess(config, 'params.integration')); + + if (segments && Object.keys(segments).length > 0 && ( + segments[checksum] || (segments[4] && + integrationConfig.iabAudienceCategories.v1_1.enabled && + !integrationConfig.iabContentCategories.v2_2.enabled && + !integrationConfig.iabContentCategories.v3_0.enabled + ) + )) { + logMessage(LOG_PREFIX, `Segtax data from localStorage: ${JSON.stringify(segments)}`); + setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segments, checksum); + callback(); + } else if (window.raynJS && typeof window.raynJS.getSegtax === 'function') { + window.raynJS.getSegtax().then((segtaxData) => { + logMessage(LOG_PREFIX, `Segtax data from RaynJS: ${JSON.stringify(segtaxData)}`); + setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segtaxData, checksum); + callback(); + }).catch((error) => { + logError(LOG_PREFIX, error); + callback(); + }); + } else { + logMessage(LOG_PREFIX, 'No segtax data'); + callback(); + } + } catch (error) { + logError(LOG_PREFIX, error); + callback(); + } +} + +export const raynSubmodule = { + name: SUBMODULE_NAME, + init: init, + getBidRequestData: alterBidRequests, + gvlid: RAYN_TCF_ID, +}; + +submodule(MODULE_NAME, raynSubmodule); diff --git a/modules/raynRtdProvider.md b/modules/raynRtdProvider.md new file mode 100644 index 00000000000..8d888a18d1f --- /dev/null +++ b/modules/raynRtdProvider.md @@ -0,0 +1,118 @@ +--- +layout: page_v2 +title: Rayn RTD Provider +display_name: Rayn Real Time Data Module +description: Rayn Real Time Data module appends privacy preserving enhanced contextual categories and audiences. Moments matter. +page_type: module +module_type: rtd +module_code: raynRtdProvider +enable_download: true +vendor_specific: true +sidebarType: 1 +--- + +# Rayn Real-time Data Submodule + +Rayn is a privacy preserving, data platform. We turn content into context, into audiences. For Personalisation, Monetisation and Insights. This module reads contextual categories and audience cohorts from RaynJS (via localStorage) and passes them to the bid-stream. + +## Integration + +To install the module, follow these instructions: + +Step 1: Prepare the base Prebid file +Compile the Rayn RTD module (`raynRtdProvider`) into your Prebid build along with the parent RTD Module (`rtdModule`). From the command line, run gulp build `gulp build --modules=rtdModule,raynRtdProvider` + +Step 2: Set configuration +Enable Rayn RTD Module using pbjs.setConfig. Example is provided in the Configuration section. See the **Parameter Description** for more detailed information of the configuration parameters. + +### Configuration + +This module is configured as part of the realTimeData.dataProviders object. + +Example format: + +```js +pbjs.setConfig( + // ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "rayn", + waitForIt: true, + params: { + bidders: ["appnexus", "pubmatic"], + integration: { + iabAudienceCategories: { + v1_1: { + tier: 6, + enabled: true, + }, + }, + iabContentCategories: { + v3_0: { + tier: 4, + enabled: true, + }, + v2_2: { + tier: 4, + enabled: true, + }, + }, + } + } + } + ] + } + // ... +} +``` + +## Parameter Description + +The parameters below provide configurability for general behaviours of the RTD submodule, as well as enabling settings for specific use cases mentioned above (e.g. tiers and bidders). + +### Parameters + +{: .table .table-bordered .table-striped } +| Name | Type | Description | Notes | +| :---------------------------------------------------- | :-------- | :----------------------------------------------------------------------------------- | :---- | +| name | `String` | RTD sub module name | Always "rayn" | +| waitForIt | `Boolean` | Required to ensure that the auction is delayed for the module to respond | Optional. Defaults to false but recommended to true | +| params | `Object` | || +| params.bidders | `Array` | Bidders with which to share context and segment information | Optional. In case no bidder is specified Rayn will append data for all bidders | +| params.integration | `Object` | Controls which IAB taxonomy should be used and up to which category tier | Optional. In case it's not defined, all supported IAB taxonomies and all category tiers will be used | +| params.integration.iabAudienceCategories | `Object` | || +| params.integration.iabAudienceCategories.v1_1 | `Object` | || +| params.integration.iabAudienceCategories.v1_1.enabled | `Boolean` | Controls if IAB Audience Taxonomy v1.1 will be used | Optional. Enabled by default | +| params.integration.iabAudienceCategories.v1_1.tier | `Number` | Controls up to which IAB Audience Taxonomy v1.1 Category tier will be used | Optional. Tier 6 by default | +| params.integration.iabContentCategories | `Object` | || +| params.integration.iabContentCategories.v3_0 | `Object` | || +| params.integration.iabContentCategories.v3_0.enabled | `Boolean` | Controls if IAB Content Taxonomy v3.0 will be used | Optional. Enabled by default | +| params.integration.iabContentCategories.v3_0.tier | `Number` | Controls up to which IAB Content Taxonomy v3.0 Category tier will be used | Optional. Tier 4 by default | +| params.integration.iabContentCategories.v2_2 | `Object` | || +| params.integration.iabContentCategories.v2_2.enabled | `Boolean` | Controls if IAB Content Taxonomy v2.2 will be used | Optional. Enabled by default | +| params.integration.iabContentCategories.v2_2.tier | `Number` | Controls up to which IAB Content Taxonomy v2.2 Category tier will be used | Optional. Tier 4 by default | + +Please note that raynRtdProvider should be integrated into the website along with RaynJS. + +## Testing + +To view an example of the on page setup: + +```bash +gulp serve-fast --modules=rtdModule,raynRtdProvider,appnexusBidAdapter +``` + +Then in your browser access: [http://localhost:9999/integrationExamples/gpt/raynRtdProvider_example.html](http://localhost:9999/integrationExamples/gpt/raynRtdProvider_example.html) + +Run the unit tests, just on the Rayn RTD module test file: + +```bash +gulp test --file "test/spec/modules/raynRtdProvider_spec.js" +``` + +## Support + +If you require further assistance or are interested in discussing the module functionality please reach out to [support@rayn.io](mailto:support@rayn.io). +You are also able to find more examples and other integration routes on the Rayn documentation site. diff --git a/test/spec/modules/raynRtdProvider_spec.js b/test/spec/modules/raynRtdProvider_spec.js new file mode 100644 index 00000000000..69ea316e8b5 --- /dev/null +++ b/test/spec/modules/raynRtdProvider_spec.js @@ -0,0 +1,308 @@ +import * as raynRTD from 'modules/raynRtdProvider.js'; +import { config } from 'src/config.js'; +import * as utils from 'src/utils.js'; + +const TEST_CHECKSUM = '-1135402174'; +const TEST_URL = 'http://localhost:9876/context.html'; +const TEST_SEGMENTS = { + [TEST_CHECKSUM]: { + 7: { + 2: ['51', '246', '652', '48', '324'] + } + } +}; + +const RTD_CONFIG = { + auctionDelay: 250, + dataProviders: [ + { + name: 'rayn', + waitForIt: true, + params: { + bidders: [], + integration: { + iabAudienceCategories: { + v1_1: { + tier: 6, + enabled: true, + }, + }, + iabContentCategories: { + v3_0: { + tier: 4, + enabled: true, + }, + v2_2: { + tier: 4, + enabled: true, + }, + }, + } + }, + }, + ], +}; + +describe('rayn RTD Submodule', function () { + let getDataFromLocalStorageStub; + + beforeEach(function () { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub( + raynRTD.storage, + 'getDataFromLocalStorage', + ); + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('Initialize module', function () { + it('should initialize and return true', function () { + expect(raynRTD.raynSubmodule.init(RTD_CONFIG.dataProviders[0])).to.equal( + true, + ); + }); + }); + + describe('Generate ortb data object', function () { + it('should set empty segment array', function () { + expect(raynRTD.generateOrtbDataObject(7, 'invalid', 2).segment).to.be.instanceOf(Array).and.lengthOf(0); + }); + + it('should set segment array', function () { + const expectedSegmentIdsMap = TEST_SEGMENTS[TEST_CHECKSUM][7][2].map((id) => { + return { id }; + }); + expect(raynRTD.generateOrtbDataObject(7, TEST_SEGMENTS[TEST_CHECKSUM][7], 4)).to.deep.equal({ + name: raynRTD.SEGMENTS_RESOLVER, + ext: { + segtax: 7, + }, + segment: expectedSegmentIdsMap, + }); + }); + }); + + describe('Generate checksum', function () { + it('should generate checksum', function () { + expect(raynRTD.generateChecksum(TEST_URL)).to.equal(TEST_CHECKSUM); + }); + }); + + describe('Get segments', function () { + it('should get segments from local storage', function () { + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(TEST_SEGMENTS)); + + const segments = raynRTD.readSegments(raynRTD.RAYN_LOCAL_STORAGE_KEY); + + expect(segments).to.deep.equal(TEST_SEGMENTS); + }); + + it('should return null if unable to read and parse data from local storage', function () { + const testString = 'test'; + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(testString); + + const segments = raynRTD.readSegments(raynRTD.RAYN_LOCAL_STORAGE_KEY); + + expect(segments).to.equal(null); + }); + }); + + describe('Set segments as bidder ortb2', function () { + it('should set global ortb2 config', function () { + const globalOrtb2 = {}; + const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + const integrationConfig = RTD_CONFIG.dataProviders[0].params.integration; + + raynRTD.setSegmentsAsBidderOrtb2({ ortb2Fragments: { global: globalOrtb2 } }, bidders, integrationConfig, TEST_SEGMENTS, TEST_CHECKSUM); + + TEST_SEGMENTS[TEST_CHECKSUM]['7']['2'].forEach((id) => { + expect(globalOrtb2.site.content.data[0].segment.find(segment => segment.id === id)).to.exist; + }) + }); + + it('should set bidder specific ortb2 config', function () { + RTD_CONFIG.dataProviders[0].params.bidders = ['appnexus']; + + const bidderOrtb2 = {}; + const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + const integrationConfig = RTD_CONFIG.dataProviders[0].params.integration; + + raynRTD.setSegmentsAsBidderOrtb2({ ortb2Fragments: { bidder: bidderOrtb2 } }, bidders, integrationConfig, TEST_SEGMENTS, TEST_CHECKSUM); + + bidders.forEach((bidder) => { + const ortb2 = bidderOrtb2[bidder]; + TEST_SEGMENTS[TEST_CHECKSUM]['7']['2'].forEach((id) => { + expect(ortb2.site.content.data[0].segment.find(segment => segment.id === id)).to.exist; + }) + }); + }); + + it('should set bidder specific ortb2 config with all segments', function () { + TEST_SEGMENTS['4'] = { + 3: ['4', '17', '72', '612'] + }; + TEST_SEGMENTS[TEST_CHECKSUM]['6'] = { + 2: ['71', '313'], + 4: ['33', '145', '712'] + }; + + const bidderOrtb2 = {}; + const bidders = RTD_CONFIG.dataProviders[0].params.bidders; + const integrationConfig = RTD_CONFIG.dataProviders[0].params.integration; + + raynRTD.setSegmentsAsBidderOrtb2({ ortb2Fragments: { bidder: bidderOrtb2 } }, bidders, integrationConfig, TEST_SEGMENTS, TEST_CHECKSUM); + + bidders.forEach((bidder) => { + const ortb2 = bidderOrtb2[bidder]; + + TEST_SEGMENTS[TEST_CHECKSUM]['6']['2'].forEach((id) => { + expect(ortb2.site.content.data[0].segment.find(segment => segment.id === id)).to.exist; + }); + TEST_SEGMENTS[TEST_CHECKSUM]['6']['4'].forEach((id) => { + expect(ortb2.site.content.data[0].segment.find(segment => segment.id === id)).to.exist; + }); + TEST_SEGMENTS[TEST_CHECKSUM]['7']['2'].forEach((id) => { + expect(ortb2.site.content.data[1].segment.find(segment => segment.id === id)).to.exist; + }); + TEST_SEGMENTS['4']['3'].forEach((id) => { + expect(ortb2.user.data[0].segment.find(segment => segment.id === id)).to.exist; + }); + }); + }); + }); + + describe('Alter Bid Requests', function () { + it('should update reqBidsConfigObj and execute callback', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(TEST_SEGMENTS)); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from localStorage: ${JSON.stringify(TEST_SEGMENTS)}`); + + logMessageSpy.restore(); + }); + + it('should update reqBidsConfigObj and execute callback using user segments from localStorage', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const testSegments = { + 4: { + 3: ['4', '17', '72', '612'] + } + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(testSegments)); + + RTD_CONFIG.dataProviders[0].params.integration.iabContentCategories = { + v3_0: { + enabled: false, + }, + v2_2: { + enabled: false, + }, + }; + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from localStorage: ${JSON.stringify(testSegments)}`); + + logMessageSpy.restore(); + }); + + it('should update reqBidsConfigObj and execute callback using segments from raynJS', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(null); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`No segtax data`); + + logMessageSpy.restore(); + }); + + it('should update reqBidsConfigObj and execute callback using audience from localStorage', function (done) { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const testSegments = { + 6: { + 4: ['3', '27', '177'] + } + }; + + global.window.raynJS = { + getSegtax: function () { + return Promise.resolve(testSegments); + } + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(null); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from RaynJS: ${JSON.stringify(testSegments)}`); + logMessageSpy.restore(); + done(); + }, 0) + }); + + it('should execute callback if log error', function (done) { + const callbackSpy = sinon.spy(); + const logErrorSpy = sinon.spy(utils, 'logError'); + const rejectError = 'Error'; + + global.window.raynJS = { + getSegtax: function () { + return Promise.reject(rejectError); + } + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(null); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + setTimeout(() => { + expect(callbackSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.equal(rejectError); + logErrorSpy.restore(); + done(); + }, 0) + }); + }); +}); From d8c2ffde7d84229aff3e62ccc1c59d650cfc22d9 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:30:00 +0100 Subject: [PATCH 424/760] mediasquare Bid Adapter: add DSA support (#11070) * mediasquare Bid Adapter: add DSA support * mediasquare Bid Adapter: add DSA support --- modules/mediasquareBidAdapter.js | 2 ++ .../modules/mediasquareBidAdapter_spec.js | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 550c715e741..1ec05d17eef 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -95,6 +95,7 @@ export const spec = { } else if (bidderRequest.hasOwnProperty('bids') && typeof bidderRequest.bids == 'object' && bidderRequest.bids.length > 0 && bidderRequest.bids[0].hasOwnProperty('userId')) { payload.userId = bidderRequest.bids[0].userId; } + if (bidderRequest.ortb2?.regs?.ext?.dsa) { payload.dsa = bidderRequest.ortb2.regs.ext.dsa } }; if (test) { payload.debug = true; } const payloadString = JSON.stringify(payload); @@ -134,6 +135,7 @@ export const spec = { 'advertiserDomains': value['adomain'] } }; + if ('dsa' in value) { bidResponse.meta.dsa = value['dsa']; } let paramsToSearchFor = ['bidder', 'code', 'match', 'hasConsent', 'context', 'increment', 'ova']; paramsToSearchFor.forEach(param => { if (param in value) { diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index d7984c05967..6082ef65055 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -102,10 +102,34 @@ describe('MediaSquare bid adapter tests', function () { 'context': 'instream', 'increment': 1.0, 'ova': 'cleared', + 'dsa': { + 'behalf': 'some-behalf', + 'paid': 'some-paid', + 'transparency': [{ + 'domain': 'test.com', + 'dsaparams': [1, 2, 3] + }], + 'adrender': 1 + } }], }}; const DEFAULT_OPTIONS = { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + } + } + }, gdprConsent: { gdprApplies: true, consentString: 'BOzZdA0OzZdA0AGABBENDJ-AAAAvh7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__79__3z3_9pxP78k89r7337Mw_v-_v-b7JCPN_Y3v-8Kg', @@ -144,6 +168,7 @@ describe('MediaSquare bid adapter tests', function () { expect(requestContent.codes[0]).to.have.property('mediatypes').exist; expect(requestContent.codes[0]).to.have.property('floor').exist; expect(requestContent.codes[0].floor).to.deep.equal({}); + expect(requestContent).to.have.property('dsa'); const requestfloor = spec.buildRequests(FLOORS_PARAMS, DEFAULT_OPTIONS); const responsefloor = JSON.parse(requestfloor.data); expect(responsefloor.codes[0]).to.have.property('floor').exist; @@ -176,6 +201,7 @@ describe('MediaSquare bid adapter tests', function () { expect(bid.meta).to.exist; expect(bid.meta.advertiserDomains).to.exist; expect(bid.meta.advertiserDomains).to.have.lengthOf(1); + expect(bid.meta.dsa).to.exist; }); it('Verifies match', function () { const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_OPTIONS); From 9f5e205a0ba50408b1682858fb906930e6771725 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 7 Feb 2024 17:32:06 +0100 Subject: [PATCH 425/760] PBjs Core : add ability to inject tracking in video (#10191) * add vast impression tracking * support additional context macro * fix spaces and singlequotes * remove 2494945CONTEXT2494945 macro * remove CONTEXT macro * do not update vastImpUrl anymore * add impression trackers in video cache * insert ony unique trackers * rename registerVastTrackers * rename arrayVastTrackers * trackers object change * check modules are allowed to add trackers based on isActivityAllowed * rename validVastTracker and add line breaks * removes duplicates verification in isValidVastTracker * changes in wrapURI + typo fix * requested changes * update function trackersToMap * using Set in trackers map * changes suggested by dgirardi * changes suggested by dgirardi * Update test/spec/video_spec.js Co-authored-by: Karim Mourra * add spaces --------- Co-authored-by: Karim Mourra --- libraries/vastTrackers/vastTrackers.js | 95 ++++++++++++++++++++++++ src/videoCache.js | 7 +- test/spec/libraries/vastTrackers_spec.js | 33 ++++++++ test/spec/videoCache_spec.js | 24 ++++-- 4 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 libraries/vastTrackers/vastTrackers.js create mode 100644 test/spec/libraries/vastTrackers_spec.js diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js new file mode 100644 index 00000000000..b4ae98aba57 --- /dev/null +++ b/libraries/vastTrackers/vastTrackers.js @@ -0,0 +1,95 @@ +import {addBidResponse} from '../../src/auction.js'; +import {VIDEO} from '../../src/mediaTypes.js'; +import {logError} from '../../src/utils.js'; +import {isActivityAllowed} from '../../src/activities/rules.js'; +import {ACTIVITY_REPORT_ANALYTICS} from '../../src/activities/activities.js'; +import {activityParams} from '../../src/activities/activityParams.js'; + +const vastTrackers = []; + +addBidResponse.before(function (next, adUnitcode, bidResponse, reject) { + if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { + const vastTrackers = getVastTrackers(bidResponse); + if (vastTrackers) { + bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); + const impTrackers = vastTrackers.get('impressions'); + if (impTrackers) { + bidResponse.vastImpUrl = [].concat(impTrackers).concat(bidResponse.vastImpUrl).filter(t => t); + } + } + } + next(adUnitcode, bidResponse, reject); +}); + +export function registerVastTrackers(moduleType, moduleName, trackerFn) { + if (typeof trackerFn === 'function') { + vastTrackers.push({'moduleType': moduleType, 'moduleName': moduleName, 'trackerFn': trackerFn}); + } +} + +export function insertVastTrackers(trackers, vastXml) { + const doc = new DOMParser().parseFromString(vastXml, 'text/xml'); + const wrappers = doc.querySelectorAll('VAST Ad Wrapper, VAST Ad InLine'); + try { + if (wrappers.length) { + wrappers.forEach(wrapper => { + if (trackers.get('impressions')) { + trackers.get('impressions').forEach(trackingUrl => { + const impression = doc.createElement('Impression'); + impression.appendChild(doc.createCDATASection(trackingUrl)); + wrapper.appendChild(impression); + }); + } + }); + vastXml = new XMLSerializer().serializeToString(doc); + } + } catch (error) { + logError('an error happened trying to insert trackers in vastXml'); + } + return vastXml; +} + +export function getVastTrackers(bid) { + let trackers = []; + vastTrackers.filter( + ({ + moduleType, + moduleName, + trackerFn + }) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName)) + ).forEach(({trackerFn}) => { + let trackersToAdd = trackerFn(bid); + trackersToAdd.forEach(trackerToAdd => { + if (isValidVastTracker(trackers, trackerToAdd)) { + trackers.push(trackerToAdd); + } + }); + }); + const trackersMap = trackersToMap(trackers); + return (trackersMap.size ? trackersMap : null); +}; + +function isValidVastTracker(trackers, trackerToAdd) { + return trackerToAdd.hasOwnProperty('event') && trackerToAdd.hasOwnProperty('url'); +} + +function trackersToMap(trackers) { + return trackers.reduce((map, {url, event}) => { + !map.has(event) && map.set(event, new Set()); + map.get(event).add(url); + return map; + }, new Map()); +} + +export function addImpUrlToTrackers(bid, trackersMap) { + if (bid.vastImpUrl) { + if (!trackersMap) { + trackersMap = new Map(); + } + if (!trackersMap.get('impressions')) { + trackersMap.set('impressions', new Set()); + } + trackersMap.get('impressions').add(bid.vastImpUrl); + } + return trackersMap; +} diff --git a/src/videoCache.js b/src/videoCache.js index 88fc27625fd..ce03f2f624e 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -42,17 +42,18 @@ const ttlBufferInSeconds = 15; * @param {string} impUrl An impression tracker URL for the delivery of the video ad * @return A VAST URL which loads XML from the given URI. */ -function wrapURI(uri, impUrl) { +function wrapURI(uri, impTrackerURLs) { + impTrackerURLs = impTrackerURLs && (Array.isArray(impTrackerURLs) ? impTrackerURLs : [impTrackerURLs]); // Technically, this is vulnerable to cross-script injection by sketchy vastUrl bids. // We could make sure it's a valid URI... but since we're loading VAST XML from the // URL they provide anyway, that's probably not a big deal. - let vastImp = (impUrl) ? `` : ``; + let impressions = impTrackerURLs ? impTrackerURLs.map(trk => ``).join('') : ''; return ` prebid.org wrapper - ${vastImp} + ${impressions} diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js new file mode 100644 index 00000000000..3849ea75b02 --- /dev/null +++ b/test/spec/libraries/vastTrackers_spec.js @@ -0,0 +1,33 @@ +import {addImpUrlToTrackers, getVastTrackers, insertVastTrackers, registerVastTrackers} from 'libraries/vastTrackers/vastTrackers.js'; +import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js'; + +describe('vast trackers', () => { + it('insert into tracker list', function() { + let trackers = getVastTrackers({'cpm': 1.0}); + if (!trackers || !trackers.get('impressions')) { + registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { + return [ + {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + ]; + }); + } + trackers = getVastTrackers({'cpm': 1.0}); + expect(trackers).to.be.a('map'); + expect(trackers.get('impressions')).to.exists; + expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true; + }); + + it('insert trackers in vastXml', function() { + const trackers = getVastTrackers({'cpm': 1.0}); + let vastXml = ''; + vastXml = insertVastTrackers(trackers, vastXml); + expect(vastXml).to.equal(''); + }); + + it('test addImpUrlToTrackers', function() { + const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers({'cpm': 1.0})); + expect(trackers).to.be.a('map'); + expect(trackers.get('impressions')).to.exists; + expect(trackers.get('impressions').has('imptracker.com')).to.be.true; + }); +}) diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index c746fdd2afd..fc6e71779cb 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,10 +1,10 @@ import chai from 'chai'; -import { getCacheUrl, store } from 'src/videoCache.js'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; +import {getCacheUrl, store} from 'src/videoCache.js'; +import {config} from 'src/config.js'; +import {server} from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; -import { batchingCache } from '../../src/auction.js'; +import {batchingCache} from '../../src/auction.js'; const should = chai.should(); @@ -127,7 +127,7 @@ describe('The video cache', function () { prebid.org wrapper - + @@ -149,6 +149,20 @@ describe('The video cache', function () { assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: 'imptracker.com', ttl: 25 }, expectedValue) }); + it('should include multiple vastImpUrl when it\'s an array', function() { + const expectedValue = ` + + + prebid.org wrapper + + + + + + `; + assertRequestMade({ vastUrl: 'my-mock-url.com', vastImpUrl: ['https://vasttracking.mydomain.com/vast?cpm=1.2', 'imptracker.com'], ttl: 25, cpm: 1.2 }, expectedValue) + }); + it('should make the expected request when store() is called on an ad with vastXml', function () { const vastXml = ''; assertRequestMade({ vastXml: vastXml, ttl: 25 }, vastXml); From 92703399478d918db90ed26a6772e8e4e4cfbeac Mon Sep 17 00:00:00 2001 From: Jurij Sinickij Date: Wed, 7 Feb 2024 18:34:18 +0200 Subject: [PATCH 426/760] Adform Bid Adapter: add DSA support (#11066) --- modules/adfBidAdapter.js | 9 +++- test/spec/modules/adfBidAdapter_spec.js | 63 ++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index e5b40f66176..0484c383762 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -64,6 +64,7 @@ export const spec = { const cur = currency && [ currency ]; const eids = setOnAny(validBidRequests, 'userIdAsEids'); const schain = setOnAny(validBidRequests, 'schain'); + const dsa = commonFpd.regs?.ext?.dsa; const imp = validBidRequests.map((bid, id) => { bid.netRevenue = pt; @@ -179,6 +180,10 @@ export const spec = { deepSetValue(request, 'source.ext.schain', schain); } + if (dsa) { + deepSetValue(request, 'regs.ext.dsa', dsa); + } + return { method: 'POST', url: 'https://' + adxDomain + '/adx/openrtb', @@ -201,6 +206,7 @@ export const spec = { const bidResponse = bidResponses[id]; if (bidResponse) { const mediaType = deepAccess(bidResponse, 'ext.prebid.type'); + const dsa = deepAccess(bidResponse, 'ext.dsa'); const result = { requestId: bid.bidId, cpm: bidResponse.price, @@ -214,7 +220,8 @@ export const spec = { dealId: bidResponse.dealid, meta: { mediaType, - advertiserDomains: bidResponse.adomain + advertiserDomains: bidResponse.adomain, + dsa } }; diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index c1acff522c0..5612af8094c 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -142,6 +142,49 @@ describe('Adf adapter', function () { assert.equal(request.user, undefined); assert.equal(request.regs, undefined); }); + + it('should transfer DSA info', function () { + let validBidRequests = [ { bidId: 'bidId', params: { siteId: 'siteId' } } ]; + + let request = JSON.parse( + spec.buildRequests(validBidRequests, { + refererInfo: { page: 'page' }, + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [ + { + domain: 'test.com', + dsaparams: [1, 2, 3] + } + ] + } + } + } + } + }).data + ); + + assert.deepEqual(request.regs, { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [ + { + domain: 'test.com', + dsaparams: [1, 2, 3] + } + ] + } + } + }); + }); }); it('should add test and is_debug to request, if test is set in parameters', function () { @@ -1007,7 +1050,16 @@ describe('Adf adapter', function () { adomain: [ 'demo.com' ], ext: { prebid: { - type: 'native' + type: 'native', + }, + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 } } } @@ -1070,6 +1122,15 @@ describe('Adf adapter', function () { assert.deepEqual(bids[0].mediaType, 'native'); assert.deepEqual(bids[0].meta.mediaType, 'native'); assert.deepEqual(bids[0].meta.advertiserDomains, [ 'demo.com' ]); + assert.deepEqual(bids[0].meta.dsa, { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 + }); assert.deepEqual(bids[0].dealId, 'deal-id'); }); it('should set correct native params', function () { From 796ad95946941d6f76d32bc55cbb94d9439aebc6 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 7 Feb 2024 19:53:02 +0200 Subject: [PATCH 427/760] Vidazoo Bid Adapter: Implement onBidWon (#11057) * Add 'onBidWon' function and 'nurl' handling to vidazooBidAdapter * Add 'onBidWon' function and 'nurl' handling to vidazooBidAdapter * This update introduces the 'onBidWon' function to the vidazooBidAdapter module, and enhances handling for the 'nurl' property. --- modules/vidazooBidAdapter.js | 47 +++++++++++++- test/spec/modules/vidazooBidAdapter_spec.js | 70 +++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index b5323181c6c..59f3fe97969 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,4 +1,14 @@ -import {_each, deepAccess, isFn, parseSizesInput, parseUrl, uniques, isArray} from '../src/utils.js'; +import { + _each, + deepAccess, + isFn, + parseSizesInput, + parseUrl, + uniques, + isArray, + formatQS, + triggerPixel +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -271,6 +281,7 @@ function interpretResponse(serverResponse, request) { height, currency, bidId, + nurl, advertiserDomains, metaData, mediaType = BANNER @@ -290,6 +301,10 @@ function interpretResponse(serverResponse, request) { ttl: exp || TTL_SECONDS, }; + if (nurl) { + response.nurl = nurl; + } + if (metaData) { Object.assign(response, { meta: metaData @@ -350,6 +365,33 @@ function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', return syncs; } +/** + * @param {Bid} bid + */ +function onBidWon(bid) { + if (!bid.nurl) { + return; + } + const wonBid = { + adId: bid.adId, + creativeId: bid.creativeId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + netRevenue: bid.netRevenue, + mediaType: bid.mediaType, + timeToRespond: bid.timeToRespond, + status: bid.status, + }; + const qs = formatQS(wonBid); + const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; + triggerPixel(url); +} + export function hashCode(s, prefix = '_') { const l = s.length; let h = 0 @@ -445,7 +487,8 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - getUserSyncs + getUserSyncs, + onBidWon }; registerBidder(spec); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 864f2b8551c..bc5165c8d54 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -648,6 +648,14 @@ describe('VidazooBidAdapter', function () { expect(responses).to.have.length(1); expect(responses[0].ttl).to.equal(300); }); + + it('should add nurl if exists on response', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].nurl = 'https://test.com/win-notice?test=123'; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].nurl).to.equal('https://test.com/win-notice?test=123'); + }); }); describe('user id system', function () { @@ -833,4 +841,66 @@ describe('VidazooBidAdapter', function () { expect(parsed).to.be.equal(value); }); }); + + describe('validate onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('should call triggerPixel if nurl exists', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + nurl: 'https://test.com/win-notice?test=123', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.true; + + const url = utils.triggerPixel.args[0]; + + expect(url[0]).to.be.equal('https://test.com/win-notice?test=123&adId=2d52001cabd527&creativeId=12610997325162499419&auctionId=1fdb5ff1b6eaa7&transactionId=c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf&adUnitCode=div-gpt-ad-12345-0&cpm=0.8¤cy=USD&originalCpm=0.8&originalCurrency=USD&netRevenue=true&mediaType=banner&timeToRespond=100&status=rendered'); + }); + + it('should not call triggerPixel if nurl does not exist', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.false; + }); + }); }); From e410b28c3db64067d3282d6d179e832b6d315984 Mon Sep 17 00:00:00 2001 From: adquery <89853721+adquery@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:00:29 +0100 Subject: [PATCH 428/760] Adquery Bid Adapter : IdSystem fix getId and decode method, added userSync with iframe type (#11019) * adquery/prebid_qid_work5 * adquery/prebid_qid_work4 --- modules/adqueryBidAdapter.js | 71 +++++++++++++++------ modules/adqueryIdSystem.js | 15 ++++- test/spec/modules/adqueryBidAdapter_spec.js | 30 ++++++++- test/spec/modules/adqueryIdSystem_spec.js | 12 ++-- 4 files changed, 101 insertions(+), 27 deletions(-) diff --git a/modules/adqueryBidAdapter.js b/modules/adqueryBidAdapter.js index d5a8ba6da84..bfcc56050fb 100644 --- a/modules/adqueryBidAdapter.js +++ b/modules/adqueryBidAdapter.js @@ -1,6 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {buildUrl, logInfo, parseSizesInput, triggerPixel} from '../src/utils.js'; +import {buildUrl, logInfo, logMessage, parseSizesInput, triggerPixel} from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -14,7 +14,9 @@ const ADQUERY_GVLID = 902; const ADQUERY_BIDDER_CODE = 'adquery'; const ADQUERY_BIDDER_DOMAIN_PROTOCOL = 'https'; const ADQUERY_BIDDER_DOMAIN = 'bidder.adquery.io'; -const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUERY_BIDDER_DOMAIN + '/prebid/userSync?1=1'; +const ADQUERY_STATIC_DOMAIN_PROTOCOL = 'https'; +const ADQUERY_STATIC_DOMAIN = 'api.adquery.io'; +const ADQUERY_USER_SYNC_DOMAIN = ADQUERY_BIDDER_DOMAIN; const ADQUERY_DEFAULT_CURRENCY = 'PLN'; const ADQUERY_NET_REVENUE = true; const ADQUERY_TTL = 360; @@ -26,7 +28,6 @@ export const spec = { supportedMediaTypes: [BANNER], /** - * f * @param {object} bid * @return {boolean} */ @@ -41,10 +42,18 @@ export const spec = { */ buildRequests: (bidRequests, bidderRequest) => { const requests = []; + + let adqueryRequestUrl = buildUrl({ + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_BIDDER_DOMAIN, + pathname: '/prebid/bid', + // search: params + }); + for (let i = 0, len = bidRequests.length; i < len; i++) { const request = { method: 'POST', - url: ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUERY_BIDDER_DOMAIN + '/prebid/bid', + url: adqueryRequestUrl, // ADQUERY_BIDDER_DOMAIN_PROTOCOL + '://' + ADQUERY_BIDDER_DOMAIN + '/prebid/bid', data: buildRequest(bidRequests[i], bidderRequest), options: { withCredentials: false, @@ -62,8 +71,8 @@ export const spec = { * @return {Bid[]} */ interpretResponse: (response, request) => { - logInfo(request); - logInfo(response); + logMessage(request); + logMessage(response); const res = response && response.body && response.body.data; let bidResponses = []; @@ -125,7 +134,6 @@ export const spec = { */ onBidWon: (bid) => { logInfo('onBidWon', bid); - const bidString = JSON.stringify(bid); let copyOfBid = JSON.parse(bidString); delete copyOfBid.ad; @@ -169,21 +177,48 @@ export const spec = { }); triggerPixel(adqueryRequestUrl); }, + /** + * Retrieves user synchronization URLs based on provided options and consents. + * + * @param {object} syncOptions - Options for synchronization. + * @param {object[]} serverResponses - Array of server responses. + * @param {object} gdprConsent - GDPR consent object. + * @param {object} uspConsent - USP consent object. + * @returns {object[]} - Array of synchronization URLs. + */ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncUrl = ADQUERY_USER_SYNC_DOMAIN; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } + logMessage('getUserSyncs', syncOptions, serverResponses, gdprConsent, uspConsent); + let syncData = { + 'gdpr': gdprConsent && gdprConsent.gdprApplies ? 1 : 0, + 'gdpr_consent': gdprConsent && gdprConsent.consentString ? gdprConsent.consentString : '', + 'ccpa_consent': uspConsent && uspConsent.uspConsent ? uspConsent.uspConsent : '', + }; + + if (window.qid) { // only for new users (new qid) + syncData.qid = window.qid; } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + + let syncUrlObject = { + protocol: ADQUERY_BIDDER_DOMAIN_PROTOCOL, + hostname: ADQUERY_USER_SYNC_DOMAIN, + pathname: '/prebid/userSync', + search: syncData + }; + + if (syncOptions.iframeEnabled) { + syncUrlObject.protocol = ADQUERY_STATIC_DOMAIN_PROTOCOL; + syncUrlObject.hostname = ADQUERY_STATIC_DOMAIN; + syncUrlObject.pathname = '/user-sync-iframe.html'; + + return [{ + type: 'iframe', + url: buildUrl(syncUrlObject) + }]; } + return [{ type: 'image', - url: syncUrl + url: buildUrl(syncUrlObject) }]; } }; @@ -205,7 +240,7 @@ function buildRequest(validBidRequests, bidderRequest) { // onetime User ID const ramdomValues = Array.from(window.crypto.getRandomValues(new Uint32Array(4))); userId = ramdomValues.map(val => val.toString(36)).join('').substring(0, 20); - logInfo('generated onetime User ID: ', userId); + logMessage('generated onetime User ID: ', userId); window.qid = userId; } diff --git a/modules/adqueryIdSystem.js b/modules/adqueryIdSystem.js index eb00011593d..43795b3caba 100644 --- a/modules/adqueryIdSystem.js +++ b/modules/adqueryIdSystem.js @@ -8,7 +8,7 @@ import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; -import {isFn, isPlainObject, isStr, logError, logInfo} from '../src/utils.js'; +import {isFn, isPlainObject, isStr, logError, logInfo, logMessage} from '../src/utils.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; /** @@ -66,7 +66,18 @@ export const adqueryIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { - logInfo('adqueryIdSubmodule getId'); + logMessage('adqueryIdSubmodule getId'); + + let qid = storage.getDataFromLocalStorage('qid'); + + if (qid) { + return { + callback: function (callback) { + callback(qid); + } + } + } + if (!isPlainObject(config.params)) { config.params = {}; } diff --git a/test/spec/modules/adqueryBidAdapter_spec.js b/test/spec/modules/adqueryBidAdapter_spec.js index e9286329d57..b4aa0992732 100644 --- a/test/spec/modules/adqueryBidAdapter_spec.js +++ b/test/spec/modules/adqueryBidAdapter_spec.js @@ -155,11 +155,39 @@ describe('adqueryBidAdapter', function () { describe('getUserSyncs', function () { it('should return iframe sync', function () { - let sync = spec.getUserSyncs() + let sync = spec.getUserSyncs( + { + iframeEnabled: true, + pixelEnabled: true, + }, + {}, + { + consentString: 'ALL', + gdprApplies: true, + }, + {} + ) expect(sync.length).to.equal(1) expect(sync[0].type === 'iframe') expect(typeof sync[0].url === 'string') }) + it('should return image sync', function () { + let sync = spec.getUserSyncs( + { + iframeEnabled: false, + pixelEnabled: true, + }, + {}, + { + consentString: 'ALL', + gdprApplies: true, + }, + {} + ) + expect(sync.length).to.equal(1) + expect(sync[0].type === 'image') + expect(typeof sync[0].url === 'string') + }) it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { diff --git a/test/spec/modules/adqueryIdSystem_spec.js b/test/spec/modules/adqueryIdSystem_spec.js index 0a2cd60d89e..7952f23189e 100644 --- a/test/spec/modules/adqueryIdSystem_spec.js +++ b/test/spec/modules/adqueryIdSystem_spec.js @@ -38,23 +38,23 @@ describe('AdqueryIdSystem', function () { const callback = adqueryIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.contains(`https://bidder.adquery.io/prebid/qid?qid=`); - request.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({qid: '6dd9eab7dfeab7df6dd9ea'})); - expect(callbackSpy.lastCall.lastArg).to.deep.equal('6dd9eab7dfeab7df6dd9ea'); + expect(request.url).to.contain(`https://bidder.adquery.io/prebid/qid`); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'qid_string' })); + expect(callbackSpy.lastCall.lastArg).to.deep.equal('qid_string'); }); it('allows configurable id url', function () { const config = { params: { - url: 'https://another_bidder.adquery.io/qid' + url: 'https://bidder2.adquery.io' } }; const callbackSpy = sinon.spy(); const callback = adqueryIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; - expect(request.url).to.contains('https://another_bidder.adquery.io/qid'); - request.respond(200, {'Content-Type': 'application/json'}, JSON.stringify({qid: 'testqid'})); + expect(request.url).to.contains('https://bidder2.adquery.io'); + request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ qid: 'testqid' })); expect(callbackSpy.lastCall.lastArg).to.deep.equal('testqid'); }); }); From cd4717c5ea98bd4a24597417937109f93500a4c0 Mon Sep 17 00:00:00 2001 From: Malkov Mikhail Date: Thu, 8 Feb 2024 17:10:09 +0300 Subject: [PATCH 429/760] NextMillenium Bid Adapter : added support for the keywords parameter openrtb (#11018) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * added support keywords * added support keywords - code style * changed test for otrb parameters --- modules/nextMillenniumBidAdapter.js | 5 ++++- .../modules/nextMillenniumBidAdapter_spec.js | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 0af2129de61..d151523b265 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -50,6 +50,9 @@ const ALLOWED_ORTB2_PARAMETERS = [ 'site.content.cat', 'site.content.language', 'device.sua', + 'site.keywords', + 'site.content.keywords', + 'user.keywords', ]; const sendingDataStatistic = initSendingDataStatistic(); @@ -200,7 +203,7 @@ export const spec = { responses.forEach(response => { if (syncOptions.pixelEnabled) setPixelImages(response); if (syncOptions.iframeEnabled) setPixelIframes(response); - }) + }); } if (!pixels.length) { diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 169ab7b01e4..b9871bbbe71 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -391,6 +391,28 @@ describe('nextMillenniumBidAdapterTests', () => { }}, }, + { + title: 'site.keywords, site.content.keywords and user.keywords', + data: { + postBody: {}, + ortb2: { + user: {keywords: 'key7,key8,key9'}, + site: { + keywords: 'key1,key2,key3', + content: {keywords: 'key4,key5,key6'}, + }, + }, + }, + + expected: { + user: {keywords: 'key7,key8,key9'}, + site: { + keywords: 'key1,key2,key3', + content: {keywords: 'key4,key5,key6'}, + }, + }, + }, + { title: 'only site.content.language', data: { From e41a1749fe03c5293ab7e41d2def3f4f782d404b Mon Sep 17 00:00:00 2001 From: contentexchange <87769951+contentexchange@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:11:57 +0200 Subject: [PATCH 430/760] Contentexchange Bid Adapter: add gvlid (#11079) * add contentexchange bid adapter * fixes * fix * fix test * validate meta * fix * add GVLID --- modules/contentexchangeBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index be5900407ea..a6aa9262061 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -7,6 +7,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'contentexchange'; const AD_URL = 'https://eu2.adnetwork.agency/pbjs'; const SYNC_URL = 'https://sync2.adnetwork.agency'; +const GVLID = 864; function isBidResponseValid (bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || @@ -88,6 +89,7 @@ function getBidFloor(bid) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], isBidRequestValid: (bid = {}) => { From fe4a2f65a130e7c3b50e02c99373ba15757cfa35 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Feb 2024 17:06:23 +0000 Subject: [PATCH 431/760] Prebid 8.35.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4821d711887..fbf536395b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.35.0-pre", + "version": "8.35.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6741b36d415..e082bb64952 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.35.0-pre", + "version": "8.35.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 61ba3fe6e2a7ff68a890546bd001f489a959f87a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Feb 2024 17:06:24 +0000 Subject: [PATCH 432/760] Increment version to 8.36.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index fbf536395b5..4717c12f7c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.35.0", + "version": "8.36.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index e082bb64952..78e27a2a44a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.35.0", + "version": "8.36.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 35487453e78304ef1656f29d36f9764de4dfe98e Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 8 Feb 2024 10:55:36 -0800 Subject: [PATCH 433/760] Core: fix missing AD_RENDER_SUCCEDED for outstream renderers (#11073) --- src/adRendering.js | 6 ++- test/spec/unit/secureCreatives_spec.js | 56 ++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/adRendering.js b/src/adRendering.js index f8fe0044f9b..37bbf20d432 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -19,7 +19,10 @@ const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = constants */ export function emitAdRenderFail({ reason, message, bid, id }) { const data = { reason, message }; - if (bid) data.bid = bid; + if (bid) { + data.bid = bid; + data.adId = bid.adId; + } if (id) data.adId = id; logError(`Error rendering ad (id: ${id}): ${message}`); @@ -64,6 +67,7 @@ export function handleRender(renderFn, {adId, options, bidResponse, doc}) { // rendering for outstream safeframe if (isRendererRequired(renderer)) { executeRenderer(renderer, bidResponse, doc); + emitAdRenderSucceeded({doc, bid: bidResponse, id: adId}) } else if (adId) { if (mediaType === VIDEO) { emitAdRenderFail({ diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 895bf03165a..75813245298 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -15,6 +15,16 @@ import {handleRender} from '../../../src/adRendering.js'; var CONSTANTS = require('src/constants.json'); describe('secureCreatives', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + function makeEvent(ev) { return Object.assign({origin: 'mock-origin', ports: []}, ev) } @@ -63,14 +73,44 @@ describe('secureCreatives', () => { } }); - it('does not invoke renderFn, but the renderer instead, if the ad has one', () => { - const renderer = { - url: 'some-custom-renderer', - render: sinon.spy() - } - handleRender(renderFn, {bidResponse: {renderer}}); - sinon.assert.notCalled(renderFn); - sinon.assert.called(renderer.render); + describe('when the ad has a renderer', () => { + let bidResponse; + beforeEach(() => { + sandbox.stub(events, 'emit'); + bidResponse = { + adId: 'mock-ad-id', + renderer: { + url: 'some-custom-renderer', + render: sinon.stub() + } + } + }); + + it('does not invoke renderFn, but the renderer instead', () => { + handleRender(renderFn, {bidResponse}); + sinon.assert.notCalled(renderFn); + sinon.assert.called(bidResponse.renderer.render); + }); + + it('emits AD_RENDER_SUCCEDED', () => { + handleRender(renderFn, {bidResponse}); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ + bid: bidResponse, + adId: bidResponse.adId + })); + }); + + it('emits AD_RENDER_FAILED', () => { + const err = new Error('error message'); + bidResponse.renderer.render.throws(err); + handleRender(renderFn, {bidResponse}); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ + bid: bidResponse, + adId: bidResponse.adId, + reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, + message: err.message + })); + }) }); ['ad', 'adUrl'].forEach((prop) => { From f31058c131a3b8a6813e3b7fc494e9ebab29b12c Mon Sep 17 00:00:00 2001 From: Sajid Mahmood Date: Thu, 8 Feb 2024 13:56:19 -0500 Subject: [PATCH 434/760] IX Bid Adapter: support DSA fields [ADDR-2990] (#11069) Co-authored-by: Sajid Mahmood --- modules/ixBidAdapter.js | 31 ++++++ test/spec/modules/ixBidAdapter_spec.js | 141 ++++++++++++++++++++++++- 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 54cf3596dbb..a29c1a39bff 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -8,6 +8,9 @@ import { isEmpty, isFn, isInteger, + isNumber, + isStr, + isPlainObject, logError, logWarn, mergeDeep, @@ -520,6 +523,9 @@ function parseBid(rawBid, currency, bidRequest) { if (rawBid.adomain && rawBid.adomain.length > 0) { bid.meta.advertiserDomains = rawBid.adomain; } + if (rawBid.ext?.dsa) { + bid.meta.dsa = rawBid.ext.dsa + } return bid; } @@ -1212,6 +1218,7 @@ function addFPD(bidderRequest, r, fpd, site, user) { } } + // regulations from ortb2 if (fpd.hasOwnProperty('regs') && !bidderRequest.gppConsent) { if (fpd.regs.hasOwnProperty('gpp') && typeof fpd.regs.gpp == 'string') { deepSetValue(r, 'regs.gpp', fpd.regs.gpp) @@ -1220,6 +1227,30 @@ function addFPD(bidderRequest, r, fpd, site, user) { if (fpd.regs.hasOwnProperty('gpp_sid') && Array.isArray(fpd.regs.gpp_sid)) { deepSetValue(r, 'regs.gpp_sid', fpd.regs.gpp_sid) } + + if (fpd.regs.ext?.dsa) { + const pubDsaObj = fpd.regs.ext.dsa; + const dsaObj = {}; + ['dsarequired', 'pubrender', 'datatopub'].forEach((dsaKey) => { + if (isNumber(pubDsaObj[dsaKey])) { + dsaObj[dsaKey] = pubDsaObj[dsaKey]; + } + }); + + if (isArray(pubDsaObj.transparency)) { + const tpData = []; + pubDsaObj.transparency.forEach((tpObj) => { + if (isPlainObject(tpObj) && isStr(tpObj.domain) && tpObj.domain != '' && isArray(tpObj.dsaparams) && tpObj.dsaparams.every((v) => isNumber(v))) { + tpData.push(tpObj); + } + }); + if (tpData.length > 0) { + dsaObj.transparency = tpData; + } + } + + if (!isEmpty(dsaObj)) deepSetValue(r, 'regs.ext.dsa', dsaObj); + } } return r; diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 05b401fe4db..7655868ffc3 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -625,6 +625,45 @@ describe('IndexexchangeAdapter', function () { ] }; + const DEFAULT_BANNER_BID_RESPONSE_WITH_DSA = { + cur: 'USD', + id: '11a22b33c44d', + seatbid: [ + { + bid: [ + { + crid: '12345', + adomain: ['www.abc.com'], + adid: '14851455', + impid: '1a2b3c4d', + cid: '3051266', + price: 100, + w: 300, + h: 250, + id: '1', + ext: { + dspid: 50, + pricelevel: '_100', + advbrandid: 303325, + advbrand: 'OECTA', + dsa: { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + 'adrender': 1 + } + }, + adm: '' + } + ], + seat: '3970' + } + ] + }; + const DEFAULT_BANNER_BID_RESPONSE_WITHOUT_ADOMAIN = { cur: 'USD', id: '11a22b33c44d', @@ -1903,6 +1942,72 @@ describe('IndexexchangeAdapter', function () { expect(r.user.testProperty).to.be.undefined; }); + it('should set dsa field when defined', function () { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [{ + domain: 'domain.com', + dsaparams: [1] + }] + } + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: { + ext: { + dsa: deepClone(dsa) + } + } + }})[0]; + const r = extractPayload(request); + + expect(r.regs.ext.dsa.dsarequired).to.equal(dsa.dsarequired); + expect(r.regs.ext.dsa.pubrender).to.equal(dsa.pubrender); + expect(r.regs.ext.dsa.datatopub).to.equal(dsa.datatopub); + expect(r.regs.ext.dsa.transparency).to.be.an('array'); + expect(r.regs.ext.dsa.transparency).to.have.deep.members(dsa.transparency); + }); + it('should not set dsa fields when fields arent appropriately defined', function () { + const dsa = { + dsarequired: '3', + pubrender: '0', + datatopub: '2', + transparency: 20 + } + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: { + ext: { + dsa: deepClone(dsa) + } + } + }})[0]; + const r = extractPayload(request); + + expect(r.regs).to.be.undefined; + }); + it('should not set dsa transparency when fields arent appropriately defined', function () { + const dsa = { + transparency: [{ + domain: 3, + dsaparams: [1] + }, + { + domain: 'domain.com', + dsaparams: 'params' + }, + { + domain: 'domain.com', + dsaparams: ['1'] + }] + } + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: { + ext: { + dsa: deepClone(dsa) + } + } + }})[0]; + const r = extractPayload(request); + + expect(r.regs).to.be.undefined; + }); it('should set gpp and gpp_sid field when defined', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {gpp: 'gpp', gpp_sid: [1]}} })[0]; const r = extractPayload(request); @@ -1911,7 +2016,7 @@ describe('IndexexchangeAdapter', function () { expect(r.regs.gpp_sid).to.be.an('array'); expect(r.regs.gpp_sid).to.include(1); }); - it('should not set gpp and gpp_sid field when not defined', function () { + it('should not set gpp, gpp_sid and dsa field when not defined', function () { const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, { ortb2: {regs: {}} })[0]; const r = extractPayload(request); @@ -3529,6 +3634,40 @@ describe('IndexexchangeAdapter', function () { expect(result[0]).to.deep.equal(expectedParse[0]); }); + it('should get correct bid response for banner ad with dsa signals', function () { + const expectedParse = [ + { + requestId: '1a2b3c4d', + cpm: 1, + creativeId: '12345', + width: 300, + height: 250, + mediaType: 'banner', + ad: '', + currency: 'USD', + ttl: 300, + netRevenue: true, + meta: { + networkId: 50, + brandId: 303325, + brandName: 'OECTA', + advertiserDomains: ['www.abc.com'], + dsa: { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + 'adrender': 1 + } + } + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE_WITH_DSA }, bannerBidderRequest); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + it('should get correct bid response for banner ad with missing adomain', function () { const expectedParse = [ { From 6eaae9d1189741b3f1f1caf3e33e773dd48acd83 Mon Sep 17 00:00:00 2001 From: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:57:27 +0100 Subject: [PATCH 435/760] PBjs Core Utils: fix deepEqual() to work correctly on sites where Array.prototype has been extended (#11077) * Make utils.deepEqual() work correctly on sites where Array.prototype has been extended * Removed blank line * Marginal improvement --------- Co-authored-by: Demetrio Girardi --- src/utils.js | 5 +++-- test/spec/utils_spec.js | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/utils.js b/src/utils.js index 67ed7339675..7f71ff299fb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -898,8 +898,9 @@ export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { (typeof obj2 === 'object' && obj2 !== null) && (!checkTypes || (obj1.constructor === obj2.constructor)) ) { - if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; - for (let prop in obj1) { + const props1 = Object.keys(obj1); + if (props1.length !== Object.keys(obj2).length) return false; + for (let prop of props1) { if (obj2.hasOwnProperty(prop)) { if (!deepEqual(obj1[prop], obj2[prop], {checkTypes})) { return false; diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 098582c0af6..c84fe124db6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1002,6 +1002,15 @@ describe('Utils', function () { const obj = {key: 'value'}; expect(deepEqual({outer: obj}, {outer: new Typed(obj)}, {checkTypes: true})).to.be.false; }); + it('should work when adding properties to the prototype of Array', () => { + after(function () { + // eslint-disable-next-line no-extend-native + delete Array.prototype.unitTestTempProp; + }); + // eslint-disable-next-line no-extend-native + Array.prototype.unitTestTempProp = 'testing'; + expect(deepEqual([], [])).to.be.true; + }); describe('cyrb53Hash', function() { it('should return the same hash for the same string', function() { From e8426c7913b912844f63d5c092375b235cd19296 Mon Sep 17 00:00:00 2001 From: nkloeber <100145701+nkloeber@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:59:49 +0100 Subject: [PATCH 436/760] Yieldlab Bid Adapter: Add Digital Services Act (DSA) handling (#10981) * YieldlabBidAdapter add Digital Services Act (DSA) handling for bid request and responses * YieldlabBidAdapter - read dsa from bidderRequest - put dsa response under meta.dsa not ext.dsa - handle multiple transparency objects under new parameter dsatransparency - only add query params if they are not undefined --- modules/yieldlabBidAdapter.js | 70 +++++++++++- test/spec/modules/yieldlabBidAdapter_spec.js | 110 +++++++++++++++++++ 2 files changed, 176 insertions(+), 4 deletions(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index e4fba5beb62..13910d688e8 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -34,10 +34,7 @@ export const spec = { * @returns {boolean} */ isBidRequestValid(bid) { - if (bid && bid.params && bid.params.adslotId && bid.params.supplyId) { - return true; - } - return false; + return !!(bid && bid.params && bid.params.adslotId && bid.params.supplyId); }, /** @@ -106,6 +103,33 @@ export const spec = { query.consent = bidderRequest.gdprConsent.consentString; } } + + if (bidderRequest.ortb2?.regs?.ext?.dsa !== undefined) { + const dsa = bidderRequest.ortb2.regs.ext.dsa; + + assignIfNotUndefined(query, 'dsarequired', dsa.dsarequired); + assignIfNotUndefined(query, 'dsapubrender', dsa.pubrender); + assignIfNotUndefined(query, 'dsadatatopub', dsa.datatopub); + + if (Array.isArray(dsa.transparency)) { + const filteredTransparencies = dsa.transparency.filter(({ domain, dsaparams }) => { + return domain && !domain.includes('~') && Array.isArray(dsaparams) && dsaparams.length > 0 && dsaparams.every(param => typeof param === 'number'); + }); + + if (filteredTransparencies.length === 1) { + const { domain, dsaparams } = filteredTransparencies[0]; + assignIfNotUndefined(query, 'dsadomain', domain); + assignIfNotUndefined(query, 'dsaparams', dsaparams.join(',')); + } else if (filteredTransparencies.length > 1) { + const dsatransparency = filteredTransparencies.map(({ domain, dsaparams }) => + `${domain}~${dsaparams.join('_')}` + ).join('~~'); + if (dsatransparency) { + query.dsatransparency = dsatransparency; + } + } + } + } } const adslots = adslotIds.join(','); @@ -174,6 +198,11 @@ export const spec = { }, }; + const dsa = getDigitalServicesActObjectFromMatchedBid(matchedBid) + if (dsa !== undefined) { + bidResponse.meta = { ...bidResponse.meta, dsa: dsa }; + } + if (isVideo(bidRequest, adType)) { const playersize = getPlayerSize(bidRequest); if (playersize) { @@ -545,4 +574,37 @@ function isImageAssetOfType(type) { return asset => asset?.img?.type === type; } +/** + * Retrieves the Digital Services Act (DSA) object from a matched bid. + * Only includes specific attributes (behalf, paid, transparency, adrender) from the DSA object. + * + * @param {Object} matchedBid - The server response body to inspect for the DSA information. + * @returns {Object|undefined} A copy of the DSA object if it exists, or undefined if not. + */ +function getDigitalServicesActObjectFromMatchedBid(matchedBid) { + if (matchedBid.dsa) { + const { behalf, paid, transparency, adrender } = matchedBid.dsa; + return { + ...(behalf !== undefined && { behalf }), + ...(paid !== undefined && { paid }), + ...(transparency !== undefined && { transparency }), + ...(adrender !== undefined && { adrender }) + }; + } + return undefined; +} + +/** + * Conditionally assigns a value to a specified key on an object if the value is not undefined. + * + * @param {Object} obj - The object to which the value will be assigned. + * @param {string} key - The key under which the value should be assigned. + * @param {*} value - The value to be assigned, if it is not undefined. + */ +function assignIfNotUndefined(obj, key, value) { + if (value !== undefined) { + obj[key] = value; + } +} + registerBidder(spec); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 93c231c816b..751dff4fe33 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -226,6 +226,36 @@ const PVID_RESPONSE = Object.assign({}, VIDEO_RESPONSE, { pvid: '43513f11-55a0-4a83-94e5-0ebc08f54a2c', }); +const DIGITAL_SERVICES_ACT_RESPONSE = Object.assign({}, RESPONSE, { + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 + } +}); + +const DIGITAL_SERVICES_ACT_CONFIG = { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + }, + } + }, + } +} + const REQPARAMS = { json: true, ts: 1234567890, @@ -486,6 +516,75 @@ describe('yieldlabBidAdapter', () => { expect(request.url).to.not.include('sizes'); }); }); + + describe('Digital Services Act handling', () => { + beforeEach(() => { + config.setConfig(DIGITAL_SERVICES_ACT_CONFIG); + }); + + afterEach(() => { + config.resetConfig(); + }); + + it('does pass dsarequired parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); + expect(request.url).to.include('dsarequired=1'); + }); + + it('does pass dsapubrender parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); + expect(request.url).to.include('dsapubrender=2'); + }); + + it('does pass dsadatatopub parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); + expect(request.url).to.include('dsadatatopub=3'); + }); + + it('does pass dsadomain parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); + expect(request.url).to.include('dsadomain=test.com'); + }); + + it('does pass encoded dsaparams parameter', () => { + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DIGITAL_SERVICES_ACT_CONFIG }); + expect(request.url).to.include('dsaparams=1%2C2%2C3'); + }); + + it('does pass multiple transparencies in dsatransparency param', () => { + const DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES = { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [ + { + domain: 'test.com', + dsaparams: [1, 2, 3] + }, + { + domain: 'example.com', + dsaparams: [4, 5, 6] + } + ] + } + } + } + } + }; + + config.setConfig(DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES); + + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...DSA_CONFIG_WITH_MULTIPLE_TRANSPARENCIES }); + + expect(request.url).to.include('dsatransparency=test.com~1_2_3~~example.com~4_5_6'); + expect(request.url).to.not.include('dsadomain'); + expect(request.url).to.not.include('dsaparams'); + }); + }); }); describe('interpretResponse', () => { @@ -676,6 +775,17 @@ describe('yieldlabBidAdapter', () => { const result = spec.interpretResponse({body: [VIDEO_RESPONSE]}, {validBidRequests: [VIDEO_REQUEST()], queryParams: REQPARAMS_IAB_CONTENT}); expect(result[0].vastUrl).to.include('&iab_content=id%3Afoo_id%2Cepisode%3A99%2Ctitle%3Afoo_title%252Cbar_title%2Cseries%3Afoo_series%2Cseason%3As1%2Cartist%3Afoo%2520bar%2Cgenre%3Abaz%2Cisrc%3ACC-XXX-YY-NNNNN%2Curl%3Ahttp%253A%252F%252Ffoo_url.de%2Ccat%3Acat1%7Ccat2%252Cppp%7Ccat3%257C%257C%257C%252F%252F%2Ccontext%3A7%2Ckeywords%3Ak1%252C%7Ck2..%2Clive%3A0'); }); + + it('should get digital services act object in matched bid response', () => { + const result = spec.interpretResponse({body: [DIGITAL_SERVICES_ACT_RESPONSE]}, {validBidRequests: [{...DEFAULT_REQUEST(), ...DIGITAL_SERVICES_ACT_CONFIG}], queryParams: REQPARAMS}); + + expect(result[0].requestId).to.equal('2d925f27f5079f'); + expect(result[0].meta.dsa.behalf).to.equal('some-behalf'); + expect(result[0].meta.dsa.paid).to.equal('some-paid'); + expect(result[0].meta.dsa.transparency[0].domain).to.equal('test.com'); + expect(result[0].meta.dsa.transparency[0].dsaparams).to.deep.equal([1, 2, 3]); + expect(result[0].meta.dsa.adrender).to.equal(1); + }); }); describe('getUserSyncs', () => { From 6fddf80dc428e8d784a69946dc53873641245c48 Mon Sep 17 00:00:00 2001 From: Evgenii Novikov Date: Thu, 8 Feb 2024 20:01:34 +0100 Subject: [PATCH 437/760] Fixed build and improved docs (#11078) --- modules/yandexAnalyticsAdapter.js | 2 +- modules/yandexAnalyticsAdapter.md | 52 ++++++++++++++++++------------- modules/yandexBidAdapter.md | 4 ++- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js index 045de18e21b..ba000db6162 100644 --- a/modules/yandexAnalyticsAdapter.js +++ b/modules/yandexAnalyticsAdapter.js @@ -148,7 +148,7 @@ const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' } adapterManager.registerAnalyticsAdapter({ adapter: yandexAnalytics, - code: 'yandexAnalytics' + code: 'yandex' }); export default yandexAnalytics; diff --git a/modules/yandexAnalyticsAdapter.md b/modules/yandexAnalyticsAdapter.md index 96d2b08320e..43460550471 100644 --- a/modules/yandexAnalyticsAdapter.md +++ b/modules/yandexAnalyticsAdapter.md @@ -1,4 +1,4 @@ -# Overview +## Overview ``` Module Name: Yandex Analytics Adapter @@ -6,31 +6,41 @@ Module Type: Analytics Adapter Maintainer: prebid@yandex-team.com ``` -# Description +## Description -This adapter is designed to work with [Yandex Metrica](https://metrica.yandex.com/about) - Top-5 worldwide web analytics tool. +The Yandex Analytics Adapter integrates Prebid.js with [Yandex Metrica](https://metrica.yandex.com/about), a top-5 worldwide web analytics tool. It offers detailed insights into auction performance and user behavior, enabling publishers to make data-driven decisions to optimize their ad revenue. -Disclosure: provider use Metrica Tag build based on https://github.com/yandex/metrica-tag, ~60 kB gzipped. +Disclosure: The adapter utilizes the Metrica Tag build based on [github.com/yandex/metrica-tag](https://github.com/yandex/metrica-tag), approximately 60 kB gzipped. -## How to setup provider +## Setup Instructions -Register your application on https://metrica.yandex.com/ and get counter id. -Insert counter initialization code obtained from the page https://metrica.yandex.com/settings?id={counterId} into your html code. -Init provider like this, where `123` is your counter id. +1. **Register Your Website:** -Note: If you have Single Page Application (SPA), [configure your tag](https://yandex.com/support/metrica/code/counter-spa-setup.html). + Visit [Yandex Metrica](https://metrica.yandex.com/) and register your website to obtain a counter ID. -```javascript -pbjs.enableAnalytics({ - provider: 'yandexAnalytics', - options: { - counters: [ - 123, - ], - }, -}); -``` +2. **Insert Counter Initialization Code:** + + Retrieve the counter initialization code from the Yandex Metrica settings page at `https://metrica.yandex.com/settings?id={counterId}`, where `{counterId}` is your counter ID, and embed it into your website's HTML. + +3. **Initialize the Adapter in Prebid.js:** + + Configure the Yandex Analytics Adapter in your Prebid.js setup. For optimal performance and ease of management, it is preferred to use a single counter. Add the following JavaScript snippet, replacing `123` with your actual counter ID: + + ```javascript + pbjs.enableAnalytics({ + provider: "yandex", + options: { + // Replace 123 with your actual counter ID + // It's preferred to use a single counter for optimal performance and ease of management + counters: [123] + } + }); + ``` + +4. **Special Instructions for Single Page Applications (SPAs):** + + If your website is an SPA, make sure to [configure your Metrica tag accordingly](https://yandex.com/support/metrica/code/counter-spa-setup.html). -## Where to find data +## Accessing Analytics Data -Go to https://metrika.yandex.com/dashboard -> Prebid Analytics +You can view the collected analytics data in the Yandex Metrica dashboard. Navigate to [metrika.yandex.com/dashboard](https://metrika.yandex.com/dashboard) and look for the Prebid Analytics section to analyze your data. diff --git a/modules/yandexBidAdapter.md b/modules/yandexBidAdapter.md index 55a658cc25c..f16d0ec0b33 100644 --- a/modules/yandexBidAdapter.md +++ b/modules/yandexBidAdapter.md @@ -8,7 +8,9 @@ Maintainer: prebid@yandex-team.com # Description -Yandex Bidder Adapter for Prebid.js. +The Yandex Prebid Adapter is designed for seamless integration with Yandex's advertising services. It facilitates effective bidding by leveraging Yandex's robust ad-serving technology, ensuring publishers can maximize their ad revenue through efficient and targeted ad placements. + +For comprehensive auction analytics, consider using the [Yandex Analytics Adapter](https://docs.prebid.org/dev-docs/analytics/yandex.html). This tool provides essential insights into auction dynamics and user interactions, empowering publishers to fine-tune their strategies for optimal ad performance. # Parameters From dbbbccd4aaafcd5a005013e47f620d4cbb1cf1aa Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 8 Feb 2024 12:16:36 -0800 Subject: [PATCH 438/760] Build system: add --no-lint-warnings option (#11082) --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index d8a18ba3df1..125dec93402 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,7 +88,7 @@ function lint(done) { '!plugins/**/node_modules/**', './*.js' ], { base: './' }) - .pipe(gulpif(argv.nolintfix, eslint(), eslint({ fix: true }))) + .pipe(eslint({ fix: !argv.nolintfix, quiet: !(argv.lintWarnings ?? true) })) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); From 43e3980a03b88b186892f5ab7258b8f5e2f057aa Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 8 Feb 2024 13:07:57 -0800 Subject: [PATCH 439/760] dsaControl module: Reject bids without meta.dsa when required (#10982) * dsaControl - reject bids without meta.dsa when required * ortbConverter: always set meta.dsa * dsaControl: reject bids whose DSA rendering method disagrees with the request --- libraries/ortbConverter/processors/default.js | 3 + modules/dsaControl.js | 67 +++++++++++ src/auctionManager.js | 4 +- src/constants.json | 4 +- src/utils/ttlCollection.js | 25 +++- test/spec/modules/dsaControl_spec.js | 113 ++++++++++++++++++ test/spec/ortbConverter/common_spec.js | 29 +++++ test/spec/unit/utils/ttlCollection_spec.js | 27 +++++ 8 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 modules/dsaControl.js create mode 100644 test/spec/modules/dsaControl_spec.js create mode 100644 test/spec/ortbConverter/common_spec.js diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index 8db2c1c461e..d92a51daba2 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -97,6 +97,9 @@ export const DEFAULT_PROCESSORS = { if (bid.adomain) { bidResponse.meta.advertiserDomains = bid.adomain; } + if (bid.ext?.dsa) { + bidResponse.meta.dsa = bid.ext.dsa; + } } } } diff --git a/modules/dsaControl.js b/modules/dsaControl.js new file mode 100644 index 00000000000..b08a6ea1f4e --- /dev/null +++ b/modules/dsaControl.js @@ -0,0 +1,67 @@ +import {config} from '../src/config.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; +import CONSTANTS from '../src/constants.json'; +import {getHook} from '../src/hook.js'; +import {logInfo, logWarn} from '../src/utils.js'; + +let expiryHandle; +let dsaAuctions = {}; + +export const addBidResponseHook = timedBidResponseHook('dsa', function (fn, adUnitCode, bid, reject) { + if (!dsaAuctions.hasOwnProperty(bid.auctionId)) { + dsaAuctions[bid.auctionId] = auctionManager.index.getAuction(bid)?.getFPD?.()?.global?.regs?.ext?.dsa; + } + const dsaRequest = dsaAuctions[bid.auctionId]; + let rejectReason; + if (dsaRequest) { + if (!bid.meta?.dsa) { + if (dsaRequest.dsarequired === 1) { + // request says dsa is supported; response does not have dsa info; warn about it + logWarn(`dsaControl: ${CONSTANTS.REJECTION_REASON.DSA_REQUIRED}; will still be accepted as regs.ext.dsa.dsarequired = 1`, bid); + } else if ([2, 3].includes(dsaRequest.dsarequired)) { + // request says dsa is required; response does not have dsa info; reject it + rejectReason = CONSTANTS.REJECTION_REASON.DSA_REQUIRED; + } + } else { + if (dsaRequest.pubrender === 0 && bid.meta.dsa.adrender === 0) { + // request says publisher can't render; response says advertiser won't; reject it + rejectReason = CONSTANTS.REJECTION_REASON.DSA_MISMATCH; + } else if (dsaRequest.pubrender === 2 && bid.meta.dsa.adrender === 1) { + // request says publisher will render; response says advertiser will; reject it + rejectReason = CONSTANTS.REJECTION_REASON.DSA_MISMATCH; + } + } + } + if (rejectReason) { + reject(rejectReason); + } else { + return fn.call(this, adUnitCode, bid, reject); + } +}); + +function toggleHooks(enabled) { + if (enabled && expiryHandle == null) { + getHook('addBidResponse').before(addBidResponseHook); + expiryHandle = auctionManager.onExpiry(auction => { + delete dsaAuctions[auction.getAuctionId()]; + }); + logInfo('dsaControl: DSA bid validation is enabled') + } else if (!enabled && expiryHandle != null) { + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + expiryHandle(); + expiryHandle = null; + logInfo('dsaControl: DSA bid validation is disabled') + } +} + +export function reset() { + toggleHooks(false); + dsaAuctions = {}; +} + +toggleHooks(true); + +config.getConfig('consentManagement', (cfg) => { + toggleHooks(cfg.consentManagement?.dsa?.validateBids ?? true); +}); diff --git a/src/auctionManager.js b/src/auctionManager.js index 8f3cbb56333..2d6e0ffbfd9 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -60,7 +60,9 @@ export function newAuctionManager() { } }) - const auctionManager = {}; + const auctionManager = { + onExpiry: _auctions.onExpiry + }; function getAuction(auctionId) { for (const auction of _auctions) { diff --git a/src/constants.json b/src/constants.json index 53ee407b772..af7e80ec0b4 100644 --- a/src/constants.json +++ b/src/constants.json @@ -134,7 +134,9 @@ "INVALID_REQUEST_ID": "Invalid request ID", "BIDDER_DISALLOWED": "Bidder code is not allowed by allowedAlternateBidderCodes / allowUnknownBidderCodes", "FLOOR_NOT_MET": "Bid does not meet price floor", - "CANNOT_CONVERT_CURRENCY": "Unable to convert currency" + "CANNOT_CONVERT_CURRENCY": "Unable to convert currency", + "DSA_REQUIRED": "Bid does not provide required DSA transparency info", + "DSA_MISMATCH": "Bid indicates inappropriate DSA rendering method" }, "PREBID_NATIVE_DATA_KEYS_TO_ORTB": { "body": "desc", diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index 392ed1c9ad7..0972d175848 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -1,5 +1,5 @@ import {GreedyPromise} from './promise.js'; -import {binarySearch, timestamp} from '../utils.js'; +import {binarySearch, logError, timestamp} from '../utils.js'; /** * Create a set-like collection that automatically forgets items after a certain time. @@ -27,6 +27,7 @@ export function ttlCollection( } = {} ) { const items = new Map(); + const callbacks = []; const pendingPurge = []; const markForPurge = monotonic ? (entry) => pendingPurge.push(entry) @@ -43,6 +44,13 @@ export function ttlCollection( let cnt = 0; for (const entry of pendingPurge) { if (entry.expiry > now) break; + callbacks.forEach(cb => { + try { + cb(entry.item) + } catch (e) { + logError(e); + } + }); items.delete(entry.item) cnt++; } @@ -135,5 +143,20 @@ export function ttlCollection( entry.refresh(); } }, + /** + * Register a callback to be run when an item has expired and is about to be + * removed the from the collection. + * @param cb a callback that takes the expired item as argument + * @return an unregistration function. + */ + onExpiry(cb) { + callbacks.push(cb); + return () => { + const idx = callbacks.indexOf(cb); + if (idx >= 0) { + callbacks.splice(idx, 1); + } + } + } }; } diff --git a/test/spec/modules/dsaControl_spec.js b/test/spec/modules/dsaControl_spec.js new file mode 100644 index 00000000000..0d7c52b5efd --- /dev/null +++ b/test/spec/modules/dsaControl_spec.js @@ -0,0 +1,113 @@ +import {addBidResponseHook, setMetaDsa, reset} from '../../../modules/dsaControl.js'; +import CONSTANTS from 'src/constants.json'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {AuctionIndex} from '../../../src/auctionIndex.js'; + +describe('DSA transparency', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + reset(); + }); + + describe('addBidResponseHook', () => { + const auctionId = 'auction-id'; + let bid, auction, fpd, next, reject; + beforeEach(() => { + next = sinon.stub(); + reject = sinon.stub(); + fpd = {}; + bid = { + auctionId + } + auction = { + getAuctionId: () => auctionId, + getFPD: () => ({global: fpd}) + } + sandbox.stub(auctionManager, 'index').get(() => new AuctionIndex(() => [auction])); + }); + + function expectRejection(reason) { + addBidResponseHook(next, 'adUnit', bid, reject); + sinon.assert.calledWith(reject, reason); + sinon.assert.notCalled(next); + } + + function expectAcceptance() { + addBidResponseHook(next, 'adUnit', bid, reject); + sinon.assert.notCalled(reject); + sinon.assert.calledWith(next, 'adUnit', bid, reject); + } + + [2, 3].forEach(required => { + describe(`when regs.ext.dsa.dsarequired is ${required} (required)`, () => { + beforeEach(() => { + fpd = { + regs: {ext: {dsa: {dsarequired: required}}} + }; + }); + + it('should reject bids that have no meta.dsa', () => { + expectRejection(CONSTANTS.REJECTION_REASON.DSA_REQUIRED); + }); + + it('should accept bids that do', () => { + bid.meta = {dsa: {}}; + expectAcceptance(); + }); + + describe('and pubrender = 0 (rendering by publisher not supported)', () => { + beforeEach(() => { + fpd.regs.ext.dsa.pubrender = 0; + }); + + it('should reject bids with adrender = 0 (advertiser will not render)', () => { + bid.meta = {dsa: {adrender: 0}}; + expectRejection(CONSTANTS.REJECTION_REASON.DSA_MISMATCH); + }); + + it('should accept bids with adrender = 1 (advertiser will render)', () => { + bid.meta = {dsa: {adrender: 1}}; + expectAcceptance(); + }); + }); + describe('and pubrender = 2 (publisher will render)', () => { + beforeEach(() => { + fpd.regs.ext.dsa.pubrender = 2; + }); + + it('should reject bids with adrender = 1 (advertiser will render)', () => { + bid.meta = {dsa: {adrender: 1}}; + expectRejection(CONSTANTS.REJECTION_REASON.DSA_MISMATCH); + }); + + it('should accept bids with adrender = 0 (advertiser will not render)', () => { + bid.meta = {dsa: {adrender: 0}}; + expectAcceptance(); + }) + }) + }); + }); + [undefined, 'garbage', 0, 1].forEach(required => { + describe(`when regs.ext.dsa.dsarequired is ${required}`, () => { + beforeEach(() => { + if (required != null) { + fpd = { + regs: {ext: {dsa: {dsarequired: required}}} + } + } + }); + + it('should accept bids regardless of their meta.dsa', () => { + addBidResponseHook(next, 'adUnit', bid, reject); + sinon.assert.notCalled(reject); + sinon.assert.calledWith(next, 'adUnit', bid, reject); + }) + }) + }) + it('should accept bids regardless of dsa when "required" any other value') + }); +}); diff --git a/test/spec/ortbConverter/common_spec.js b/test/spec/ortbConverter/common_spec.js new file mode 100644 index 00000000000..d2d61e6778c --- /dev/null +++ b/test/spec/ortbConverter/common_spec.js @@ -0,0 +1,29 @@ +import {DEFAULT_PROCESSORS} from '../../../libraries/ortbConverter/processors/default.js'; +import {BID_RESPONSE} from '../../../src/pbjsORTB.js'; + +describe('common processors', () => { + describe('bid response properties', () => { + const responseProps = DEFAULT_PROCESSORS[BID_RESPONSE].props.fn; + let context; + + beforeEach(() => { + context = { + ortbResponse: {} + } + }) + + describe('meta.dsa', () => { + const MOCK_DSA = {transparency: 'info'}; + it('is not set if bid has no meta.dsa', () => { + const resp = {}; + responseProps(resp, {}, context); + expect(resp.meta?.dsa).to.not.exist; + }); + it('is set to ext.dsa otherwise', () => { + const resp = {}; + responseProps(resp, {ext: {dsa: MOCK_DSA}}, context); + expect(resp.meta.dsa).to.eql(MOCK_DSA); + }) + }) + }) +}) diff --git a/test/spec/unit/utils/ttlCollection_spec.js b/test/spec/unit/utils/ttlCollection_spec.js index 29c6c438855..76cfa32d955 100644 --- a/test/spec/unit/utils/ttlCollection_spec.js +++ b/test/spec/unit/utils/ttlCollection_spec.js @@ -67,6 +67,33 @@ describe('ttlCollection', () => { }); }); + it('should run onExpiry when items are cleared', () => { + const i1 = {ttl: 1000, some: 'data'}; + const i2 = {ttl: 2000, some: 'data'}; + coll.add(i1); + coll.add(i2); + const cb = sinon.stub(); + coll.onExpiry(cb); + return waitForPromises().then(() => { + clock.tick(500); + sinon.assert.notCalled(cb); + clock.tick(SLACK + 500); + sinon.assert.calledWith(cb, i1); + clock.tick(3000); + sinon.assert.calledWith(cb, i2); + }) + }); + + it('should allow unregistration of onExpiry callbacks', () => { + const cb = sinon.stub(); + coll.add({ttl: 500}); + coll.onExpiry(cb)(); + return waitForPromises().then(() => { + clock.tick(500 + SLACK); + sinon.assert.notCalled(cb); + }) + }) + it('should not wait too long if a shorter ttl shows up', () => { coll.add({ttl: 4000}); coll.add({ttl: 1000}); From 40474958a89607ca09b483c59765a22fe18abaaa Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Feb 2024 21:45:32 +0000 Subject: [PATCH 440/760] Prebid 8.36.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4717c12f7c4..48c1fabb125 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.36.0-pre", + "version": "8.36.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 78e27a2a44a..ee895df69e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.36.0-pre", + "version": "8.36.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8bda2cee499152160e7b9784473ddf206df7b697 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 8 Feb 2024 21:45:33 +0000 Subject: [PATCH 441/760] Increment version to 8.37.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48c1fabb125..4686462599c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.36.0", + "version": "8.37.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ee895df69e3..1954a8c1294 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.36.0", + "version": "8.37.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f7163de30684028ed8480de20190cdcc840917d6 Mon Sep 17 00:00:00 2001 From: redaguermas Date: Sat, 10 Feb 2024 03:16:40 -0800 Subject: [PATCH 442/760] NoBid Analytics Adapter: added support for flag to control bidWon and auctionEnd independently. (#11087) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Delete nobidBidAdapter.js.orig * Delete a * Delete .jsdtscope * Delete org.eclipse.wst.jsdt.ui.superType.container * Delete org.eclipse.wst.jsdt.ui.superType.name * Delete .project * Added support for counting blocked requests for the Optimizer. * Added missing function for testing. * Added unit tests * Added support for Analytics adapter flag to control bidWon and auctionEnd independently. --------- Co-authored-by: Reda Guermas --- modules/nobidAnalyticsAdapter.js | 26 +++- .../modules/nobidAnalyticsAdapter_spec.js | 127 ++++++++++++++++-- 2 files changed, 135 insertions(+), 18 deletions(-) diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index 3a7912c37e1..2c119e28610 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 = '1.1.0'; +const VERSION = '2.0.0'; const MODULE_NAME = 'nobidAnalyticsAdapter'; const ANALYTICS_OPT_FLUSH_TIMEOUT_SECONDS = 5 * 1000; const RETENTION_SECONDS = 1 * 24 * 3600; @@ -49,7 +49,7 @@ function sendEvent (event, eventType) { return ret; } if (!nobidAnalytics.initOptions || !nobidAnalytics.initOptions.siteId || !event) return; - if (nobidAnalytics.isAnalyticsDisabled()) { + if (nobidAnalytics.isAnalyticsDisabled(eventType)) { log('NoBid Analytics is Disabled'); return; } @@ -97,8 +97,16 @@ function sendAuctionEndEvent (event, eventType) { 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.noBids, ['bidder', 'sizes', 'bidId']); - if (data.bidderRequests) cleanupObjectAttributes(data.bidderRequests.bids, ['mediaTypes', 'adUnitCode', 'sizes', 'bidId']); - if (data.bidderRequests) cleanupObjectAttributes(data.bidderRequests.refererInfo, ['topmostLocation']); + if (data.bidderRequests) { + data.bidderRequests.forEach(bidderRequest => { + cleanupObjectAttributes(bidderRequest.bids, ['mediaTypes', 'adUnitCode', 'sizes', 'bidId']); + }); + } + if (data.bidderRequests) { + data.bidderRequests.forEach(bidderRequest => { + cleanupObjectAttributes(bidderRequest.refererInfo, ['topmostLocation']); + }); + } sendEvent(data, eventType); } function auctionInit (event) { @@ -147,12 +155,18 @@ nobidAnalytics = { isExpired (data) { return isExpired(data, this.retentionSeconds); }, - isAnalyticsDisabled () { + isAnalyticsDisabled (eventType) { let stored = storage.getDataFromLocalStorage(this.ANALYTICS_DATA_NAME); if (!isJson(stored)) return false; stored = JSON.parse(stored); if (this.isExpired(stored)) return false; - return stored.disabled; + if (stored.disabled === 1) return true; + else if (stored.disabled === 0) return false; + if (eventType) { + if (stored[`disabled_${eventType}`] === 1) return true; + else if (stored[`disabled_${eventType}`] === 0) return false; + } + return false; }, processServerResponse (response) { if (!isJson(response)) return; diff --git a/test/spec/modules/nobidAnalyticsAdapter_spec.js b/test/spec/modules/nobidAnalyticsAdapter_spec.js index 06a39ffd020..3da334eea97 100644 --- a/test/spec/modules/nobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/nobidAnalyticsAdapter_spec.js @@ -344,7 +344,6 @@ describe('NoBid Prebid Analytic', function () { bidderRequestId: '7c1940bb285731', bids: [ { - bidder: 'nobid', params: { siteId: SITE_ID }, mediaTypes: { banner: { sizes: [[728, 90]] } }, adUnitCode: 'leaderboard', @@ -390,19 +389,28 @@ describe('NoBid Prebid Analytic', function () { const auctionEndRequest = JSON.parse(server.requests[0].requestBody); expect(auctionEndRequest).to.have.property('auctionId', requestOutgoing.auctionId); expect(auctionEndRequest.bidderRequests).to.have.length(1); - expect(auctionEndRequest.bidderRequests[0]).to.have.property('bidderCode', requestOutgoing.bidderRequests[0].bidderCode); + expect(auctionEndRequest.bidderRequests[0].bidderCode).to.equal(requestOutgoing.bidderRequests[0].bidderCode); expect(auctionEndRequest.bidderRequests[0].bids).to.have.length(1); - expect(auctionEndRequest.bidderRequests[0].bids[0]).to.have.property('bidder', requestOutgoing.bidderRequests[0].bids[0].bidder); - expect(auctionEndRequest.bidderRequests[0].bids[0]).to.have.property('adUnitCode', requestOutgoing.bidderRequests[0].bids[0].adUnitCode); - expect(auctionEndRequest.bidderRequests[0].bids[0].params).to.have.property('siteId', requestOutgoing.bidderRequests[0].bids[0].params.siteId); - expect(auctionEndRequest.bidderRequests[0].refererInfo).to.have.property('topmostLocation', requestOutgoing.bidderRequests[0].refererInfo.topmostLocation); + 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(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.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(typeof auctionEndRequest.bidsReceived[0].source).to.equal('undefined'); done(); }); it('Analytics disabled test', function (done) { let disabled; - nobidAnalytics.processServerResponse(JSON.stringify({disabled: false})); + nobidAnalytics.processServerResponse(JSON.stringify({disabled: 0})); disabled = nobidAnalytics.isAnalyticsDisabled(); expect(disabled).to.equal(false); events.emit(constants.EVENTS.AUCTION_END, {auctionId: '1234567890'}); @@ -417,7 +425,7 @@ describe('NoBid Prebid Analytic', function () { clock.tick(1000); expect(server.requests).to.have.length(3); - nobidAnalytics.processServerResponse(JSON.stringify({disabled: true})); + nobidAnalytics.processServerResponse(JSON.stringify({disabled: 1})); disabled = nobidAnalytics.isAnalyticsDisabled(); expect(disabled).to.equal(true); events.emit(constants.EVENTS.AUCTION_END, {auctionId: '12345678902'}); @@ -425,7 +433,7 @@ describe('NoBid Prebid Analytic', function () { expect(server.requests).to.have.length(3); nobidAnalytics.retentionSeconds = 5; - nobidAnalytics.processServerResponse(JSON.stringify({disabled: true})); + nobidAnalytics.processServerResponse(JSON.stringify({disabled: 1})); clock.tick(1000); disabled = nobidAnalytics.isAnalyticsDisabled(); expect(disabled).to.equal(true); @@ -437,6 +445,100 @@ describe('NoBid Prebid Analytic', function () { }); }); + describe('Analytics disabled event type test', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + clock = sinon.useFakeTimers(Date.now()); + }); + + afterEach(function () { + events.getEvents.restore(); + clock.restore(); + }); + + after(function () { + nobidAnalytics.disableAnalytics(); + }); + + it('Analytics disabled event type test', function (done) { + // Initialize adapter + const initOptions = { options: { siteId: SITE_ID } }; + nobidAnalytics.enableAnalytics(initOptions); + adapterManager.enableAnalytics({ provider: 'nobid', options: initOptions }); + + let eventType = constants.EVENTS.AUCTION_END; + let disabled; + nobidAnalytics.processServerResponse(JSON.stringify({disabled: 0})); + disabled = nobidAnalytics.isAnalyticsDisabled(); + expect(disabled).to.equal(false); + events.emit(eventType, {auctionId: '1234567890'}); + clock.tick(1000); + expect(server.requests).to.have.length(1); + events.emit(eventType, {auctionId: '12345678901'}); + clock.tick(1000); + expect(server.requests).to.have.length(2); + + server.requests.length = 0; + expect(server.requests).to.have.length(0); + + nobidAnalytics.processServerResponse(JSON.stringify({disabled_auctionEnd: 1})); + disabled = nobidAnalytics.isAnalyticsDisabled(eventType); + expect(disabled).to.equal(true); + events.emit(eventType, {auctionId: '1234567890'}); + clock.tick(1000); + expect(server.requests).to.have.length(0); + + server.requests.length = 0; + + nobidAnalytics.processServerResponse(JSON.stringify({disabled_auctionEnd: 0})); + disabled = nobidAnalytics.isAnalyticsDisabled(eventType); + expect(disabled).to.equal(false); + events.emit(constants.EVENTS.AUCTION_END, {auctionId: '1234567890'}); + clock.tick(1000); + expect(server.requests).to.have.length(1); + + server.requests.length = 0; + expect(server.requests).to.have.length(0); + + eventType = constants.EVENTS.BID_WON; + nobidAnalytics.processServerResponse(JSON.stringify({disabled_bidWon: 1})); + disabled = nobidAnalytics.isAnalyticsDisabled(eventType); + expect(disabled).to.equal(true); + events.emit(eventType, {bidderCode: 'nobid'}); + clock.tick(1000); + expect(server.requests).to.have.length(0); + + server.requests.length = 0; + expect(server.requests).to.have.length(0); + + eventType = constants.EVENTS.AUCTION_END; + nobidAnalytics.processServerResponse(JSON.stringify({disabled: 1})); + disabled = nobidAnalytics.isAnalyticsDisabled(eventType); + expect(disabled).to.equal(true); + events.emit(eventType, {auctionId: '1234567890'}); + clock.tick(1000); + expect(server.requests).to.have.length(0); + + server.requests.length = 0; + expect(server.requests).to.have.length(0); + + eventType = constants.EVENTS.AUCTION_END; + nobidAnalytics.processServerResponse(JSON.stringify({disabled_auctionEnd: 1, disabled_bidWon: 0})); + disabled = nobidAnalytics.isAnalyticsDisabled(eventType); + expect(disabled).to.equal(true); + events.emit(eventType, {auctionId: '1234567890'}); + clock.tick(1000); + expect(server.requests).to.have.length(0); + disabled = nobidAnalytics.isAnalyticsDisabled(constants.EVENTS.BID_WON); + expect(disabled).to.equal(false); + events.emit(constants.EVENTS.BID_WON, {bidderCode: 'nobid'}); + clock.tick(1000); + expect(server.requests).to.have.length(1); + + done(); + }); + }); + describe('NoBid Carbonizer', function () { beforeEach(function () { sinon.stub(events, 'getEvents').returns([]); @@ -456,7 +558,8 @@ describe('NoBid Prebid Analytic', function () { let active = nobidCarbonizer.isActive(); expect(active).to.equal(false); - active = nobidCarbonizer.isActive(JSON.stringify({carbonizer_active: false})); + nobidAnalytics.processServerResponse(JSON.stringify({carbonizer_active: false})); + active = nobidCarbonizer.isActive(); expect(active).to.equal(false); nobidAnalytics.processServerResponse(JSON.stringify({carbonizer_active: true})); @@ -469,12 +572,12 @@ describe('NoBid Prebid Analytic', function () { let stored = nobidCarbonizer.getStoredLocalData(); expect(stored[nobidAnalytics.ANALYTICS_DATA_NAME]).to.contain(`{"carbonizer_active":true,"ts":`); clock.tick(5000); - active = nobidCarbonizer.isActive(adunits, true); + active = nobidCarbonizer.isActive(); expect(active).to.equal(false); nobidAnalytics.retentionSeconds = previousRetention; nobidAnalytics.processServerResponse(JSON.stringify({carbonizer_active: true})); - active = nobidCarbonizer.isActive(adunits, true); + active = nobidCarbonizer.isActive(); expect(active).to.equal(true); let adunits = [ From e13bd53ab5df06723c36eca8d20b5b7aa544dfd4 Mon Sep 17 00:00:00 2001 From: kidayu <154117971+kida-yuga@users.noreply.github.com> Date: Tue, 13 Feb 2024 02:00:31 +0900 Subject: [PATCH 443/760] Microad Bid Adapter: send gpid and other to our request. (#11076) * Microad Bid Adapter: add gpid and other * Microad Bid Adapter: use deepAccess * Microad Bid Adapter: To simple existing check --------- Co-authored-by: kida-yuga --- modules/microadBidAdapter.js | 20 +++ test/spec/modules/microadBidAdapter_spec.js | 190 ++++++++++++++++++++ 2 files changed, 210 insertions(+) diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index ed88dce757c..61aa9b795de 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -115,6 +115,26 @@ export const spec = { params['aids'] = JSON.stringify(aidsParams) } + const pbadslot = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || pbadslot; + if (gpid) { + params['gpid'] = gpid; + } + + if (pbadslot) { + params['pbadslot'] = pbadslot; + } + + const adservname = deepAccess(bid, 'ortb2Imp.ext.data.adserver.name'); + if (adservname) { + params['adservname'] = adservname; + } + + const adservadslot = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); + if (adservadslot) { + params['adservadslot'] = adservadslot; + } + requests.push({ method: 'GET', url: ENDPOINT_URLS[ENVIRONMENT], diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index bd6d04a6312..9eb36d2fa6c 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -382,6 +382,196 @@ describe('microadBidAdapter', () => { }) }); }) + + describe('should send gpid', () => { + it('from gpid', () => { + const bidRequest = Object.assign({}, bidRequestTemplate, { + ortb2Imp: { + ext: { + tid: 'transaction-id', + gpid: '1111/2222', + data: { + pbadslot: '3333/4444' + } + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + gpid: '1111/2222', + pbadslot: '3333/4444' + }) + ); + }) + }) + + it('from pbadslot', () => { + const bidRequest = Object.assign({}, bidRequestTemplate, { + ortb2Imp: { + ext: { + tid: 'transaction-id', + data: { + pbadslot: '3333/4444' + } + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + gpid: '3333/4444', + pbadslot: '3333/4444' + }) + ); + }) + }) + }) + + const notGettingGpids = { + 'they are not existing': bidRequestTemplate, + 'they are blank': { + ortb2Imp: { + ext: { + tid: 'transaction-id', + gpid: '', + data: { + pbadslot: '' + } + } + } + } + } + + Object.entries(notGettingGpids).forEach(([testTitle, param]) => { + it(`should not send gpid because ${testTitle}`, () => { + const bidRequest = Object.assign({}, bidRequestTemplate, param); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + }) + ); + expect(request.data.gpid).to.be.undefined; + expect(request.data.pbadslot).to.be.undefined; + }) + }) + }) + + it('should send adservname', () => { + const bidRequest = Object.assign({}, bidRequestTemplate, { + ortb2Imp: { + ext: { + tid: 'transaction-id', + data: { + adserver: { + name: 'gam' + } + } + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + adservname: 'gam' + }) + ); + }) + }) + + const notGettingAdservnames = { + 'it is not existing': bidRequestTemplate, + 'it is blank': { + ortb2Imp: { + ext: { + tid: 'transaction-id', + data: { + adserver: { + name: '' + } + } + } + } + } + } + + Object.entries(notGettingAdservnames).forEach(([testTitle, param]) => { + it(`should not send adservname because ${testTitle}`, () => { + const bidRequest = Object.assign({}, bidRequestTemplate, param); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + }) + ); + expect(request.data.adservname).to.be.undefined; + }) + }) + }) + + it('should send adservadslot', () => { + const bidRequest = Object.assign({}, bidRequestTemplate, { + ortb2Imp: { + ext: { + tid: 'transaction-id', + data: { + adserver: { + adslot: '/1111/home' + } + } + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + adservadslot: '/1111/home' + }) + ); + }) + }) + + const notGettingAdservadslots = { + 'it is not existing': bidRequestTemplate, + 'it is blank': { + ortb2Imp: { + ext: { + tid: 'transaction-id', + data: { + adserver: { + adslot: '' + } + } + } + } + } + } + + Object.entries(notGettingAdservadslots).forEach(([testTitle, param]) => { + it(`should not send adservadslot because ${testTitle}`, () => { + const bidRequest = Object.assign({}, bidRequestTemplate, param); + const requests = spec.buildRequests([bidRequest], bidderRequest) + requests.forEach(request => { + expect(request.data).to.deep.equal( + Object.assign({}, expectedResultTemplate, { + cbt: request.data.cbt, + }) + ); + expect(request.data.adservadslot).to.be.undefined; + }) + }) + }) }); describe('interpretResponse', () => { From 55fa1a5c894593779b368d5877fb1583cf176e96 Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Tue, 13 Feb 2024 06:30:22 +1300 Subject: [PATCH 444/760] StroeerCore Bid Adapter: add DSA support (#11083) --- modules/stroeerCoreBidAdapter.js | 11 +++- .../modules/stroeerCoreBidAdapter_spec.js | 60 +++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 307a50c7f78..89ed6995a7e 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -1,4 +1,4 @@ -import { buildUrl, deepAccess, generateUUID, getWindowSelf, getWindowTop, isEmpty, isStr, logWarn } from '../src/utils.js'; +import { buildUrl, deepAccess, deepSetValue, generateUUID, getWindowSelf, getWindowTop, isEmpty, isStr, logWarn } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {find} from '../src/polyfill.js'; @@ -76,6 +76,12 @@ export const spec = { }; } + const DSA_KEY = 'ortb2.regs.ext.dsa'; + const dsa = deepAccess(bidderRequest, DSA_KEY); + if (dsa) { + deepSetValue(basePayload, DSA_KEY, dsa); + } + const bannerBids = validBidRequests .filter(hasBanner) .map(mapToPayloadBannerBid); @@ -108,7 +114,8 @@ export const spec = { netRevenue: true, creativeId: '', meta: { - advertiserDomains: bidResponse.adomain + advertiserDomains: bidResponse.adomain, + dsa: bidResponse.dsa }, mediaType, }; diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 2ed5f80f152..66e0da6ddf8 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -844,6 +844,39 @@ describe('stroeerCore bid adapter', function () { assert.nestedPropertyVal(bid, 'ban.fp.cur', 'EUR'); assert.deepNestedPropertyVal(bid, 'ban.fp.siz', [{w: 160, h: 60, p: 2.7}]); }); + + it('should add the DSA signals', () => { + const bidReq = buildBidderRequest(); + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'testplatform.com', + dsaparams: [1], + }, + { + domain: 'testdomain.com', + dsaparams: [1, 2] + } + ] + } + const ortb2 = { + regs: { + ext: { + dsa + } + } + } + + bidReq.ortb2 = utils.deepClone(ortb2); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + const sentOrtb2 = serverRequestInfo.data.ortb2; + + assert.deepEqual(sentOrtb2, ortb2); + }); }); }); }); @@ -882,13 +915,32 @@ describe('stroeerCore bid adapter', function () { assertStandardFieldsOnVideoBid(videoBidResponse, 'bid1', 'video', 800, 250, 4); }) - it('should add data to meta object', () => { + it('should add advertiser domains to meta object', () => { const response = buildBidderResponse(); response.bids[0] = Object.assign(response.bids[0], {adomain: ['website.org', 'domain.com']}); const result = spec.interpretResponse({body: response}); - assert.deepPropertyVal(result[0], 'meta', {advertiserDomains: ['website.org', 'domain.com']}); - // nothing provided for the second bid - assert.deepPropertyVal(result[1], 'meta', {advertiserDomains: undefined}); + assert.deepPropertyVal(result[0].meta, 'advertiserDomains', ['website.org', 'domain.com']); + assert.propertyVal(result[1].meta, 'advertiserDomains', undefined); + }); + + it('should add dsa info to meta object', () => { + const dsaResponse = { + behalf: 'AdvertiserA', + paid: 'AdvertiserB', + transparency: [{ + domain: 'dspexample.com', + dsaparams: [1, 2], + }], + adrender: 1 + }; + + const response = buildBidderResponse(); + response.bids[0] = Object.assign(response.bids[0], {dsa: utils.deepClone(dsaResponse)}); + + const result = spec.interpretResponse({body: response}); + + assert.deepPropertyVal(result[0].meta, 'dsa', dsaResponse); + assert.propertyVal(result[1].meta, 'dsa', undefined); }); }); From aa092c03b57ea5fff5c5a06c2f5d7cc97d371858 Mon Sep 17 00:00:00 2001 From: Christophe Oudar <90898980+github-christophe-oudar@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:00:56 +0100 Subject: [PATCH 445/760] Teads adapter: add dsa info support in bid request & response (#11080) --- modules/teadsBidAdapter.js | 8 +++ test/spec/modules/teadsBidAdapter_spec.js | 63 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 1c12b0e3968..d03782611e4 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -101,6 +101,11 @@ export const spec = { payload.userAgentClientHints = userAgentClientHints; } + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + payload.dsa = dsa; + } + const payloadString = JSON.stringify(payload); return { method: 'POST', @@ -138,6 +143,9 @@ export const spec = { if (bid.dealId) { bidResponse.dealId = bid.dealId } + if (bid?.ext?.dsa) { + bidResponse.meta.dsa = bid.ext.dsa; + } bidResponses.push(bidResponse); }); } diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 98f706f5193..f26081b0cef 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -1005,6 +1005,45 @@ describe('teadsBidAdapter', () => { } }); } + + it('should add dsa info to payload if available', function () { + const bidRequestWithDsa = Object.assign({}, bidderRequestDefault, { + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + } + } + } + }); + + const requestWithDsa = spec.buildRequests(bidRequests, bidRequestWithDsa); + const payload = JSON.parse(requestWithDsa.data); + + expect(payload.dsa).to.exist; + expect(payload.dsa).to.deep.equal( + { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + ); + + const defaultRequest = spec.buildRequests(bidRequests, bidderRequestDefault); + expect(JSON.parse(defaultRequest.data).dsa).to.not.exist; + }); }); describe('interpretResponse', function() { @@ -1031,7 +1070,18 @@ describe('teadsBidAdapter', () => { 'width': 350, 'creativeId': 'fs3ff', 'placementId': 34, - 'dealId': 'ABC_123' + 'dealId': 'ABC_123', + 'ext': { + 'dsa': { + 'behalf': 'some-behalf', + 'paid': 'some-paid', + 'transparency': [{ + 'domain': 'test.com', + 'dsaparams': [1, 2, 3] + }], + 'adrender': 1 + } + } }] } }; @@ -1057,7 +1107,16 @@ describe('teadsBidAdapter', () => { 'currency': 'USD', 'netRevenue': true, 'meta': { - advertiserDomains: [] + advertiserDomains: [], + dsa: { + behalf: 'some-behalf', + paid: 'some-paid', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }], + adrender: 1 + } }, 'ttl': 360, 'ad': AD_SCRIPT, From 0862952985f5fbf24d7508f036419ade8115cdc0 Mon Sep 17 00:00:00 2001 From: Rich Audience Date: Tue, 13 Feb 2024 18:03:39 +0100 Subject: [PATCH 446/760] Richaudience Bid Adapter : add compatibility to GPP (#11022) * RichaudienceBidAdapter add function onTimeout * Add unit test * revert: Revert changes in integrationExamples/creative.html * fix: Remove useless package in richaudiences test module * Change referer with host * Fix(RichaudienceBidAdapter): Change url tracking * deploy * change test * remove change others adapters * feat(RichaudienceBidAdapter): Add compatibility to GPP * fix(RichaudienceBidAdapter): Add test to GPP * fix(RichaudienceBidAdapter): Add test to GPP * fix(RichaudienceBidAdapter): Change tmax/timeout hardcoded #9787 --------- Co-authored-by: Sergi Gimenez --- modules/richaudienceBidAdapter.js | 31 +++++++++++-- .../modules/richaudienceBidAdapter_spec.js | 43 ++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js index 36513aeda47..b63e31266fb 100755 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -49,7 +49,7 @@ export const spec = { referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.ortb2Imp?.ext?.tid, - timeout: config.getConfig('bidderTimeout'), + timeout: bidderRequest.timeout || 600, user: raiSetEids(bid), demand: raiGetDemandType(bid), videoData: raiGetVideoInfo(bid), @@ -75,6 +75,18 @@ export const spec = { } } + if (bidderRequest?.gppConsent) { + payload.privacy = { + gpp: bidderRequest.gppConsent.gppString, + gpp_sid: bidderRequest.gppConsent.applicableSections + } + } else if (bidderRequest?.ortb2?.regs?.gpp) { + payload.privacy = { + gpp: bidderRequest.ortb2.regs.gpp, + gpp_sid: bidderRequest.ortb2.regs.gpp_sid + } + } + var payloadString = JSON.stringify(payload); var endpoint = 'https://shb.richaudience.com/hb/'; @@ -145,12 +157,13 @@ export const spec = { * @param {gdprConsent} GPDR consent object * @returns {Array} */ - getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { const syncs = []; var rand = Math.floor(Math.random() * 9999999999); var syncUrl = ''; var consent = ''; + var consentGPP = ''; var raiSync = {}; @@ -160,11 +173,20 @@ export const spec = { consent = `consentString=${gdprConsent.consentString}` } + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + consentGPP = 'gpp=' + encodeURIComponent(gppConsent.gppString); + consentGPP += '&gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + if (syncOptions.iframeEnabled && raiSync.raiIframe != 'exclude') { syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand if (consent != '') { syncUrl += `&${consent}` } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } syncs.push({ type: 'iframe', url: syncUrl @@ -176,6 +198,9 @@ export const spec = { if (consent != '') { syncUrl += `&${consent}` } + if (consentGPP != '') { + syncUrl += `&${consentGPP}` + } syncs.push({ type: 'image', url: syncUrl @@ -346,7 +371,7 @@ function raiGetTimeoutURL(data) { url = url.replace('[timeout_publisher]', timeout) url = url.replace('[placement_hash]', params[0].pid) - if (REFERER != null) { + if (document.location.host != null) { url = url.replace('[domain]', document.location.host) } return url diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 20c60ca328a..d2b173f53df 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -293,7 +293,7 @@ describe('Richaudience adapter tests', function () { expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); - expect(requestContent).to.have.property('timeout').and.to.equal(3000); + expect(requestContent).to.have.property('timeout').and.to.equal(600); expect(requestContent).to.have.property('numIframes').and.to.equal(0); expect(typeof requestContent.scr_rsl === 'string') expect(typeof requestContent.cpuc === 'number') @@ -916,7 +916,7 @@ describe('Richaudience adapter tests', function () { it('onTimeout exist as a function', () => { expect(spec.onTimeout).to.exist.and.to.be.a('function'); }); - it('should send timeout', function () { + it('should send timeouts', function () { spec.onTimeout(DEFAULT_PARAMS_VIDEO_TIMEOUT); expect(utils.triggerPixel.called).to.equal(true); expect(utils.triggerPixel.firstCall.args[0]).to.equal('https://s.richaudience.com/err/?ec=6&ev=3000&pla=ADb1f40rmi&int=PREBID&pltfm=&node=&dm=localhost:9876'); @@ -924,6 +924,13 @@ describe('Richaudience adapter tests', function () { }); describe('userSync', function () { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }); it('Verifies user syncs iframe include', function () { config.setConfig({ 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} @@ -1261,5 +1268,37 @@ describe('Richaudience adapter tests', function () { }, [], {consentString: '', gdprApplies: true}); expect(syncs).to.have.lengthOf(0); }); + + it('Verifies user syncs iframe/image include with GPP', function () { + config.setConfig({ + 'userSync': {filterSettings: {iframe: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({iframeEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + + config.setConfig({ + 'userSync': {filterSettings: {image: {bidders: '*', filter: 'include'}}} + }) + + var syncs = spec.getUserSyncs({pixelEnabled: true}, [BID_RESPONSE], { + gppString: 'DBABL~BVVqAAEABgA.QA', + applicableSections: [7, 5]}, + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('Verifies user syncs URL image include with GPP', function () { + const gppConsent = { gppString: 'DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA', applicableSections: [0] }; + const result = spec.getUserSyncs({pixelEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?referrer=http%3A%2F%2Fdomain.com&gpp=DBACMYA~CP5P4cAP5P4cAPoABAESAlEAAAAAAAAAAAAAA2QAQA2ADZABADYAAAAA.QA2QAQA2AAAA.IA2QAQA2AAAA~BP5P4cAP5P4cAPoABABGBACAAAAAAAAAAAAAAAAAAA.YAAAAAAAAAA&gpp_sid=0` + }]); + }); }) }); From 5e7b34b7c68542fe79852e40b127eec4d30b2b67 Mon Sep 17 00:00:00 2001 From: Brian Schmidt Date: Tue, 13 Feb 2024 13:54:42 -0800 Subject: [PATCH 447/760] add OpenX topics iframe (#11039) --- modules/topicsFpdModule.js | 5 ++++- modules/topicsFpdModule.md | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index dd240a929a7..f07ab0c7c14 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -22,13 +22,16 @@ export function reset() { } const bidderIframeList = { - maxTopicCaller: 2, + maxTopicCaller: 4, bidders: [{ bidder: 'pubmatic', iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' }, { bidder: 'rtbhouse', iframeURL: 'https://topics.authorizedvault.com/topicsapi.html' + }, { + bidder: 'openx', + iframeURL: 'https://pa.openx.net/topics_frame.html' }, { bidder: 'improvedigital', iframeURL: 'https://hb.360yield.com/privacy-sandbox/topics.html' diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index 6ef0bf241dd..a6bb14dca67 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -40,6 +40,10 @@ pbjs.setConfig({ bidder: 'rtbhouse', iframeURL: 'https://topics.authorizedvault.com/topicsapi.html', expiry: 7 // Configurable expiry days + },{ + bidder: 'openx', + iframeURL: 'https://pa.openx.net/topics_frame.html', + expiry: 7 // Configurable expiry days },{ bidder: 'rubicon', iframeURL: 'https://rubicon.com:8080/topics/fpd/topic.html', // dummy URL From 6eeb99eb289db02443d9e8e208bcd32341f89a33 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 14 Feb 2024 14:02:21 +0100 Subject: [PATCH 448/760] =?UTF-8?q?=E2=9C=A8=20add=20sellerCurrency=20to?= =?UTF-8?q?=20fledge=20auction=20config=20for=20criteo=20bid=20adapter=20(?= =?UTF-8?q?#11084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 4 +--- test/spec/modules/criteoBidAdapter_spec.js | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index fcf8d2ad953..40d39dc5618 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -298,9 +298,6 @@ export const spec = { if (!sellerSignals.floor && bidRequest.params.bidFloor) { sellerSignals.floor = bidRequest.params.bidFloor; } - if (!sellerSignals.sellerCurrency && bidRequest.params.bidFloorCur) { - sellerSignals.sellerCurrency = bidRequest.params.bidFloorCur; - } if (body?.ext?.sellerSignalsPerImp !== undefined) { const sellerSignalsPerImp = body.ext.sellerSignalsPerImp[bidId]; if (sellerSignalsPerImp !== undefined) { @@ -317,6 +314,7 @@ export const spec = { auctionSignals: {}, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, interestGroupBuyers: Object.keys(perBuyerSignals), + sellerCurrency: sellerSignals.currency || '???', }, }); }); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index ce07c6e49bc..4c599550afb 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2576,7 +2576,8 @@ describe('The Criteo bidding adapter', function () { sellerSignalsPerImp: { 'test-bidId': { foo2: 'bar2', - } + currency: 'USD' + }, }, }, }, @@ -2646,8 +2647,9 @@ describe('The Criteo bidding adapter', function () { foo: 'bar', foo2: 'bar2', floor: 1, - sellerCurrency: 'EUR', + currency: 'USD', }, + sellerCurrency: 'USD' }, }); expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ @@ -2669,8 +2671,8 @@ describe('The Criteo bidding adapter', function () { sellerSignals: { foo: 'bar', floor: 1, - sellerCurrency: 'EUR', }, + sellerCurrency: '???' }, }); }); From 7fbe4e9b98144982e330c9c108031693fdc0242d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Wed, 14 Feb 2024 14:25:23 +0100 Subject: [PATCH 449/760] greenbids Analytics Adapter: fix double sampling bug (#11090) * greenbidsAnalyticsAdapter: fix double sampling bug * greenbidsAnalyticsAdapter bump version --- modules/greenbidsAnalyticsAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 5d1f35f24ff..9b23dba01b9 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -6,7 +6,7 @@ import {deepClone, generateUUID, logError, logInfo, logWarn} from '../src/utils. const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.0.0'; +export const ANALYTICS_VERSION = '2.1.0'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -33,7 +33,6 @@ export const isSampled = function(greenbidsId, samplingRate) { return true; } const hashInt = parseInt(greenbidsId.slice(-4), 16); - return hashInt < samplingRate * (0xFFFF + 1); } @@ -59,8 +58,6 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER if (typeof analyticsOptions.options.sampling === 'number') { logWarn('"options.sampling" is deprecated, please use "greenbidsSampling" instead.'); analyticsOptions.options.greenbidsSampling = analyticsOptions.options.sampling; - // Set sampling to null to prevent prebid analytics integrated sampling to happen - analyticsOptions.options.sampling = null; } /** @@ -228,6 +225,10 @@ greenbidsAnalyticsAdapter.originEnableAnalytics = greenbidsAnalyticsAdapter.enab greenbidsAnalyticsAdapter.enableAnalytics = function(config) { this.initConfig(config); + if (typeof config.options.sampling === 'number') { + // Set sampling to 1 to prevent prebid analytics integrated sampling to happen + config.options.sampling = 1; + } logInfo('loading greenbids analytics'); greenbidsAnalyticsAdapter.originEnableAnalytics(config); }; From 8df7a80feefc335207e41480444ac373a6a42376 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Wed, 14 Feb 2024 16:25:39 +0100 Subject: [PATCH 450/760] mediasquare Bid Adapter: minor change with floors (#11100) --- modules/mediasquareBidAdapter.js | 2 ++ test/spec/modules/mediasquareBidAdapter_spec.js | 1 + 2 files changed, 3 insertions(+) diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js index 1ec05d17eef..a84c19b786b 100644 --- a/modules/mediasquareBidAdapter.js +++ b/modules/mediasquareBidAdapter.js @@ -62,6 +62,8 @@ export const spec = { if (tmpFloor != {}) { floor[value.join('x')] = tmpFloor; } }); } + let tmpFloor = adunitValue.getFloor({currency: 'USD', mediaType: '*', size: '*'}); + if (tmpFloor != {}) { floor['*'] = tmpFloor; } } codes.push({ owner: adunitValue.params.owner, diff --git a/test/spec/modules/mediasquareBidAdapter_spec.js b/test/spec/modules/mediasquareBidAdapter_spec.js index 6082ef65055..cdeae38aa19 100644 --- a/test/spec/modules/mediasquareBidAdapter_spec.js +++ b/test/spec/modules/mediasquareBidAdapter_spec.js @@ -173,6 +173,7 @@ describe('MediaSquare bid adapter tests', function () { const responsefloor = JSON.parse(requestfloor.data); expect(responsefloor.codes[0]).to.have.property('floor').exist; expect(responsefloor.codes[0].floor).to.have.property('300x250').and.to.have.property('floor').and.to.equal(1); + expect(responsefloor.codes[0].floor).to.have.property('*'); }); it('Verify parse response', function () { From b6276873d507a532835623da84e376046a569aba Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 14 Feb 2024 10:01:11 -0800 Subject: [PATCH 451/760] PAAPI/fledgeForGpt: make auction configs available independently from GPT (#10930) * paapi module * fledgeForGpt/paapi split and config aliases * Add reuse = false option and GPT slot reset * simpler log messages * fix reuse * refactory reset logic * remove reuse option, treat auction configs as single-use * do not do global reset if called with auction filter * at auction end autoconfig, reset all slots involved in the auction * includeBlanks * use includeBlanks from fledgeForGpt --- integrationExamples/gpt/fledge_example.html | 15 +- modules/.submodules.json | 3 + modules/fledgeForGpt.js | 243 +++----- modules/paapi.js | 241 ++++++++ test/helpers/indexStub.js | 4 +- test/spec/modules/fledgeForGpt_spec.js | 506 ++++------------ test/spec/modules/paapi_spec.js | 628 ++++++++++++++++++++ 7 files changed, 1101 insertions(+), 539 deletions(-) create mode 100644 modules/paapi.js create mode 100644 test/spec/modules/paapi_spec.js diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/fledge_example.html index 5059e03daef..5a6ab7a5fef 100644 --- a/integrationExamples/gpt/fledge_example.html +++ b/integrationExamples/gpt/fledge_example.html @@ -44,15 +44,11 @@ pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { - enabled: true - } - }); - - pbjs.setBidderConfig({ - bidders: ['openx'], - config: { - fledgeEnabled: true + paapi: { + enabled: true, + gpt: { + autoconfig: false + } } }); @@ -69,6 +65,7 @@ googletag.cmd.push(function() { pbjs.que.push(function() { pbjs.setTargetingForGPTAsync(); + pbjs.setPAAPIConfigForGPT(); googletag.pubads().refresh(); }); }); diff --git a/modules/.submodules.json b/modules/.submodules.json index d2a13a57330..61d8c843d47 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -102,6 +102,9 @@ "videoModule": [ "jwplayerVideoProvider", "videojsVideoProvider" + ], + "paapi": [ + "fledgeForGpt" ] } } diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 7162f4e9230..7e841234e24 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -1,169 +1,104 @@ /** - * Fledge modules is responsible for registering fledged auction configs into the GPT slot; - * GPT is resposible to run the fledge auction. + * GPT-specific slot configuration logic for PAAPI. */ -import { config } from '../src/config.js'; -import { getHook } from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js'; -import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; -import * as events from '../src/events.js' -import CONSTANTS from '../src/constants.json'; -import {currencyCompare} from '../libraries/currencyUtils/currency.js'; -import {maximum, minimum} from '../src/utils/reducers.js'; +import {submodule} from '../src/hook.js'; +import {deepAccess, logInfo, logWarn} from '../src/utils.js'; import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; - -const MODULE = 'fledgeForGpt' -const PENDING = {}; - -export let isEnabled = false; - -config.getConfig('fledgeForGpt', config => init(config.fledgeForGpt)); - -/** - * Module init. - */ -export function init(cfg) { - if (cfg && cfg.enabled === true) { - if (!isEnabled) { - getHook('addComponentAuction').before(addComponentAuctionHook); - getHook('makeBidRequests').after(markForFledge); - events.on(CONSTANTS.EVENTS.AUCTION_INIT, onAuctionInit); - events.on(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); - isEnabled = true; - } - logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} fledge)`, cfg); - } else { - if (isEnabled) { - getHook('addComponentAuction').getHooks({hook: addComponentAuctionHook}).remove(); - getHook('makeBidRequests').getHooks({hook: markForFledge}).remove() - events.off(CONSTANTS.EVENTS.AUCTION_INIT, onAuctionInit); - events.off(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); - isEnabled = false; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + +const MODULE = 'fledgeForGpt'; + +let getPAAPIConfig; + +// for backwards compat, we attempt to automatically set GPT configuration as soon as we +// have the auction configs available. Disabling this allows one to call pbjs.setPAAPIConfigForGPT at their +// own pace. +let autoconfig = true; + +Object.entries({ + [MODULE]: MODULE, + 'paapi': 'paapi.gpt' +}).forEach(([topic, ns]) => { + const configKey = `${ns}.autoconfig`; + config.getConfig(topic, (cfg) => { + autoconfig = deepAccess(cfg, configKey, true); + }); +}); + +export function slotConfigurator() { + const PREVIOUSLY_SET = {}; + return function setComponentAuction(adUnitCode, auctionConfigs, reset = true) { + const gptSlot = getGptSlotForAdUnitCode(adUnitCode); + if (gptSlot && gptSlot.setConfig) { + let previous = PREVIOUSLY_SET[adUnitCode] ?? {}; + let configsBySeller = Object.fromEntries(auctionConfigs.map(cfg => [cfg.seller, cfg])); + const sellers = Object.keys(configsBySeller); + if (reset) { + configsBySeller = Object.assign(previous, configsBySeller); + previous = Object.fromEntries(sellers.map(seller => [seller, null])); + } else { + sellers.forEach(seller => { + previous[seller] = null; + }); + } + Object.keys(previous).length ? PREVIOUSLY_SET[adUnitCode] = previous : delete PREVIOUSLY_SET[adUnitCode]; + const componentAuction = Object.entries(configsBySeller) + .map(([configKey, auctionConfig]) => ({configKey, auctionConfig})); + if (componentAuction.length > 0) { + gptSlot.setConfig({componentAuction}); + logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); + } + } else if (auctionConfigs.length > 0) { + logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); } - logInfo(`${MODULE} disabled`, cfg); - } -} - -function setComponentAuction(adUnitCode, auctionConfigs) { - const gptSlot = getGptSlotForAdUnitCode(adUnitCode); - if (gptSlot && gptSlot.setConfig) { - gptSlot.setConfig({ - componentAuction: auctionConfigs.map(cfg => ({ - configKey: cfg.seller, - auctionConfig: cfg - })) - }); - logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); - } else { - logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); - } + }; } -function onAuctionInit({auctionId}) { - PENDING[auctionId] = {}; -} +const setComponentAuction = slotConfigurator(); -function getSlotSignals(bidsReceived = [], bidRequests = []) { - let bidfloor, bidfloorcur; - if (bidsReceived.length > 0) { - const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); - bidfloor = bestBid.cpm; - bidfloorcur = bestBid.currency; - } else { - const floors = bidRequests.map(bid => typeof bid.getFloor === 'function' && bid.getFloor()).filter(f => f); - const minFloor = floors.length && floors.reduce(minimum(currencyCompare(floor => [floor.floor, floor.currency]))) - bidfloor = minFloor?.floor; - bidfloorcur = minFloor?.currency; - } - const cfg = {}; - if (bidfloor) { - deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); - bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); +export function onAuctionConfigFactory(setGptConfig = setComponentAuction) { + return function onAuctionConfig(auctionId, configsByAdUnit, markAsUsed) { + if (autoconfig) { + Object.entries(configsByAdUnit).forEach(([adUnitCode, cfg]) => { + setGptConfig(adUnitCode, cfg?.componentAuctions ?? []); + markAsUsed(adUnitCode); + }); + } } - return cfg; } -function onAuctionEnd({auctionId, bidsReceived, bidderRequests}) { - try { - const allReqs = bidderRequests?.flatMap(br => br.bids); - Object.entries(PENDING[auctionId]).forEach(([adUnitCode, auctionConfigs]) => { - const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); - setComponentAuction(adUnitCode, auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg))) +export function setPAAPIConfigFactory( + getConfig = (filters) => getPAAPIConfig(filters, true), + setGptConfig = setComponentAuction) { + /** + * Configure GPT slots with PAAPI auction configs. + * `filters` are the same filters accepted by `pbjs.getPAAPIConfig`; + */ + return function(filters = {}) { + let some = false; + Object.entries( + getConfig(filters) || {} + ).forEach(([au, config]) => { + if (config != null) { + some = true; + } + setGptConfig(au, config?.componentAuctions || [], true); }) - } finally { - delete PENDING[auctionId]; - } -} - -function setFPDSignals(auctionConfig, fpd) { - auctionConfig.auctionSignals = mergeDeep({}, {prebid: fpd}, auctionConfig.auctionSignals); -} - -export function addComponentAuctionHook(next, request, componentAuctionConfig) { - const {adUnitCode, auctionId, ortb2, ortb2Imp} = request; - if (PENDING.hasOwnProperty(auctionId)) { - setFPDSignals(componentAuctionConfig, {ortb2, ortb2Imp}); - !PENDING[auctionId].hasOwnProperty(adUnitCode) && (PENDING[auctionId][adUnitCode] = []); - PENDING[auctionId][adUnitCode].push(componentAuctionConfig); - } else { - logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig) - } - next(request, componentAuctionConfig); -} - -function isFledgeSupported() { - return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator -} - -export function markForFledge(next, bidderRequests) { - if (isFledgeSupported()) { - const globalFledgeConfig = config.getConfig('fledgeForGpt'); - const bidders = globalFledgeConfig?.bidders ?? []; - bidderRequests.forEach((bidderReq) => { - const useGlobalConfig = globalFledgeConfig?.enabled && (bidders.length === 0 || bidders.includes(bidderReq.bidderCode)); - config.runWithBidder(bidderReq.bidderCode, () => { - const fledgeEnabled = config.getConfig('fledgeEnabled') ?? (useGlobalConfig ? globalFledgeConfig.enabled : undefined); - const defaultForSlots = config.getConfig('defaultForSlots') ?? (useGlobalConfig ? globalFledgeConfig?.defaultForSlots : undefined); - Object.assign(bidderReq, {fledgeEnabled}); - bidderReq.bids.forEach(bidReq => { deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidReq.ortb2Imp?.ext?.ae ?? defaultForSlots) }) - }) - }); - } - next(bidderRequests); -} - -export function setImpExtAe(imp, bidRequest, context) { - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { - delete imp.ext?.ae; - } -} -registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); - -// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up -// fledge response processing in two steps: first aggregate all the auction configs by their imp... - -export function parseExtPrebidFledge(response, ortbResponse, context) { - (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { - const impCtx = context.impContext[cfg.impid]; - if (!impCtx?.imp?.ext?.ae) { - logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); - } else { - impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; - impCtx.fledgeConfigs.push(cfg); + if (!some) { + logInfo(`${MODULE}: No component auctions available to set`); } - }) + } } -registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); - -// ...then, make them available in the adapter's response. This is the client side version, for which the -// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} +/** + * Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`. + */ +getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory(); -export function setResponseFledgeConfigs(response, ortbResponse, context) { - const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({bidId: impCtx.bidRequest.bidId, config: cfg.config}))); - if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; +submodule('paapi', { + name: 'gpt', + onAuctionConfig: onAuctionConfigFactory(), + init(params) { + getPAAPIConfig = params.getPAAPIConfig; } -} -registerOrtbProcessor({type: RESPONSE, name: 'fledgeAuctionConfigs', priority: -1, fn: setResponseFledgeConfigs, dialects: [PBS]}) +}); diff --git a/modules/paapi.js b/modules/paapi.js new file mode 100644 index 00000000000..720935bd3f5 --- /dev/null +++ b/modules/paapi.js @@ -0,0 +1,241 @@ +/** + * Collect PAAPI component auction configs from bid adapters and make them available through `pbjs.getPAAPIConfig()` + */ +import {config} from '../src/config.js'; +import {getHook, module} from '../src/hook.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; +import * as events from '../src/events.js'; +import CONSTANTS from '../src/constants.json'; +import {currencyCompare} from '../libraries/currencyUtils/currency.js'; +import {maximum, minimum} from '../src/utils/reducers.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + +const MODULE = 'PAAPI'; + +const submodules = []; +const USED = new WeakSet(); + +export function registerSubmodule(submod) { + submodules.push(submod); + submod.init && submod.init({getPAAPIConfig}); +} + +module('paapi', registerSubmodule); + +function auctionConfigs() { + const store = new WeakMap(); + return function (auctionId, init = {}) { + const auction = auctionManager.index.getAuction({auctionId}); + if (auction == null) return; + if (!store.has(auction)) { + store.set(auction, init); + } + return store.get(auction); + }; +} + +const pendingForAuction = auctionConfigs(); +const configsForAuction = auctionConfigs(); +let latestAuctionForAdUnit = {}; +let moduleConfig = {}; + +['paapi', 'fledgeForGpt'].forEach(ns => { + config.getConfig(ns, config => { + init(config[ns], ns); + }); +}); + +export function reset() { + submodules.splice(0, submodules.length); + latestAuctionForAdUnit = {}; +} + +export function init(cfg, configNamespace) { + if (configNamespace !== 'paapi') { + logWarn(`'${configNamespace}' configuration options will be renamed to 'paapi'; consider using setConfig({paapi: [...]}) instead`); + } + if (cfg && cfg.enabled === true) { + moduleConfig = cfg; + logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); + } else { + moduleConfig = {}; + logInfo(`${MODULE} disabled`, cfg); + } +} + +getHook('addComponentAuction').before(addComponentAuctionHook); +getHook('makeBidRequests').after(markForFledge); +events.on(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); + +function getSlotSignals(bidsReceived = [], bidRequests = []) { + let bidfloor, bidfloorcur; + if (bidsReceived.length > 0) { + const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); + bidfloor = bestBid.cpm; + bidfloorcur = bestBid.currency; + } else { + const floors = bidRequests.map(bid => typeof bid.getFloor === 'function' && bid.getFloor()).filter(f => f); + const minFloor = floors.length && floors.reduce(minimum(currencyCompare(floor => [floor.floor, floor.currency]))); + bidfloor = minFloor?.floor; + bidfloorcur = minFloor?.currency; + } + const cfg = {}; + if (bidfloor) { + deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); + bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); + } + return cfg; +} + +function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes}) { + const allReqs = bidderRequests?.flatMap(br => br.bids); + const paapiConfigs = {}; + (adUnitCodes || []).forEach(au => { + paapiConfigs[au] = null; + !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); + }) + Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => { + const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; + const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); + paapiConfigs[adUnitCode] = { + componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) + }; + latestAuctionForAdUnit[adUnitCode] = auctionId; + }); + configsForAuction(auctionId, paapiConfigs); + submodules.forEach(submod => submod.onAuctionConfig?.( + auctionId, + paapiConfigs, + (adUnitCode) => paapiConfigs[adUnitCode] != null && USED.add(paapiConfigs[adUnitCode])) + ); +} + +function setFPDSignals(auctionConfig, fpd) { + auctionConfig.auctionSignals = mergeDeep({}, {prebid: fpd}, auctionConfig.auctionSignals); +} + +export function addComponentAuctionHook(next, request, componentAuctionConfig) { + if (getFledgeConfig().enabled) { + const {adUnitCode, auctionId, ortb2, ortb2Imp} = request; + const configs = pendingForAuction(auctionId); + if (configs != null) { + setFPDSignals(componentAuctionConfig, {ortb2, ortb2Imp}); + !configs.hasOwnProperty(adUnitCode) && (configs[adUnitCode] = []); + configs[adUnitCode].push(componentAuctionConfig); + } else { + logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig); + } + } + next(request, componentAuctionConfig); +} + +/** + * Get PAAPI auction configuration. + * + * @param auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used + * @param adUnitCode? optional ad unit filter + * @param includeBlanks if true, include null entries for ad units that match the given filters but do not have any available auction configs. + * @returns {{}} a map from ad unit code to auction config for the ad unit. + */ +export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = false) { + const output = {}; + const targetedAuctionConfigs = auctionId && configsForAuction(auctionId); + Object.keys((auctionId != null ? targetedAuctionConfigs : latestAuctionForAdUnit) ?? []).forEach(au => { + const latestAuctionId = latestAuctionForAdUnit[au]; + const auctionConfigs = targetedAuctionConfigs ?? (latestAuctionId && configsForAuction(latestAuctionId)); + if ((adUnitCode ?? au) === au) { + let candidate; + if (targetedAuctionConfigs?.hasOwnProperty(au)) { + candidate = targetedAuctionConfigs[au]; + } else if (auctionId == null && auctionConfigs?.hasOwnProperty(au)) { + candidate = auctionConfigs[au]; + } + if (candidate && !USED.has(candidate)) { + output[au] = candidate; + USED.add(candidate); + } else if (includeBlanks) { + output[au] = null; + } + } + }) + return output; +} + +getGlobal().getPAAPIConfig = (filters) => getPAAPIConfig(filters); + +function isFledgeSupported() { + return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator; +} + +function getFledgeConfig() { + const bidder = config.getCurrentBidder(); + const useGlobalConfig = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); + return { + enabled: config.getConfig('fledgeEnabled') ?? useGlobalConfig, + ae: config.getConfig('defaultForSlots') ?? (useGlobalConfig ? moduleConfig.defaultForSlots : undefined) + }; +} + +export function markForFledge(next, bidderRequests) { + if (isFledgeSupported()) { + bidderRequests.forEach((bidderReq) => { + config.runWithBidder(bidderReq.bidderCode, () => { + const {enabled, ae} = getFledgeConfig(); + Object.assign(bidderReq, {fledgeEnabled: enabled}); + bidderReq.bids.forEach(bidReq => { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidReq.ortb2Imp?.ext?.ae ?? ae); + }); + }); + }); + } + next(bidderRequests); +} + +export function setImpExtAe(imp, bidRequest, context) { + if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { + delete imp.ext?.ae; + } +} + +registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); + +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { + const impCtx = context.impContext[cfg.impid]; + if (!impCtx?.imp?.ext?.ae) { + logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + } else { + impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; + impCtx.fledgeConfigs.push(cfg); + } + }); +} + +registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); + +// ...then, make them available in the adapter's response. This is the client side version, for which the +// interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} + +export function setResponseFledgeConfigs(response, ortbResponse, context) { + const configs = Object.values(context.impContext) + .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({ + bidId: impCtx.bidRequest.bidId, + config: cfg.config + }))); + if (configs.length > 0) { + response.fledgeAuctionConfigs = configs; + } +} + +registerOrtbProcessor({ + type: RESPONSE, + name: 'fledgeAuctionConfigs', + priority: -1, + fn: setResponseFledgeConfigs, + dialects: [PBS] +}); diff --git a/test/helpers/indexStub.js b/test/helpers/indexStub.js index 2916b60ae32..5202106c9cf 100644 --- a/test/helpers/indexStub.js +++ b/test/helpers/indexStub.js @@ -1,6 +1,6 @@ import {AuctionIndex} from '../../src/auctionIndex.js'; -export function stubAuctionIndex({bidRequests, bidderRequests, adUnits}) { +export function stubAuctionIndex({bidRequests, bidderRequests, adUnits, auctionId = 'mock-auction'}) { if (adUnits == null) { adUnits = [] } @@ -15,7 +15,7 @@ export function stubAuctionIndex({bidRequests, bidderRequests, adUnits}) { } const auction = { getAuctionId() { - return 'mock-auction' + return auctionId; }, getBidRequests() { return bidderRequests; diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js index 60a8e196ae0..8ab11171121 100644 --- a/test/spec/modules/fledgeForGpt_spec.js +++ b/test/spec/modules/fledgeForGpt_spec.js @@ -1,419 +1,177 @@ -import { - expect -} from 'chai'; -import * as fledge from 'modules/fledgeForGpt.js'; -import {config} from '../../../src/config.js'; -import adapterManager from '../../../src/adapterManager.js'; -import * as utils from '../../../src/utils.js'; +import {onAuctionConfigFactory, setPAAPIConfigFactory, slotConfigurator} from 'modules/fledgeForGpt.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; -import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; -import {parseExtPrebidFledge, setImpExtAe, setResponseFledgeConfigs} from 'modules/fledgeForGpt.js'; -import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; -import {getGlobal} from '../../../src/prebidGlobal.js'; +import {deepSetValue} from '../../../src/utils.js'; +import {config} from 'src/config.js'; describe('fledgeForGpt module', () => { - let sandbox; + let sandbox, fledgeAuctionConfig; beforeEach(() => { sandbox = sinon.sandbox.create(); + fledgeAuctionConfig = { + seller: 'bidder', + mock: 'config' + }; }); afterEach(() => { sandbox.restore(); }); - describe('addComponentAuction', function () { - before(() => { - fledge.init({enabled: true}); - }); - const fledgeAuctionConfig = { - seller: 'bidder', - mock: 'config' - }; - - describe('addComponentAuctionHook', function () { - let nextFnSpy, mockGptSlot; - beforeEach(function () { - nextFnSpy = sinon.spy(); - mockGptSlot = { - setConfig: sinon.stub(), - getAdUnitPath: () => 'mock/gpt/au' - }; - sandbox.stub(gptUtils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); - }); - - it('should call next()', function () { - const request = {auctionId: 'aid', adUnitCode: 'auc'}; - fledge.addComponentAuctionHook(nextFnSpy, request, fledgeAuctionConfig); - sinon.assert.calledWith(nextFnSpy, request, fledgeAuctionConfig); + describe('slotConfigurator', () => { + let mockGptSlot, setGptConfig; + beforeEach(() => { + mockGptSlot = { + setConfig: sinon.stub(), + getAdUnitPath: () => 'mock/gpt/au' + }; + sandbox.stub(gptUtils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); + setGptConfig = slotConfigurator(); + }); + it('should set GPT slot config', () => { + setGptConfig('au', [fledgeAuctionConfig]); + sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); + sinon.assert.calledWith(mockGptSlot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: fledgeAuctionConfig, + }] }); + }); - it('should collect auction configs and route them to GPT at end of auction', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); - const cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; - const cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; - fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au1'}, cf1); - fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au2'}, cf2); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au1'); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au2'); + describe('when reset = true', () => { + it('should reset GPT slot config', () => { + setGptConfig('au', [fledgeAuctionConfig]); + mockGptSlot.setConfig.resetHistory(); + gptUtils.getGptSlotForAdUnitCode.resetHistory(); + setGptConfig('au', [], true); + sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); sinon.assert.calledWith(mockGptSlot.setConfig, { componentAuction: [{ - configKey: 'b1', - auctionConfig: cf1, + configKey: 'bidder', + auctionConfig: null }] }); + }); + + it('should reset only sellers with no fresh config', () => { + setGptConfig('au', [{seller: 's1'}, {seller: 's2'}]); + mockGptSlot.setConfig.resetHistory(); + setGptConfig('au', [{seller: 's1'}], true); sinon.assert.calledWith(mockGptSlot.setConfig, { componentAuction: [{ - configKey: 'b2', - auctionConfig: cf2, + configKey: 's1', + auctionConfig: {seller: 's1'} + }, { + configKey: 's2', + auctionConfig: null }] - }); + }) }); - it('should drop auction configs after end of auction', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); - fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + it('should not reset sellers that were already reset', () => { + setGptConfig('au', [{seller: 's1'}]); + setGptConfig('au', [], true); + mockGptSlot.setConfig.resetHistory(); + setGptConfig('au', [], true); sinon.assert.notCalled(mockGptSlot.setConfig); - }); + }) - it('should augment auctionSignals with FPD', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); - fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au1', ortb2: {fpd: 1}, ortb2Imp: {fpd: 2}}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId: 'aid'}); + it('should keep track of configuration history by slot', () => { + setGptConfig('au1', [{seller: 's1'}]); + setGptConfig('au1', [{seller: 's2'}], false); + setGptConfig('au2', [{seller: 's3'}]); + mockGptSlot.setConfig.resetHistory(); + setGptConfig('au1', [], true); sinon.assert.calledWith(mockGptSlot.setConfig, { componentAuction: [{ - configKey: 'bidder', - auctionConfig: { - ...fledgeAuctionConfig, - auctionSignals: { - prebid: { - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - } - } - }, + configKey: 's1', + auctionConfig: null + }, { + configKey: 's2', + auctionConfig: null }] - }) - }) - - describe('floor signal', () => { - before(() => { - if (!getGlobal().convertCurrency) { - getGlobal().convertCurrency = () => null; - getGlobal().convertCurrency.mock = true; - } - }); - after(() => { - if (getGlobal().convertCurrency.mock) { - delete getGlobal().convertCurrency; - } - }); - - beforeEach(() => { - sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { - if (from === to) return amount; - if (from === 'USD' && to === 'JPY') return amount * 100; - if (from === 'JPY' && to === 'USD') return amount / 100; - throw new Error('unexpected currency conversion'); - }); }); - + }) + }); + }); + describe('onAuctionConfig', () => { + [ + 'fledgeForGpt', + 'paapi.gpt' + ].forEach(namespace => { + describe(`using ${namespace} config`, () => { Object.entries({ - 'bids': (payload, values) => { - payload.bidsReceived = values - .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) - .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]) - }, - 'no bids': (payload, values) => { - payload.bidderRequests = values - .map((val) => ({bids: [{adUnitCode: 'au', getFloor: () => ({floor: val.amount, currency: val.cur})}]})) - .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]) - } - }).forEach(([tcase, setup]) => { - describe(`when auction has ${tcase}`, () => { - Object.entries({ - 'no currencies': { - values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], - 'bids': { - bidfloor: 100, - bidfloorcur: undefined - }, - 'no bids': { - bidfloor: 1, - bidfloorcur: undefined, - } - }, - 'only zero values': { - values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], - 'bids': { - bidfloor: undefined, - bidfloorcur: undefined, - }, - 'no bids': { - bidfloor: undefined, - bidfloorcur: undefined, - } - }, - 'matching currencies': { - values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], - 'bids': { - bidfloor: 100, - bidfloorcur: 'JPY', - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } - }, - 'mixed currencies': { - values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], - 'bids': { - bidfloor: 10, - bidfloorcur: 'USD' - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } - } - }).forEach(([t, testConfig]) => { - const values = testConfig.values; - const {bidfloor, bidfloorcur} = testConfig[tcase]; - - describe(`with ${t}`, () => { - let payload; - beforeEach(() => { - payload = {auctionId: 'aid'}; - setup(payload, values); - }); + 'omitted': [undefined, true], + 'enabled': [true, true], + 'disabled': [false, false] + }).forEach(([t, [autoconfig, shouldSetConfig]]) => { + describe(`when autoconfig is ${t}`, () => { + beforeEach(() => { + const cfg = {}; + deepSetValue(cfg, `${namespace}.autoconfig`, autoconfig); + config.setConfig(cfg); + }); + afterEach(() => { + config.resetConfig(); + }); - it('should populate bidfloor/bidfloorcur', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: 'aid'}); - fledge.addComponentAuctionHook(nextFnSpy, {auctionId: 'aid', adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, payload); - sinon.assert.calledWith(mockGptSlot.setConfig, sinon.match(arg => { - return arg.componentAuction.some(au => au.auctionConfig.auctionSignals?.prebid?.bidfloor === bidfloor && au.auctionConfig.auctionSignals?.prebid?.bidfloorcur === bidfloorcur) - })) - }) - }); + it(`should ${shouldSetConfig ? '' : 'NOT'} set GPT slot configuration`, () => { + const auctionConfig = {componentAuctions: [{seller: 'mock1'}, {seller: 'mock2'}]}; + const setGptConfig = sinon.stub(); + const markAsUsed = sinon.stub(); + onAuctionConfigFactory(setGptConfig)('aid', {au1: auctionConfig, au2: null}, markAsUsed); + if (shouldSetConfig) { + sinon.assert.calledWith(setGptConfig, 'au1', auctionConfig.componentAuctions); + sinon.assert.calledWith(setGptConfig, 'au2', []); + sinon.assert.calledWith(markAsUsed, 'au1'); + } else { + sinon.assert.notCalled(setGptConfig); + sinon.assert.notCalled(markAsUsed); + } }); }) }) - }); - }); + }) + }) }); - - describe('fledgeEnabled', function () { - const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); - - before(function () { - // navigator.runAdAuction & co may not exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - Object.keys(navProps).forEach(p => { - navigator[p] = sinon.stub(); - }); - hook.ready(); + describe('setPAAPIConfigForGpt', () => { + let getPAAPIConfig, setGptConfig, setPAAPIConfigForGPT; + beforeEach(() => { + getPAAPIConfig = sinon.stub(); + setGptConfig = sinon.stub(); + setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig); }); - after(function () { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); - }); - - afterEach(function () { - config.resetConfig(); + Object.entries({ + missing: null, + empty: {} + }).forEach(([t, configs]) => { + it(`does not set GPT slot config when config is ${t}`, () => { + getPAAPIConfig.returns(configs); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + sinon.assert.notCalled(setGptConfig); + }) }); - const adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] + it('sets GPT slot config for each ad unit that has PAAPI config, and resets the rest', () => { + const cfg = { + au1: { + componentAuctions: [{seller: 's1'}, {seller: 's2'}] }, - }, - 'bids': [ - { - 'bidder': 'appnexus', + au2: { + componentAuctions: [{seller: 's3'}] }, - { - 'bidder': 'rubicon', - }, - ] - }]; - function expectFledgeFlags(...enableFlags) { - const bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); - - expect(bidRequests[0].bids[0].bidder).equals('appnexus'); - expect(bidRequests[0].fledgeEnabled).to.eql(enableFlags[0].enabled) - bidRequests[0].bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)) - - expect(bidRequests[1].bids[0].bidder).equals('rubicon'); - expect(bidRequests[1].fledgeEnabled).to.eql(enableFlags[1].enabled) - bidRequests[1].bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - } - - describe('with setBidderConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({bidderSequence: 'fixed'}); - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - fledgeEnabled: true, - defaultForSlots: 1, - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); - }); - }); - - describe('with setConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setConfig({ - bidderSequence: 'fixed', - fledgeForGpt: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); - }); - - it('should set fledgeEnabled correctly for all bidders', function () { - config.setConfig({ - bidderSequence: 'fixed', - fledgeForGpt: { - enabled: true, - defaultForSlots: 1, - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); - }); - - it('should not override pub-defined ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - fledgeForGpt: { - enabled: true, - defaultForSlots: 1, - } - }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); - }) - }); - }); - - describe('ortb processors for fledge', () => { - it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; - setImpExtAe(imp, {}, {bidderRequest: {}}); - expect(imp.ext.ae).to.not.exist; - }); - it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); - }); - describe('parseExtPrebidFledge', () => { - function packageConfigs(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } - } - }; - } - - function generateImpCtx(fledgeFlags) { - return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); - } - - function generateCfg(impid, ...ids) { - return ids.map((id) => ({impid, config: {id}})); - } - - function extractResult(ctx) { - return Object.fromEntries( - Object.entries(ctx) - .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) - .filter(([_, val]) => val != null) - ); + au3: null } - - it('should collect fledge configs by imp', () => { - const ctx = { - impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) - }; - const resp = packageConfigs( - generateCfg('e1', 1, 2, 3) - .concat(generateCfg('e2', 4) - .concat(generateCfg('d1', 5, 6))) - ); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({ - e1: [1, 2, 3], - e2: [4], - }); - }); - it('should not choke if fledge config references unknown imp', () => { - const ctx = {impContext: generateImpCtx({i: 1})}; - const resp = packageConfigs(generateCfg('unknown', 1)); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({}); - }); - }); - describe('setResponseFledgeConfigs', () => { - it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { - const ctx = { - impContext: { - 1: { - bidRequest: {bidId: 'bid1'}, - fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] - }, - 2: { - bidRequest: {bidId: 'bid2'}, - fledgeConfigs: [{config: {id: 3}}] - }, - 3: { - bidRequest: {bidId: 'bid3'} - } - } - }; - const resp = {}; - setResponseFledgeConfigs(resp, {}, ctx); - expect(resp.fledgeAuctionConfigs).to.eql([ - {bidId: 'bid1', config: {id: 1}}, - {bidId: 'bid1', config: {id: 2}}, - {bidId: 'bid2', config: {id: 3}}, - ]); - }); - it('should not set fledgeAuctionConfigs if none exist', () => { - const resp = {}; - setResponseFledgeConfigs(resp, {}, { - impContext: { - 1: { - fledgeConfigs: [] - }, - 2: {} - } - }); - expect(resp).to.eql({}); - }); + getPAAPIConfig.returns(cfg); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + Object.entries(cfg).forEach(([au, config]) => { + sinon.assert.calledWith(setGptConfig, au, config?.componentAuctions ?? [], true); + }) }); - }); + }) }); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js new file mode 100644 index 00000000000..3d264e87e51 --- /dev/null +++ b/test/spec/modules/paapi_spec.js @@ -0,0 +1,628 @@ +import {expect} from 'chai'; +import {config} from '../../../src/config.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as utils from '../../../src/utils.js'; +import {hook} from '../../../src/hook.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import { + addComponentAuctionHook, + getPAAPIConfig, + parseExtPrebidFledge, + registerSubmodule, + setImpExtAe, + setResponseFledgeConfigs, + reset +} from 'modules/paapi.js'; +import * as events from 'src/events.js'; +import CONSTANTS from 'src/constants.json'; +import {getGlobal} from '../../../src/prebidGlobal.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {stubAuctionIndex} from '../../helpers/indexStub.js'; +import {AuctionIndex} from '../../../src/auctionIndex.js'; + +describe('paapi module', () => { + let sandbox; + before(reset); + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + afterEach(() => { + sandbox.restore(); + reset(); + }); + + [ + 'fledgeForGpt', + 'paapi' + ].forEach(configNS => { + describe(`using ${configNS} for configuration`, () => { + describe('getPAAPIConfig', function () { + let nextFnSpy, fledgeAuctionConfig; + before(() => { + config.setConfig({[configNS]: {enabled: true}}); + }); + beforeEach(() => { + fledgeAuctionConfig = { + seller: 'bidder', + mock: 'config' + }; + nextFnSpy = sinon.spy(); + }); + + describe('on a single auction', function () { + const auctionId = 'aid'; + beforeEach(function () { + sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); + }); + + it('should call next()', function () { + const request = {auctionId, adUnitCode: 'auc'}; + addComponentAuctionHook(nextFnSpy, request, fledgeAuctionConfig); + sinon.assert.calledWith(nextFnSpy, request, fledgeAuctionConfig); + }); + + describe('should collect auction configs', () => { + let cf1, cf2; + beforeEach(() => { + cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; + cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, cf1); + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, cf2); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + }); + + it('and make them available at end of auction', () => { + sinon.assert.match(getPAAPIConfig({auctionId}), { + au1: { + componentAuctions: [cf1] + }, + au2: { + componentAuctions: [cf2] + } + }); + }); + + it('and filter them by ad unit', () => { + const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); + expect(Object.keys(cfg)).to.have.members(['au1']); + sinon.assert.match(cfg.au1, { + componentAuctions: [cf1] + }); + }); + + it('and not return them again', () => { + getPAAPIConfig(); + const cfg = getPAAPIConfig(); + expect(cfg).to.eql({}); + }); + + describe('includeBlanks = true', () => { + it('includes all ad units', () => { + const cfg = getPAAPIConfig({}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }) + it('includes the targeted adUnit', () => { + expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ + au3: null + }) + }); + it('includes the targeted auction', () => { + const cfg = getPAAPIConfig({auctionId}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }); + it('does not include non-existing ad units', () => { + expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); + }); + it('does not include non-existing auctions', () => { + expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); + }) + }); + }); + + it('should drop auction configs after end of auction', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + expect(getPAAPIConfig({auctionId})).to.eql({}); + }); + + it('should augment auctionSignals with FPD', () => { + addComponentAuctionHook(nextFnSpy, { + auctionId, + adUnitCode: 'au1', + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + }, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + sinon.assert.match(getPAAPIConfig({auctionId}), { + au1: { + componentAuctions: [{ + ...fledgeAuctionConfig, + auctionSignals: { + prebid: { + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + } + } + }] + } + }); + }); + + describe('submodules', () => { + let submods; + beforeEach(() => { + submods = [1, 2].map(i => ({ + name: `test${i}`, + onAuctionConfig: sinon.stub() + })); + submods.forEach(registerSubmodule); + }); + + describe('onAuctionConfig', () => { + const auctionId = 'aid'; + it('is invoked with null configs when there\'s no config', () => { + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); + submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); + }); + it('is invoked with relevant configs', () => { + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + submods.forEach(submod => { + sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { + au1: {componentAuctions: [fledgeAuctionConfig]}, + au2: {componentAuctions: [fledgeAuctionConfig]}, + au3: null + }) + }); + }); + it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { + submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { + markAsUsed('au1'); + }); + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.eql({}); + }); + it('keeps them available if they do not', () => { + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.not.be.empty; + }) + }); + }); + + describe('floor signal', () => { + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }); + + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { + if (from === to) return amount; + if (from === 'USD' && to === 'JPY') return amount * 100; + if (from === 'JPY' && to === 'USD') return amount / 100; + throw new Error('unexpected currency conversion'); + }); + }); + + Object.entries({ + 'bids': (payload, values) => { + payload.bidsReceived = values + .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) + .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); + }, + 'no bids': (payload, values) => { + payload.bidderRequests = values + .map((val) => ({ + bids: [{ + adUnitCode: 'au', + getFloor: () => ({floor: val.amount, currency: val.cur}) + }] + })) + .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); + } + }).forEach(([tcase, setup]) => { + describe(`when auction has ${tcase}`, () => { + Object.entries({ + 'no currencies': { + values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], + 'bids': { + bidfloor: 100, + bidfloorcur: undefined + }, + 'no bids': { + bidfloor: 1, + bidfloorcur: undefined, + } + }, + 'only zero values': { + values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], + 'bids': { + bidfloor: undefined, + bidfloorcur: undefined, + }, + 'no bids': { + bidfloor: undefined, + bidfloorcur: undefined, + } + }, + 'matching currencies': { + values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], + 'bids': { + bidfloor: 100, + bidfloorcur: 'JPY', + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + }, + 'mixed currencies': { + values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], + 'bids': { + bidfloor: 10, + bidfloorcur: 'USD' + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + } + }).forEach(([t, testConfig]) => { + const values = testConfig.values; + const {bidfloor, bidfloorcur} = testConfig[tcase]; + + describe(`with ${t}`, () => { + let payload; + beforeEach(() => { + payload = {auctionId}; + setup(payload, values); + }); + + it('should populate bidfloor/bidfloorcur', () => { + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); + events.emit(CONSTANTS.EVENTS.AUCTION_END, payload); + const signals = getPAAPIConfig({auctionId}).au.componentAuctions[0].auctionSignals; + expect(signals.prebid?.bidfloor).to.eql(bidfloor); + expect(signals.prebid?.bidfloorcur).to.eql(bidfloorcur); + }); + }); + }); + }); + }); + }); + }); + + describe('with multiple auctions', () => { + const AUCTION1 = 'auction1'; + const AUCTION2 = 'auction2'; + + function mockAuction(auctionId) { + return { + getAuctionId() { + return auctionId; + } + }; + } + + function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { + expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); + Object.entries(actualConfig).forEach(([au, cfg]) => { + cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); + }); + } + + let configs; + beforeEach(() => { + const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; + sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); + configs = {[AUCTION1]: {}, [AUCTION2]: {}}; + Object.entries({ + [AUCTION1]: [['au1', 'au2'], ['missing-1']], + [AUCTION2]: [['au2', 'au3'], []], + }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { + adUnitCodes.forEach(adUnitCode => { + const cfg = {...fledgeAuctionConfig, auctionId, adUnitCode}; + configs[auctionId][adUnitCode] = cfg; + addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode}, cfg); + }); + events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); + }); + }); + + it('should filter by auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); + }); + + it('should filter by auction and ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); + }); + + it('should use last auction for each ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + }); + + it('should filter by ad unit and use latest auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); + }); + + it('should keep track of which configs were returned', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); + expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); + }); + + describe('includeBlanks = true', () => { + Object.entries({ + 'auction with blanks': { + filters: {auctionId: AUCTION1}, + expected: {au1: true, au2: true, 'missing-1': false} + }, + 'blank adUnit in an auction': { + filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, + expected: {'missing-1': false} + }, + 'non-existing auction': { + filters: {auctionId: 'other'}, + expected: {} + }, + 'non-existing adUnit in an auction': { + filters: {auctionId: AUCTION2, adUnitCode: 'other'}, + expected: {} + }, + 'non-existing ad unit': { + filters: {adUnitCode: 'other'}, + expected: {}, + }, + 'non existing ad unit in a non-existing auction': { + filters: {adUnitCode: 'other', auctionId: 'other'}, + expected: {} + }, + 'all ad units': { + filters: {}, + expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} + } + }).forEach(([t, {filters, expected}]) => { + it(t, () => { + const cfg = getPAAPIConfig(filters, true); + expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); + Object.entries(expected).forEach(([au, shouldBeFilled]) => { + if (shouldBeFilled) { + expect(cfg[au]).to.not.be.null; + } else { + expect(cfg[au]).to.be.null; + } + }) + }) + }) + }); + }); + }); + + describe('markForFledge', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { + navigator[p] = sinon.stub(); + }); + hook.ready(); + config.resetConfig(); + }); + + after(function () { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }); + + afterEach(function () { + config.resetConfig(); + }); + + const adUnits = [{ + 'code': '/19968336/header-bid-tag1', + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + }, + }, + 'bids': [ + { + 'bidder': 'appnexus', + }, + { + 'bidder': 'rubicon', + }, + ] + }]; + + function expectFledgeFlags(...enableFlags) { + const bidRequests = Object.fromEntries( + adapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ).map(b => [b.bidderCode, b]) + ); + + expect(bidRequests.appnexus.fledgeEnabled).to.eql(enableFlags[0].enabled); + bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); + + expect(bidRequests.rubicon.fledgeEnabled).to.eql(enableFlags[1].enabled); + bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); + } + + describe('with setBidderConfig()', () => { + it('should set fledgeEnabled correctly per bidder', function () { + config.setBidderConfig({ + bidders: ['appnexus'], + config: { + defaultForSlots: 1, + fledgeEnabled: true + } + }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); + }); + }); + + describe('with setConfig()', () => { + it('should set fledgeEnabled correctly per bidder', function () { + config.setConfig({ + bidderSequence: 'fixed', + [configNS]: { + enabled: true, + bidders: ['appnexus'], + defaultForSlots: 1, + } + }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); + }); + + it('should set fledgeEnabled correctly for all bidders', function () { + config.setConfig({ + bidderSequence: 'fixed', + [configNS]: { + enabled: true, + defaultForSlots: 1, + } + }); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); + }); + + it('should not override pub-defined ext.ae', () => { + config.setConfig({ + bidderSequence: 'fixed', + [configNS]: { + enabled: true, + defaultForSlots: 1, + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); + expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); + }); + }); + }); + }); + }); + + describe('ortb processors for fledge', () => { + it('imp.ext.ae should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + }); + it('imp.ext.ae should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: 2}}; + setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); + expect(imp.ext.ae).to.equal(2); + }); + describe('parseExtPrebidFledge', () => { + function packageConfigs(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + }; + } + + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + function extractResult(ctx) { + return Object.fromEntries( + Object.entries(ctx) + .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .filter(([_, val]) => val != null) + ); + } + + it('should collect fledge configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parseExtPrebidFledge({}, resp, ctx); + expect(extractResult(ctx.impContext)).to.eql({}); + }); + }); + describe('setResponseFledgeConfigs', () => { + it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + const ctx = { + impContext: { + 1: { + bidRequest: {bidId: 'bid1'}, + fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + }, + 2: { + bidRequest: {bidId: 'bid2'}, + fledgeConfigs: [{config: {id: 3}}] + }, + 3: { + bidRequest: {bidId: 'bid3'} + } + } + }; + const resp = {}; + setResponseFledgeConfigs(resp, {}, ctx); + expect(resp.fledgeAuctionConfigs).to.eql([ + {bidId: 'bid1', config: {id: 1}}, + {bidId: 'bid1', config: {id: 2}}, + {bidId: 'bid2', config: {id: 3}}, + ]); + }); + it('should not set fledgeAuctionConfigs if none exist', () => { + const resp = {}; + setResponseFledgeConfigs(resp, {}, { + impContext: { + 1: { + fledgeConfigs: [] + }, + 2: {} + } + }); + expect(resp).to.eql({}); + }); + }); + }); +}); From 24b6d7191770e2f438da046f339196414fc737b8 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Wed, 14 Feb 2024 19:02:30 +0100 Subject: [PATCH 452/760] Add Onetag topics iframe (#11091) Co-authored-by: onetag-dev --- modules/topicsFpdModule.js | 3 +++ modules/topicsFpdModule.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index f07ab0c7c14..b5a90dacfb4 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -35,6 +35,9 @@ const bidderIframeList = { }, { bidder: 'improvedigital', iframeURL: 'https://hb.360yield.com/privacy-sandbox/topics.html' + }, { + bidder: 'onetag', + iframeURL: 'https://onetag-sys.com/static/topicsapi.html' }] } diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index a6bb14dca67..89eb03fb6df 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -52,6 +52,10 @@ pbjs.setConfig({ bidder: 'appnexus', iframeURL: 'https://appnexus.com:8080/topics/fpd/topic.html', // dummy URL expiry: 7 // Configurable expiry days + }, { + bidder: 'onetag', + iframeURL: 'https://onetag-sys.com/static/topicsapi.html', + expiry: 7 // Configurable expiry days }] } .... From 0b629557c16151e7953dbe40eae218055f3ba3af Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Wed, 14 Feb 2024 19:02:42 +0100 Subject: [PATCH 453/760] Use built-in sampling (#11041) --- modules/liveIntentAnalyticsAdapter.js | 11 ++----- .../liveIntentAnalyticsAdapter_spec.js | 32 ++++++++++++++++--- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/modules/liveIntentAnalyticsAdapter.js b/modules/liveIntentAnalyticsAdapter.js index ffe4f8f58b0..54402bcafc6 100644 --- a/modules/liveIntentAnalyticsAdapter.js +++ b/modules/liveIntentAnalyticsAdapter.js @@ -10,11 +10,8 @@ const ANALYTICS_TYPE = 'endpoint'; const URL = 'https://wba.liadm.com/analytic-events'; const GVL_ID = 148; const ADAPTER_CODE = 'liveintent'; -const DEFAULT_SAMPLING = 0.1; const DEFAULT_BID_WON_TIMEOUT = 2000; const { EVENTS: { AUCTION_END } } = CONSTANTS; -let initOptions = {}; -let isSampled; let bidWonTimeout; function handleAuctionEnd(args) { @@ -123,19 +120,15 @@ function ignoreUndefined(data) { let liAnalytics = Object.assign(adapter({URL, ANALYTICS_TYPE}), { track({ eventType, args }) { - if (eventType == AUCTION_END && args && isSampled) { handleAuctionEnd(args); } + if (eventType == AUCTION_END && args) { handleAuctionEnd(args); } } }); // save the base class function liAnalytics.originEnableAnalytics = liAnalytics.enableAnalytics; - // override enableAnalytics so we can get access to the config passed in from the page liAnalytics.enableAnalytics = function (config) { - initOptions = config.options; - const sampling = (initOptions && initOptions.sampling) ?? DEFAULT_SAMPLING; - isSampled = Math.random() < parseFloat(sampling); - bidWonTimeout = (initOptions && initOptions.bidWonTimeout) ?? DEFAULT_BID_WON_TIMEOUT; + bidWonTimeout = config?.options?.bidWonTimeout ?? DEFAULT_BID_WON_TIMEOUT; liAnalytics.originEnableAnalytics(config); // call the base class function }; diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index fa4c5cd8cad..d00bfbc7bb5 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -16,7 +16,7 @@ let events = require('src/events'); let constants = require('src/constants.json'); let auctionId = '99abbc81-c1f1-41cd-8f25-f7149244c897' -const config = { +const configWithSamplingAll = { provider: 'liveintent', options: { bidWonTimeout: 2000, @@ -24,6 +24,14 @@ const config = { } } +const configWithSamplingNone = { + provider: 'liveintent', + options: { + bidWonTimeout: 2000, + sampling: 0 + } +} + let args = { auctionId: auctionId, timestamp: 1660915379703, @@ -273,8 +281,8 @@ describe('LiveIntent Analytics Adapter ', () => { clock.restore(); }); - it('request is computed and sent correctly', () => { - liAnalytics.enableAnalytics(config); + it('request is computed and sent correctly when sampling is 1', () => { + liAnalytics.enableAnalytics(configWithSamplingAll); sandbox.stub(utils, 'generateUUID').returns(instanceId); sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); @@ -288,7 +296,23 @@ describe('LiveIntent Analytics Adapter ', () => { it('track is called', () => { sandbox.stub(liAnalytics, 'track'); - liAnalytics.enableAnalytics(config); + liAnalytics.enableAnalytics(configWithSamplingAll); expectEvents().to.beTrackedBy(liAnalytics.track); }) + + it('no request is computed when sampling is 0', () => { + liAnalytics.enableAnalytics(configWithSamplingNone); + sandbox.stub(utils, 'generateUUID').returns(instanceId); + sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); + sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); + events.emit(constants.EVENTS.AUCTION_END, args); + clock.tick(2000); + expect(server.requests.length).to.equal(0); + }); + + it('track is not called', () => { + sandbox.stub(liAnalytics, 'track'); + liAnalytics.enableAnalytics(configWithSamplingNone); + sinon.assert.callCount(liAnalytics.track, 0); + }) }); From 5b4bfd9632f3d412c397f9ad0112e6335f1ccdbc Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:29:35 +0200 Subject: [PATCH 454/760] Rubicon Bid Adapter: pass DSA fields (#10974) * Pass DSA fields through fastlane.json * adjusting field names to reflect IAB changes * adjust to new field names * Add DSA meta field for biiders * Add an unit test to handle DSA in response * Update the comments --------- Co-authored-by: bretg --- modules/rubiconBidAdapter.js | 25 ++++ test/spec/modules/rubiconBidAdapter_spec.js | 132 ++++++++++++++++++++ 2 files changed, 157 insertions(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index daaf9a14b9f..813db65717e 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -697,6 +697,10 @@ export const spec = { bid.mediaType = ad.creative_type; } + if (ad.dsa && Object.keys(ad.dsa).length) { + bid.meta.dsa = ad.dsa; + } + if (ad.adomain) { bid.meta.advertiserDomains = Array.isArray(ad.adomain) ? ad.adomain : [ad.adomain]; } @@ -906,6 +910,7 @@ function applyFPD(bidRequest, mediaType, data) { let impExtData = deepAccess(bidRequest.ortb2Imp, 'ext.data') || {}; const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + const dsa = deepAccess(fpd, 'regs.ext.dsa'); const SEGTAX = {user: [4], site: [1, 2, 5, 6]}; const MAP = {user: 'tg_v.', site: 'tg_i.', adserver: 'tg_i.dfp_ad_unit_code', pbadslot: 'tg_i.pbadslot', keywords: 'kw'}; const validate = function(prop, key, parentName) { @@ -961,6 +966,26 @@ function applyFPD(bidRequest, mediaType, data) { data['p_gpid'] = gpid; } + // add dsa signals + if (dsa && Object.keys(dsa).length) { + pick(dsa, [ + 'dsainfo', (dsainfo) => data['dsainfo'] = dsainfo, + 'dsarequired', (required) => data['dsarequired'] = required, + 'pubrender', (pubrender) => data['dsapubrender'] = pubrender, + 'datatopub', (datatopub) => data['dsadatatopubs'] = datatopub, + 'transparency', (transparency) => { + if (Array.isArray(transparency) && transparency.length) { + data['dsatransparency'] = transparency.reduce((param, transp) => { + if (param) { + param += '~~' + } + return param += `${transp.domain}~${transp.dsaparams.join('_')}` + }, '') + } + } + ]) + } + // only send one of pbadslot or dfp adunit code (prefer pbadslot) if (data['tg_i.pbadslot']) { delete data['tg_i.dfp_ad_unit_code']; diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index f0e33ce940e..62e494ea295 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -21,6 +21,7 @@ import 'modules/priceFloors.js'; import 'modules/multibid/index.js'; import adapterManager from 'src/adapterManager.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import { deepClone } from '../../../src/utils.js'; const INTEGRATION = `pbjs_lite_v$prebid.version$`; // $prebid.version$ will be substituted in by gulp in built prebid const PBS_INTEGRATION = 'pbjs'; @@ -1711,6 +1712,57 @@ describe('the rubicon adapter', function () { expect(data['p_gpid']).to.equal('/1233/sports&div1'); }); + describe('Pass DSA signals', function() { + const ortb2 = { + regs: { + ext: { + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'testdomain.com', + dsaparams: [1], + }, + { + domain: 'testdomain2.com', + dsaparams: [1, 2] + } + ] + } + } + } + } + it('should send dsa signals if \"ortb2.regs.ext.dsa\"', function() { + const expectedTransparency = 'testdomain.com~1~~testdomain2.com~1_2' + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest) + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('dsarequired'); + expect(data).to.have.property('dsapubrender'); + expect(data).to.have.property('dsadatatopubs'); + expect(data).to.have.property('dsatransparency'); + + expect(data['dsarequired']).to.equal(ortb2.regs.ext.dsa.dsarequired.toString()); + expect(data['dsapubrender']).to.equal(ortb2.regs.ext.dsa.pubrender.toString()); + expect(data['dsadatatopubs']).to.equal(ortb2.regs.ext.dsa.datatopub.toString()); + expect(data['dsatransparency']).to.equal(expectedTransparency) + }) + it('should return one transparency param', function() { + const expectedTransparency = 'testdomain.com~1'; + const ortb2Clone = deepClone(ortb2); + ortb2Clone.regs.ext.dsa.transparency.pop() + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest) + const data = parseQuery(request.data); + + expect(data).to.be.an('Object'); + expect(data).to.have.property('dsatransparency'); + expect(data['dsatransparency']).to.equal(expectedTransparency); + }) + }) + it('should send gpid and pbadslot since it is prefered over dfp code', function () { bidderRequest.bids[0].ortb2Imp = { ext: { @@ -3276,6 +3328,86 @@ describe('the rubicon adapter', function () { expect(bids[0].cpm).to.be.equal(0); }); + it('should handle DSA object from response', function() { + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'adomain': ['test.com'], + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ], + 'dsa': { + 'behalf': 'Advertiser', + 'paid': 'Advertiser', + 'transparency': [{ + 'domain': 'dsp1domain.com', + 'dsaparams': [1, 2] + }], + 'adrender': 1 + } + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'adomain': ['test.com'], + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ], + 'dsa': {} + } + ] + }; + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids).to.be.lengthOf(2); + expect(bids[1].meta.dsa).to.have.property('behalf'); + expect(bids[1].meta.dsa).to.have.property('paid'); + + // if we dont have dsa field in response or the dsa object is empty + expect(bids[0].meta).to.not.have.property('dsa'); + }) + it('should create bids with matching requestIds if imp id matches', function () { let bidRequests = [{ 'bidder': 'rubicon', From f9584c68a1741d2dbfc21aacfb3d501af68a082b Mon Sep 17 00:00:00 2001 From: Denis <7009699+someden@users.noreply.github.com> Date: Wed, 14 Feb 2024 21:44:18 +0300 Subject: [PATCH 455/760] Fix build (#11098) --- gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 125dec93402..d035da4b0fc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,7 +88,7 @@ function lint(done) { '!plugins/**/node_modules/**', './*.js' ], { base: './' }) - .pipe(eslint({ fix: !argv.nolintfix, quiet: !(argv.lintWarnings ?? true) })) + .pipe(eslint({ fix: !argv.nolintfix, quiet: !(typeof argv.lintWarnings === 'boolean' ? argv.lintWarnings : true) })) .pipe(eslint.format('stylish')) .pipe(eslint.failAfterError()) .pipe(gulpif(isFixed, gulp.dest('./'))); From 8e5a2b94d7a29e5ba212ac0108c4d2f335a5b3ea Mon Sep 17 00:00:00 2001 From: Aymeric Le Corre Date: Wed, 14 Feb 2024 20:40:22 +0100 Subject: [PATCH 456/760] Lucead Bid Adapter: Add new adapter (#11068) * Add Whitebox adapter * Add Lucead Bid Adapter * update maintainer * update endpoint url --- modules/luceadBidAdapter.js | 147 ++++++++++++++++++ modules/luceadBidAdapter.md | 27 ++++ src/adloader.js | 3 +- test/spec/modules/luceadBidAdapter_spec.js | 165 +++++++++++++++++++++ 4 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 modules/luceadBidAdapter.js create mode 100644 modules/luceadBidAdapter.md create mode 100644 test/spec/modules/luceadBidAdapter_spec.js diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js new file mode 100644 index 00000000000..8958e8f3786 --- /dev/null +++ b/modules/luceadBidAdapter.js @@ -0,0 +1,147 @@ +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 {fetch} from '../src/ajax.js'; + +const bidderCode = 'lucead'; +let baseUrl = 'https://ayads.io'; +let staticUrl = 'https://s.ayads.io'; +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'; +const defaultCurrency = 'EUR'; +const defaultTtl = 500; +const isDevEnv = location.hostname.endsWith('.ngrok-free.app'); + +function isBidRequestValid(bidRequest) { + return !!bidRequest?.params?.placementId; +} + +export function log(msg, obj) { + logInfo('Lucead - ' + msg, obj); +} + +function buildRequests(validBidRequests, bidderRequest) { + if (isDevEnv) { + baseUrl = `https://${location.hostname}`; + staticUrl = baseUrl; + companionUrl = `${staticUrl}/dist/prebid-companion.js`; + endpointUrl = `${baseUrl}/go`; + } + + log('buildRequests', { + validBidRequests, + bidderRequest, + }); + + const companionData = { + base_url: baseUrl, + static_url: staticUrl, + endpoint_url: endpointUrl, + request_id: bidderRequest.bidderRequestId, + validBidRequests, + bidderRequest, + getUniqueIdentifierStr, + ortbConverter, + }; + + loadExternalScript(companionUrl, bidderCode, () => window.ayads_prebid && window.ayads_prebid(companionData)); + + return validBidRequests.map(bidRequest => ({ + method: 'POST', + url: `${endpointUrl}/prebid/sub`, + data: JSON.stringify({ + request_id: bidderRequest.bidderRequestId, + domain: location.hostname, + bid_id: bidRequest.bidId, + sizes: bidRequest.sizes, + media_types: bidRequest.mediaTypes, + fledge_enabled: bidderRequest.fledgeEnabled, + enable_contextual: bidRequest?.params?.enableContextual !== false, + enable_pa: bidRequest?.params?.enablePA !== false, + params: bidRequest.params, + }), + options: { + contentType: 'text/plain', + withCredentials: false + }, + })); +} + +function interpretResponse(serverResponse, bidRequest) { + // @see required fields https://docs.prebid.org/dev-docs/bidder-adaptor.html + const response = serverResponse.body; + const bidRequestData = JSON.parse(bidRequest.data); + + const bids = response.enable_contextual !== false ? [{ + requestId: response?.bid_id || '1', // bid request id, the bid id + cpm: response?.cpm || 0, + width: (response?.size && response?.size?.width) || 300, + height: (response?.size && response?.size?.height) || 250, + currency: response?.currency || defaultCurrency, + ttl: response?.ttl || defaultTtl, + creativeId: response?.ad_id || '0', + netRevenue: response?.netRevenue || true, + ad: response?.ad || '', + meta: { + advertiserDomains: response?.advertiserDomains || [], + }, + }] : null; + + log('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); + + if (response.enable_pa === false) { return bids; } + + const fledgeAuctionConfig = { + seller: baseUrl, + decisionLogicUrl: `${baseUrl}/js/ssp.js`, + interestGroupBuyers: [baseUrl], + perBuyerSignals: {}, + auctionSignals: { + size: bidRequestData.sizes ? {width: bidRequestData?.sizes[0][0] || 300, height: bidRequestData?.sizes[0][1] || 250} : null, + }, + }; + + const fledgeAuctionConfigs = [{bidId: response.bid_id, config: fledgeAuctionConfig}]; + + return {bids, fledgeAuctionConfigs}; +} + +function report(type = 'impression', data = {}) { + // noinspection JSCheckFunctionSignatures + return fetch(`${endpointUrl}/report/${type}`, { + body: JSON.stringify(data), + method: 'POST', + contentType: 'text/plain' + }); +} + +function onBidWon(bid) { + log('Bid won', bid); + + return report(`impression`, { + bid_id: bid?.bidId, + ad_id: bid?.creativeId, + placement_id: bid?.params ? bid?.params[0]?.placementId : 0, + spent: bid?.cpm, + currency: bid?.currency, + }); +} + +function onTimeout(timeoutData) { + log('Timeout from adapter', timeoutData); +} + +export const spec = { + code: bidderCode, + // gvlid: BIDDER_GVLID, + aliases: [], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidWon, + onTimeout, +}; + +// noinspection JSCheckFunctionSignatures +registerBidder(spec); diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md new file mode 100644 index 00000000000..45fd3ec5301 --- /dev/null +++ b/modules/luceadBidAdapter.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: Lucead Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@lucead.com + +# Description + +Module that connects to Lucead demand source to fetch bids. + +# Test Parameters +``` +const adUnits = [ + { + code: 'test-div', + sizes: [[300, 250]], + bids: [ + { + bidder: "lucead", + params: { + placementId: '1', + } + } + ] + } + ]; +``` diff --git a/src/adloader.js b/src/adloader.js index f60955736bd..5309f3a3d42 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -31,7 +31,8 @@ const _approvedLoadExternalJSList = [ 'qortex', 'dynamicAdBoost', 'contxtful', - 'id5' + 'id5', + 'lucead', ] /** diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js new file mode 100644 index 00000000000..fa8d76cc30b --- /dev/null +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -0,0 +1,165 @@ +/* eslint-disable prebid/validate-imports,no-undef */ +import { expect } from 'chai'; +import { spec } from 'modules/luceadBidAdapter.js'; +import sinon from 'sinon'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import {deepClone} from 'src/utils.js'; +import * as ajax from 'src/ajax.js'; + +describe('Lucead Adapter', () => { + describe('inherited functions', function () { + it('exists and is a function', function () { + // noinspection JSCheckFunctionSignatures + const adapter = newBidder(spec); + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid; + beforeEach(function () { + bid = { + bidder: 'lucead', + params: { + placementId: '1', + }, + }; + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('onBidWon', function () { + let sandbox; + const bid = { foo: 'bar' }; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + it('should trigger impression pixel', function () { + sandbox.spy(ajax, 'fetch'); + spec.onBidWon(bid); + expect(ajax.fetch.args[0][0]).to.match(/report\/impression$/); + }); + + afterEach(function () { + sandbox.restore(); + }); + }); + + describe('buildRequests', function () { + const bidRequests = [ + { + bidder: 'lucead', + adUnitCode: 'lucead_code', + bidId: 'abc1234', + sizes: [[1800, 1000], [640, 300]], + requestId: 'xyz654', + params: { + placementId: '123', + } + } + ]; + + const bidderRequest = { + bidderRequestId: '13aaa3df18bfe4', + bids: {} + }; + + it('should have a post method', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].method).to.equal('POST'); + }); + + it('should contains a request id equals to the bid id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(request[0].data).bid_id).to.equal(bidRequests[0].bidId); + }); + + it('should have an url that contains sub keyword', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request[0].url).to.match(/sub/); + }); + }); + + describe('interpretResponse', function () { + const serverResponse = { + body: { + 'bid_id': '2daf899fbe4c52', + 'request_id': '13aaa3df18bfe4', + 'ad': 'Ad', + 'ad_id': '3890677904', + 'cpm': 3.02, + 'currency': 'USD', + 'time': 1707257712095, + 'size': {'width': 300, 'height': 250}, + } + }; + + const bidRequest = {data: JSON.stringify({ + 'request_id': '13aaa3df18bfe4', + 'domain': '7cdb-2a02-8429-e4a0-1701-bc69-d51c-86e-b279.ngrok-free.app', + 'bid_id': '2daf899fbe4c52', + 'sizes': [[300, 250]], + 'media_types': {'banner': {'sizes': [[300, 250]]}}, + 'fledge_enabled': true, + 'enable_contextual': true, + 'enable_pa': true, + 'params': {'placementId': '1'}, + })}; + + it('should get correct bid response', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + + expect(Object.keys(result.bids[0])).to.have.members([ + 'requestId', + 'cpm', + 'width', + 'height', + 'currency', + 'ttl', + 'creativeId', + 'netRevenue', + 'ad', + 'meta', + ]); + }); + + it('should return bid empty response', function () { + const serverResponse = {body: {cpm: 0}}; + const bidRequest = {data: '{}'}; + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result.bids[0].ad).to.be.equal(''); + expect(result.bids[0].cpm).to.be.equal(0); + }); + + it('should add advertiserDomains', function () { + const bidRequest = {data: JSON.stringify({ + bidder: 'lucead', + params: { + placementId: '1', + } + })}; + + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(Object.keys(result.bids[0].meta)).to.include.members(['advertiserDomains']); + }); + + it('should support disabled contextual bids', function () { + const serverResponseWithDisabledContectual = deepClone(serverResponse); + serverResponseWithDisabledContectual.body.enable_contextual = false; + const result = spec.interpretResponse(serverResponseWithDisabledContectual, bidRequest); + expect(result.bids).to.be.null; + }); + + it('should support disabled Protected Audience', function () { + const serverResponseWithEnablePaFalse = deepClone(serverResponse); + serverResponseWithEnablePaFalse.body.enable_pa = false; + const result = spec.interpretResponse(serverResponseWithEnablePaFalse, bidRequest); + expect(result.fledgeAuctionConfigs).to.be.undefined; + }); + }); +}); From 40e3f40c5613062a981616e39a80514b6b8c8460 Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Wed, 14 Feb 2024 23:00:30 +0100 Subject: [PATCH 457/760] Adnuntius Bid Adapter: Allow user ID to be passed as parameter (#11029) * Removed linting issues * Fixed merge issues. * Bugfix on storageTool. * Change to pass user ID as a parameter to the adserver. * fetch user id from paraters comment. --------- Co-authored-by: Antonios Sarhanis --- modules/adnuntiusBidAdapter.js | 11 +++++++--- test/spec/modules/adnuntiusBidAdapter_spec.js | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index a498d056513..02dd7453be8 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -103,11 +103,16 @@ const storageTool = (function () { storage.setDataInLocalStorage(META_DATA_KEY, JSON.stringify(metaDataForSaving)); }; - const getUsi = function (meta, ortb2) { - let usi = (meta && meta.usi) ? meta.usi : false; + 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 if (ortb2 && ortb2.user && ortb2.user.id) { usi = ortb2.user.id } + if (paramUsi) usi = paramUsi return usi; } @@ -131,7 +136,7 @@ const storageTool = (function () { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; metaInternal = getMetaInternal().reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); - metaInternal.usi = getUsi(metaInternal, ortb2); + metaInternal.usi = getUsi(metaInternal, ortb2, bidderRequest); if (!metaInternal.usi) { delete metaInternal.usi; } diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index e109ca1829c..20795b59e8c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -660,6 +660,27 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); + + it('should user in user', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + }); + const req = [ + { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: '000000000008b6bc', + network: 'adnuntius', + userId: 'different_user_id' + } + } + ] + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(`${ENDPOINT_URL_BASE}&userId=different_user_id`); + }); }); describe('user privacy', function() { From 13cbc7c53c52e856fdcd5d9522283d9609c2d103 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 15 Feb 2024 14:01:31 +0100 Subject: [PATCH 458/760] Adagio Bid Adapter: add DSA support (#11096) --- modules/adagioBidAdapter.js | 7 ++- test/spec/modules/adagioBidAdapter_spec.js | 57 +++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index efa6777a0dd..acd89ef72df 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -990,6 +990,9 @@ export const spec = { const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled') const usIfr = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') + // We don't validate the dsa object in adapter and let our server do it. + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + const aucId = generateUUID() const adUnits = validBidRequests.map(rawBidRequest => { @@ -1179,7 +1182,8 @@ export const spec = { coppa: coppa, ccpa: uspConsent, gpp: gppConsent.gpp, - gppSid: gppConsent.gppSid + gppSid: gppConsent.gppSid, + dsa: dsa // populated if exists }, schain: schain, user: { @@ -1216,6 +1220,7 @@ export const spec = { const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); if (bidReq) { + // bidObj.meta is the `bidResponse.meta` object according to https://docs.prebid.org/dev-docs/bidder-adaptor.html#interpreting-the-response bidObj.meta = deepAccess(bidObj, 'meta', {}); bidObj.meta.mediaType = bidObj.mediaType; bidObj.meta.advertiserDomains = (Array.isArray(bidObj.aDomain) && bidObj.aDomain.length) ? bidObj.aDomain : []; diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 38dbaf348fd..13c02cc9bae 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -975,6 +975,41 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[0].gpid).to.exist.and.equal(gpid); }); }); + + describe('with DSA', function() { + it('should add DSA to the request', function() { + const dsaObject = { + dsarequired: 1, + pubrender: 1, + datatopub: 2, + transparency: [{ + domain: 'domain.com', + dsaparams: [1, 2] + }] + } + + const bid01 = new BidRequestBuilder().withParams().build(); + + const bidderRequest = new BidderRequestBuilder({ + ortb2: { + regs: { + ext: { + dsa: dsaObject + } + } + } + }).build(); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.regs.dsa).to.deep.equal(dsaObject); + }); + + it('should not add DSA to the request if not present', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.regs.dsa).to.be.undefined; + }); + }) }); describe('interpretResponse()', function() { @@ -1129,7 +1164,7 @@ describe('Adagio bid adapter', () => { utilsMock.verify(); }); - describe('Response with video outstream', () => { + describe('Response with video outstream', function() { const bidRequestWithOutstream = utils.deepClone(bidRequest); bidRequestWithOutstream.data.adUnits[0].mediaTypes.video = { context: 'outstream', @@ -1202,7 +1237,7 @@ describe('Adagio bid adapter', () => { }); }); - describe('Response with native add', () => { + describe('Response with native add', function() { const serverResponseWithNative = utils.deepClone(serverResponse) serverResponseWithNative.body.bids[0].mediaType = 'native'; serverResponseWithNative.body.bids[0].admNative = { @@ -1379,6 +1414,24 @@ describe('Adagio bid adapter', () => { expect(r[0].native.javascriptTrackers).to.equal(expected); }); }); + + describe('Response with DSA', function() { + const dsaResponseObj = { + 'behalf': 'Advertiser', + 'paid': 'Advertiser', + 'transparency': { + 'domain': 'dsp1domain.com', + 'params': [1, 2] + }, + 'adrender': 1 + }; + + const serverResponseWithDsa = utils.deepClone(serverResponse); + serverResponseWithDsa.body.bids[0].meta.dsa = dsaResponseObj; + + const bidResponse = spec.interpretResponse(serverResponseWithDsa, bidRequest)[0]; + expect(bidResponse.meta.dsa).to.to.deep.equals(dsaResponseObj); + }) }); describe('getUserSyncs()', function() { From 9379982f8c1b72d78af5d5325230f9f6b5a07b1e Mon Sep 17 00:00:00 2001 From: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:05:41 +0100 Subject: [PATCH 459/760] RTB House Bid Adapter: add DSA support (#11097) * RTB House adapter: add DSA support * RTB House: add DSA support with extended field control --- modules/rtbhouseBidAdapter.js | 51 ++++- test/spec/modules/rtbhouseBidAdapter_spec.js | 217 ++++++++++++++++++- 2 files changed, 255 insertions(+), 13 deletions(-) diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 5f94ec3dea4..cfad8fce966 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isArray, logError, logInfo, mergeDeep} from '../src/utils.js'; +import {deepAccess, deepClone, isArray, logError, logInfo, mergeDeep, isEmpty, isPlainObject, isNumber, isStr} from '../src/utils.js'; import {getOrigin} from '../libraries/getOrigin/index.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -18,6 +18,12 @@ const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const TTL = 55; const GVLID = 16; +const DSA_ATTRIBUTES = [ + { name: 'dsarequired', 'min': 0, 'max': 3 }, + { name: 'pubrender', 'min': 0, 'max': 2 }, + { name: 'datatopub', 'min': 0, 'max': 2 } +]; + // Codes defined by OpenRTB Native Ads 1.1 specification export const OPENRTB = { NATIVE: { @@ -95,6 +101,17 @@ export const spec = { } }); + const dsa = deepAccess(ortb2Params, 'regs.ext.dsa'); + if (validateDSA(dsa)) { + mergeDeep(request, { + regs: { + ext: { + dsa + } + } + }); + } + let computedEndpointUrl = ENDPOINT_URL; if (bidderRequest.fledgeEnabled) { @@ -133,7 +150,13 @@ export const spec = { } else { interpretedBid = interpretBannerBid(serverBid); } - if (serverBid.ext) interpretedBid.ext = serverBid.ext; + + if (serverBid.ext) { + interpretedBid.ext = deepClone(serverBid.ext); + if (serverBid.ext.dsa) { + interpretedBid.meta = Object.assign({}, interpretedBid.meta, { dsa: serverBid.ext.dsa }); + } + } bids.push(interpretedBid); }); @@ -518,3 +541,27 @@ function interpretNativeAd(adm) { }); return result; } + +/** + * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md + * + * @param {object} dsa + * @returns {boolean} whether dsa object contains valid attributes values + */ +function validateDSA(dsa) { + if (isEmpty(dsa) || !isPlainObject(dsa)) return false; + + return DSA_ATTRIBUTES.reduce((prev, attr) => { + const dsaEntry = dsa[attr.name]; + return prev && ( + !dsa.hasOwnProperty(attr.name) || + (isNumber(dsaEntry) && dsaEntry >= attr.min && dsaEntry <= attr.max) + ) + }, true) && + (!dsa.hasOwnProperty('transparency') || + (isArray(dsa.transparency) && dsa.transparency.every( + v => isPlainObject(v) && isStr(v.domain) && v.domain && isArray(v.dsaparams) && + v.dsaparams.every(x => isNumber(x)) + )) + ) +} diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 0b944dcb077..77b746b9b69 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; +import { mergeDeep } from '../../../src/utils'; describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); @@ -304,6 +305,152 @@ describe('RTBHouseAdapter', () => { expect(data.user).to.nested.include({'ext.data': 'some user data'}); }); + context('DSA', () => { + const validDSAObject = { + 'dsarequired': 3, + 'pubrender': 0, + 'datatopub': 2, + 'transparency': [ + { + 'domain': 'platform1domain.com', + 'dsaparams': [1] + }, + { + 'domain': 'SSP2domain.com', + 'dsaparams': [1, 2] + } + ] + }; + const invalidDSAObjects = [ + -1, + 0, + '', + 'x', + true, + [], + [1], + {}, + { + 'dsarequired': -1 + }, + { + 'pubrender': -1 + }, + { + 'datatopub': -1 + }, + { + 'dsarequired': 4 + }, + { + 'pubrender': 3 + }, + { + 'datatopub': 3 + }, + { + 'dsarequired': '1' + }, + { + 'pubrender': '1' + }, + { + 'datatopub': '1' + }, + { + 'transparency': '1' + }, + { + 'transparency': 2 + }, + { + 'transparency': [ + 1, 2 + ] + }, + { + 'transparency': [ + { + domain: '', + dsaparams: [] + } + ] + }, + { + 'transparency': [ + { + domain: 'x', + dsaparams: null + } + ] + }, + { + 'transparency': [ + { + domain: 'x', + dsaparams: [1, '2'] + } + ] + }, + ]; + let bidRequest; + + beforeEach(() => { + bidRequest = Object.assign([], bidRequests); + }); + + it('should add dsa information to the request via bidderRequest.ortb2.regs.ext.dsa', function () { + const localBidderRequest = { + ...bidderRequest, + ortb2: { + regs: { + ext: { + dsa: validDSAObject + } + } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + + expect(data).to.have.nested.property('regs.ext.dsa'); + expect(data.regs.ext.dsa.dsarequired).to.equal(3); + expect(data.regs.ext.dsa.pubrender).to.equal(0); + expect(data.regs.ext.dsa.datatopub).to.equal(2); + expect(data.regs.ext.dsa.transparency).to.deep.equal([ + { + 'domain': 'platform1domain.com', + 'dsaparams': [1] + }, + { + 'domain': 'SSP2domain.com', + 'dsaparams': [1, 2] + } + ]); + }); + + invalidDSAObjects.forEach((invalidDSA, index) => { + it(`should not add dsa information to the request via bidderRequest.ortb2.regs.ext.dsa; test# ${index}`, function () { + const localBidderRequest = { + ...bidderRequest, + ortb2: { + regs: { + ext: { + dsa: invalidDSA + } + } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + + expect(data).to.not.have.nested.property('regs.ext.dsa'); + }); + }); + }); + context('FLEDGE', function() { afterEach(function () { config.resetConfig(); @@ -563,17 +710,20 @@ describe('RTBHouseAdapter', () => { }); describe('interpretResponse', function () { - let response = [{ - 'id': 'bidder_imp_identifier', - 'impid': '552b8922e28f27', - 'price': 0.5, - 'adid': 'Ad_Identifier', - 'adm': '', - 'adomain': ['rtbhouse.com'], - 'cid': 'Ad_Identifier', - 'w': 300, - 'h': 250 - }]; + let response; + beforeEach(() => { + response = [{ + 'id': 'bidder_imp_identifier', + 'impid': '552b8922e28f27', + 'price': 0.5, + 'adid': 'Ad_Identifier', + 'adm': '', + 'adomain': ['rtbhouse.com'], + 'cid': 'Ad_Identifier', + 'w': 300, + 'h': 250 + }]; + }); let fledgeResponse = { 'id': 'bid-identifier', @@ -638,6 +788,51 @@ describe('RTBHouseAdapter', () => { }); }); + context('when the response contains DSA object', function () { + it('should get correct bid response', function () { + const dsa = { + 'dsa': { + 'behalf': 'Advertiser', + 'paid': 'Advertiser', + 'transparency': [{ + 'domain': 'dsp1domain.com', + 'dsaparams': [1, 2] + }], + 'adrender': 1 + } + }; + mergeDeep(response[0], { ext: dsa }); + + const expectedResponse = [ + { + 'requestId': '552b8922e28f27', + 'cpm': 0.5, + 'creativeId': 29681110, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['rtbhouse.com'], + ...dsa + }, + 'netRevenue': true, + ext: { ...dsa } + } + ]; + let bidderRequest; + let result = spec.interpretResponse({body: response}, {bidderRequest}); + + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0]).to.have.nested.property('meta.dsa'); + expect(result[0]).to.have.nested.property('ext.dsa'); + expect(result[0].meta.dsa).to.deep.equal(expectedResponse[0].meta.dsa); + expect(result[0].ext.dsa).to.deep.equal(expectedResponse[0].meta.dsa); + }); + }); + describe('native', () => { const adm = { native: { From 57325f2279dfd320591663f72d1eab0cb89d1274 Mon Sep 17 00:00:00 2001 From: The Moneytizer <155010605+themoneytizer@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:58:00 -0500 Subject: [PATCH 460/760] The Moneytizer Bid Adapter: initial release (#11047) --- modules/themoneytizerBidAdapter.js | 102 +++++++ modules/themoneytizerBidAdapter.md | 44 +++ .../modules/themoneytizerBidAdapter_spec.js | 289 ++++++++++++++++++ 3 files changed, 435 insertions(+) create mode 100644 modules/themoneytizerBidAdapter.js create mode 100644 modules/themoneytizerBidAdapter.md create mode 100644 test/spec/modules/themoneytizerBidAdapter_spec.js diff --git a/modules/themoneytizerBidAdapter.js b/modules/themoneytizerBidAdapter.js new file mode 100644 index 00000000000..9f187478fa7 --- /dev/null +++ b/modules/themoneytizerBidAdapter.js @@ -0,0 +1,102 @@ +import { logInfo, logWarn } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'themoneytizer'; +const GVLID = 1265; +const ENDPOINT_URL = 'https://ads.biddertmz.com/m/'; + +export const spec = { + aliases: [BIDDER_CODE], + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + gvlid: GVLID, + + isBidRequestValid: function (bid) { + if (!(bid && bid.params.pid)) { + logWarn('Invalid bid request - missing required bid params'); + return false; + } + + return true; + }, + + buildRequests: function (validBidRequests, bidderRequest) { + return validBidRequests.map((bidRequest) => { + const payload = { + ext: bidRequest.ortb2Imp.ext, + params: bidRequest.params, + size: bidRequest.mediaTypes, + adunit: bidRequest.adUnitCode, + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + ortb2: bidderRequest.ortb2, + eids: bidRequest.userIdAsEids, + id: bidRequest.auctionId, + schain: bidRequest.schain, + version: '$prebid.version$', + excl_sync: window.tmzrBidderExclSync + }; + + const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; + + if (bidderRequest && bidderRequest.refererInfo) { + payload.referer = bidderRequest.refererInfo.topmostLocation; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + if (bidRequest.params.test) { + payload.test = bidRequest.params.test; + } + + payload.userEids = bidRequest.userIdAsEids || []; + + return { + method: 'POST', + url: baseUrl, + data: JSON.stringify(payload), + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + const bidResponses = []; + const response = serverResponse.body; + + if (response && response.bid && !response.timeout && !!response.bid.ad) { + bidResponses.push(response.bid); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return []; + } + + let s = []; + serverResponses.map((c) => { + if (c.body.c_sync) { + c.body.c_sync.bidder_status.map((p) => { + if (p.usersync.type === 'redirect') { + p.usersync.type = 'image'; + } + s.push(p.usersync); + }) + } + }); + + return s; + }, + + onTimeout: function onTimeout(timeoutData) { + logInfo('The Moneytizer - Timeout from adapter', timeoutData); + }, +}; + +registerBidder(spec); diff --git a/modules/themoneytizerBidAdapter.md b/modules/themoneytizerBidAdapter.md new file mode 100644 index 00000000000..5515013575c --- /dev/null +++ b/modules/themoneytizerBidAdapter.md @@ -0,0 +1,44 @@ +# Overview + +``` +Module Name: The Moneytizer Bid Adapter +Module Type: Bidder Adapter +Maintainer: tech@themoneytizer.com +``` + +## Description + +Module that connects to The Moneytizer demand sources + +## Bid Parameters + +| Key | Required | Example | Description | +| --------------- | -------- | ---------------------------------------------| ---------------------------------------| +| `pid` | yes | `12345` | The Moneytizer's publisher token | +| `test` | no | `1` | Set to 1 to receive a test bid response| +| `baseUrl` | no | `'https://custom-endpoint.biddertmz.com/m/'` | Call on custom endpoint | + +## Test parameters + +```js + +var adUnits = [ + { + code: 'your-adunit-code', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, + }, + bids: [ + { + bidder: "themoneytizer", + params: { + pid: -1, + test: 1 + }, + }, + ], + }, +]; +``` diff --git a/test/spec/modules/themoneytizerBidAdapter_spec.js b/test/spec/modules/themoneytizerBidAdapter_spec.js new file mode 100644 index 00000000000..8cff7a57e69 --- /dev/null +++ b/test/spec/modules/themoneytizerBidAdapter_spec.js @@ -0,0 +1,289 @@ +import { spec } from '../../../modules/themoneytizerBidAdapter.js' + +const ENDPOINT_URL = 'https://ads.biddertmz.com/m/'; + +const VALID_BID_BANNER = { + bidder: 'themoneytizer', + ortb2Imp: { + ext: {} + }, + params: { + pid: 123456, + }, + mediaTypes: { + banner: { + sizes: [[970, 250]] + } + }, + adUnitCode: 'ad-unit-code', + bidId: '82376dbe72be72', + timeout: 3000, + ortb2: {}, + userIdAsEids: [], + auctionId: '123456-abcdef-7890', + schain: {}, +} + +const VALID_TEST_BID_BANNER = { + bidder: 'themoneytizer', + ortb2Imp: { + ext: {} + }, + params: { + pid: 123456, + test: 1, + baseUrl: 'https://custom-endpoint.biddertmz.com/m/' + }, + mediaTypes: { + banner: { + sizes: [[970, 250]] + } + }, + adUnitCode: 'ad-unit-code', + bidId: '82376dbe72be72', + timeout: 3000, + ortb2: {}, + userIdAsEids: [], + auctionId: '123456-abcdef-7890', + schain: {} +} + +const BIDDER_REQUEST_BANNER = { + bids: [VALID_BID_BANNER, VALID_TEST_BID_BANNER], + refererInfo: { + topmostLocation: 'http://prebid.org/', + canonicalUrl: 'http://prebid.org/' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'abcdefghxyz' + } +} + +const SERVER_RESPONSE = { + c_sync: { + status: 'ok', + bidder_status: [ + { + bidder: 'bidder-A', + usersync: { + url: 'https://syncurl.com', + type: 'redirect' + } + }, + { + bidder: 'bidder-B', + usersync: { + url: 'https://syncurl2.com', + type: 'image' + } + } + ] + }, + bid: { + requestId: '17750222eb16825', + cpm: 0.098, + currency: 'USD', + width: 300, + height: 600, + creativeId: '44368852571075698202250', + dealId: '', + netRevenue: true, + ttl: 5, + ad: '

This is an ad

', + mediaType: 'banner', + } +}; + +describe('The Moneytizer Bidder Adapter', function () { + describe('codes', function () { + it('should return a bidder code of themoneytizer', function () { + expect(spec.code).to.equal('themoneytizer'); + }); + }); + + describe('gvlid', function () { + it('should expose gvlid', function () { + expect(spec.gvlid).to.equal(1265) + }); + }); + + describe('isBidRequestValid', function () { + it('should return true for a bid with all required fields', function () { + const validBid = spec.isBidRequestValid(VALID_BID_BANNER); + expect(validBid).to.be.true; + }); + + it('should return false for an invalid bid', function () { + const invalidBid = spec.isBidRequestValid(null); + expect(invalidBid).to.be.false; + }); + + it('should return false when params are incomplete', function () { + const bidWithIncompleteParams = { + ...VALID_BID_BANNER, + params: {} + }; + expect(spec.isBidRequestValid(bidWithIncompleteParams)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let requests, request, requests_test, request_test; + + before(function () { + requests = spec.buildRequests([VALID_BID_BANNER], BIDDER_REQUEST_BANNER); + request = requests[0]; + + requests_test = spec.buildRequests([VALID_TEST_BID_BANNER], BIDDER_REQUEST_BANNER); + request_test = requests_test[0]; + }); + + it('should build a request array for valid bids', function () { + expect(requests).to.be.an('array').that.is.not.empty; + }); + + it('should build a request array for valid test bids', function () { + expect(requests_test).to.be.an('array').that.is.not.empty; + }); + + it('should build a request with the correct method, URL, and data type', function () { + expect(request).to.include.keys(['method', 'url', 'data']); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT_URL); + expect(request.data).to.be.a('string'); + }); + + it('should build a test request with the correct method, URL, and data type', function () { + expect(request_test).to.include.keys(['method', 'url', 'data']); + expect(request_test.method).to.equal('POST'); + expect(request_test.url).to.equal(VALID_TEST_BID_BANNER.params.baseUrl); + expect(request_test.data).to.be.a('string'); + }); + + describe('Payload structure', function () { + let payload; + + before(function () { + payload = JSON.parse(request.data); + }); + + it('should have correct payload structure', function () { + expect(payload).to.be.an('object'); + expect(payload.size).to.be.an('object'); + expect(payload.params).to.be.an('object'); + }); + }); + + describe('Payload structure optional params', function () { + let payload; + + before(function () { + payload = JSON.parse(request_test.data); + }); + + it('should have correct params', function () { + expect(payload.params.pid).to.equal(123456); + }); + + it('should have correct referer info', function () { + expect(payload.referer).to.equal(BIDDER_REQUEST_BANNER.refererInfo.topmostLocation); + expect(payload.referer_canonical).to.equal(BIDDER_REQUEST_BANNER.refererInfo.canonicalUrl); + }); + + it('should have correct GDPR consent', function () { + expect(payload.consent_string).to.equal(BIDDER_REQUEST_BANNER.gdprConsent.consentString); + expect(payload.consent_required).to.equal(BIDDER_REQUEST_BANNER.gdprConsent.gdprApplies); + }); + }); + }); + + describe('interpretResponse', function () { + let bidResponse, receivedBid; + const responseBody = SERVER_RESPONSE; + + before(function () { + receivedBid = responseBody.bid; + const response = { body: responseBody }; + bidResponse = spec.interpretResponse(response, null); + }); + + it('should not return an empty response', function () { + expect(bidResponse).to.not.be.empty; + }); + + describe('Parsed Bid Object', function () { + let bid; + + before(function () { + bid = bidResponse[0]; + }); + + it('should not be empty', function () { + expect(bid).to.not.be.empty; + }); + + it('should correctly interpret ad markup', function () { + expect(bid.ad).to.equal(receivedBid.ad); + }); + + it('should correctly interpret CPM', function () { + expect(bid.cpm).to.equal(receivedBid.cpm); + }); + + it('should correctly interpret dimensions', function () { + expect(bid.height).to.equal(receivedBid.height); + expect(bid.width).to.equal(receivedBid.width); + }); + + it('should correctly interpret request ID', function () { + expect(bid.requestId).to.equal(receivedBid.requestId); + }); + }); + }); + + describe('onTimeout', function () { + const timeoutData = [{ + timeout: null + }]; + + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + it('should include timeoutData', function () { + expect(spec.onTimeout(timeoutData)).to.be.undefined; + }) + }); + + describe('getUserSyncs', function () { + const response = { body: SERVER_RESPONSE }; + + it('should have empty user sync with iframeEnabled to false and pixelEnabled to false', function () { + const result = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: false }, [response]); + + expect(result).to.be.empty; + }); + + it('should have user sync with iframeEnabled to true', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, [response]); + + expect(result).to.not.be.empty; + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal(SERVER_RESPONSE.c_sync.bidder_status[0].usersync.url); + }); + + it('should have user sync with pixelEnabled to true', function () { + const result = spec.getUserSyncs({ pixelEnabled: true }, [response]); + + expect(result).to.not.be.empty; + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal(SERVER_RESPONSE.c_sync.bidder_status[0].usersync.url); + }); + + it('should transform type redirect into image', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, [response]); + + expect(result[1].type).to.equal('image'); + }); + }); +}); From 45521111c5a2b6aa28397407b2ab92051c7c8b41 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 15 Feb 2024 21:55:31 +0000 Subject: [PATCH 461/760] Prebid 8.37.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4686462599c..d05031ed335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.37.0-pre", + "version": "8.37.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 1954a8c1294..9406d6d5b3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.37.0-pre", + "version": "8.37.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 11c77af9a7f37b6562b0848cbb3a10b651b2a56f Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 15 Feb 2024 21:55:32 +0000 Subject: [PATCH 462/760] Increment version to 8.38.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d05031ed335..592c1984509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.37.0", + "version": "8.38.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9406d6d5b3c..60a42e6afd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.37.0", + "version": "8.38.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From a0194cf33bfe67f69744d646c32a02ee7447c287 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:38:25 +0100 Subject: [PATCH 463/760] ZetaGlobalSsp Analytics Adapter: keep only needed fields in event (#11107) * ZetaGlobalSspAnalyticsAdapter: keep only needed fields in event * - --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 44 +++++++++++++++--- .../zeta_global_sspAnalyticsAdapter_spec.js | 45 ++++++++++--------- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 751b55ec673..1eb4cab93b0 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -92,14 +92,44 @@ function auctionEndHandler(args) { logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); const event = { - adUnitCodes: args.adUnitCodes, - adUnits: args.adUnits, - auctionEnd: args.auctionEnd, auctionId: args.auctionId, - bidderRequests: args.bidderRequests, - bidsReceived: args.bidsReceived, - noBids: args.noBids, - winningBids: args.winningBids + adUnits: args.adUnits, + bidderRequests: args.bidderRequests?.map(br => ({ + bidderCode: br?.bidderCode, + refererInfo: br?.refererInfo, + bids: br?.bids?.map(b => ({ + adUnitCode: b?.adUnitCode, + auctionId: b?.auctionId, + bidId: b?.bidId, + requestId: b?.requestId, + bidderCode: b?.bidderCode, + mediaTypes: b?.mediaTypes, + sizes: b?.sizes, + bidder: b?.bidder, + params: b?.params + })) + })), + bidsReceived: args.bidsReceived?.map(br => ({ + adId: br?.adId, + adserverTargeting: { + hb_adomain: br?.adserverTargeting?.hb_adomain + }, + cpm: br?.cpm, + creativeId: br?.creativeId, + mediaType: br?.mediaType, + renderer: br?.renderer, + size: br?.size, + timeToRespond: br?.timeToRespond, + adUnitCode: br?.adUnitCode, + auctionId: br?.auctionId, + bidId: br?.bidId, + requestId: br?.requestId, + bidderCode: br?.bidderCode, + mediaTypes: br?.mediaTypes, + sizes: br?.sizes, + bidder: br?.bidder, + params: br?.params + })) } // save zetaParams to cache diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 5194a6a526a..54b61f19506 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -9,7 +9,7 @@ let events = require('src/events'); const EVENTS = { AUCTION_END: { - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'timestamp': 1638441234544, 'auctionEnd': 1638441234784, 'auctionStatus': 'completed', @@ -61,7 +61,7 @@ const EVENTS = { 600 ] ], - 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83' + 'transactionId': '6b29369c' } ], 'adUnitCodes': [ @@ -70,7 +70,7 @@ const EVENTS = { 'bidderRequests': [ { 'bidderCode': 'zeta_global_ssp', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'bidderRequestId': '1207cb49191887', 'bids': [ { @@ -90,7 +90,7 @@ const EVENTS = { } }, 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'transactionId': '6b29369c', 'sizes': [ [ 300, @@ -103,7 +103,7 @@ const EVENTS = { ], 'bidId': '206be9a13236af', 'bidderRequestId': '1207cb49191887', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, @@ -126,7 +126,7 @@ const EVENTS = { }, { 'bidderCode': 'appnexus', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'bidderRequestId': '32b97f0a935422', 'bids': [ { @@ -149,7 +149,7 @@ const EVENTS = { } }, 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'transactionId': '6b29369c', 'sizes': [ [ 300, @@ -162,7 +162,7 @@ const EVENTS = { ], 'bidId': '41badc0e164c758', 'bidderRequestId': '32b97f0a935422', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, @@ -205,7 +205,7 @@ const EVENTS = { } }, 'adUnitCode': '/19968336/header-bid-tag-0', - 'transactionId': '6b29369c-0c2e-414e-be1f-5867aec18d83', + 'transactionId': '6b29369c', 'sizes': [ [ 300, @@ -218,7 +218,7 @@ const EVENTS = { ], 'bidId': '41badc0e164c758', 'bidderRequestId': '32b97f0a935422', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, @@ -243,12 +243,12 @@ const EVENTS = { 'netRevenue': true, 'meta': { 'advertiserDomains': [ - 'viaplay.fi' + 'example.adomain' ] }, 'originalCpm': 2.258302852806723, 'originalCurrency': 'USD', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'responseTimestamp': 1638441234670, 'requestTimestamp': 1638441234547, 'bidder': 'zeta_global_ssp', @@ -268,7 +268,7 @@ const EVENTS = { 'hb_size': '480x320', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'viaplay.fi' + 'hb_adomain': 'example.adomain' } } ], @@ -311,12 +311,12 @@ const EVENTS = { 'netRevenue': true, 'meta': { 'advertiserDomains': [ - 'viaplay.fi' + 'example.adomain' ] }, 'originalCpm': 2.258302852806723, 'originalCurrency': 'USD', - 'auctionId': '75e394d9-ccce-4978-9238-91e6a1ac88a1', + 'auctionId': '75e394d9', 'responseTimestamp': 1638441234670, 'requestTimestamp': 1638441234547, 'bidder': 'zeta_global_ssp', @@ -336,7 +336,7 @@ const EVENTS = { 'hb_size': '480x320', 'hb_source': 'client', 'hb_format': 'banner', - 'hb_adomain': 'viaplay.fi' + 'hb_adomain': 'example.adomain' }, 'status': 'rendered', 'params': [ @@ -409,16 +409,19 @@ describe('Zeta Global SSP Analytics Adapter', function() { const auctionEnd = JSON.parse(requests[0].requestBody); const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd.adUnitCodes[0]).to.be.equal('/19968336/header-bid-tag-0'); + expect(auctionEnd.adUnitCodes).to.be.undefined; expect(auctionEnd.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.auctionEnd).to.be.equal(1638441234784); - expect(auctionEnd.auctionId).to.be.equal('75e394d9-ccce-4978-9238-91e6a1ac88a1'); + expect(auctionEnd.auctionEnd).to.be.undefined; + expect(auctionEnd.auctionId).to.be.equal('75e394d9'); expect(auctionEnd.bidderRequests[0].bidderCode).to.be.equal('zeta_global_ssp'); + expect(auctionEnd.bidderRequests[0].bids[0].bidId).to.be.equal('206be9a13236af'); + expect(auctionEnd.bidderRequests[0].bids[0].adUnitCode).to.be.equal('/19968336/header-bid-tag-0'); expect(auctionEnd.bidsReceived[0].bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.noBids[0].bidder).to.be.equal('appnexus'); + expect(auctionEnd.bidsReceived[0].adserverTargeting.hb_adomain).to.be.equal('example.adomain'); + expect(auctionEnd.bidsReceived[0].auctionId).to.be.equal('75e394d9'); expect(auctionSucceeded.adId).to.be.equal('5759bb3ef7be1e8'); - expect(auctionSucceeded.bid.auctionId).to.be.equal('75e394d9-ccce-4978-9238-91e6a1ac88a1'); + expect(auctionSucceeded.bid.auctionId).to.be.equal('75e394d9'); expect(auctionSucceeded.bid.requestId).to.be.equal('206be9a13236af'); expect(auctionSucceeded.bid.bidderCode).to.be.equal('zeta_global_ssp'); expect(auctionSucceeded.bid.creativeId).to.be.equal('456456456'); From cd328b676e1dc217d6e103ec0baf34fa40c73880 Mon Sep 17 00:00:00 2001 From: Trevor <42976142+trevoradbutler@users.noreply.github.com> Date: Mon, 19 Feb 2024 07:01:34 -0700 Subject: [PATCH 464/760] Add AdButler bid adapter (#11011) --- modules/adbutlerBidAdapter.js | 113 +++++++ modules/adbutlerBidAdapter.md | 31 ++ test/spec/modules/adbutlerBidAdapter_spec.js | 329 +++++++++++++++++++ 3 files changed, 473 insertions(+) create mode 100644 modules/adbutlerBidAdapter.js create mode 100644 modules/adbutlerBidAdapter.md create mode 100644 test/spec/modules/adbutlerBidAdapter_spec.js diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js new file mode 100644 index 00000000000..de430a5c916 --- /dev/null +++ b/modules/adbutlerBidAdapter.js @@ -0,0 +1,113 @@ +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; + +const BIDDER_CODE = 'adbutler'; + +function getTrackingPixelsMarkup(pixelURLs) { + return pixelURLs + .map(pixelURL => ``) + .join(); +} + +export const spec = { + code: BIDDER_CODE, + pageID: Math.floor(Math.random() * 10e6), + aliases: ['divreach', 'doceree'], + supportedMediaTypes: [BANNER], + + isBidRequestValid(bid) { + return !!(bid.params.accountID && bid.params.zoneID); + }, + + buildRequests(validBidRequests) { + const zoneCounters = {}; + + return utils._map(validBidRequests, function (bidRequest) { + const zoneID = bidRequest.params?.zoneID; + + zoneCounters[zoneID] ??= 0; + + const domain = bidRequest.params?.domain ?? 'servedbyadbutler.com'; + const adserveBase = `https://${domain}/adserve`; + const params = { + ...(bidRequest.params?.extra ?? {}), + ID: bidRequest.params?.accountID, + type: 'hbr', + setID: zoneID, + pid: spec.pageID, + place: zoneCounters[zoneID], + kw: bidRequest.params?.keyword, + }; + + const paramsString = Object.entries(params).map(([key, value]) => `${key}=${value}`).join(';'); + const requestURI = `${adserveBase}/;${paramsString};`; + + zoneCounters[zoneID]++; + + return { + method: 'GET', + url: requestURI, + data: {}, + bidRequest, + }; + }); + }, + + interpretResponse(serverResponse, serverRequest) { + const bidObj = serverRequest.bidRequest; + const response = serverResponse.body ?? {}; + + if (!bidObj || response.status !== 'SUCCESS') { + return []; + } + + const width = parseInt(response.width); + const height = parseInt(response.height); + + const sizeValid = (bidObj.mediaTypes?.banner?.sizes ?? []).some(([w, h]) => w === width && h === height); + + if (!sizeValid) { + return []; + } + + const cpm = response.cpm; + const minCPM = bidObj.params?.minCPM ?? null; + const maxCPM = bidObj.params?.maxCPM ?? null; + + if (minCPM !== null && cpm < minCPM) { + return []; + } + + if (maxCPM !== null && cpm > maxCPM) { + return []; + } + + let advertiserDomains = []; + + if (response.advertiser?.domain) { + advertiserDomains.push(response.advertiser.domain); + } + + const bidResponse = { + requestId: bidObj.bidId, + cpm, + currency: 'USD', + width, + height, + ad: response.ad_code + getTrackingPixelsMarkup(response.tracking_pixels), + ttl: 360, + creativeId: response.placement_id, + netRevenue: true, + meta: { + advertiserId: response.advertiser?.id, + advertiserName: response.advertiser?.name, + advertiserDomains, + }, + }; + + return [bidResponse]; + }, +}; + +registerBidder(spec); diff --git a/modules/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md new file mode 100644 index 00000000000..88b5cf64475 --- /dev/null +++ b/modules/adbutlerBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +**Module Name**: AdButler Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: trevor@sparklit.com + +# Description + +Bid Adapter for creating a bid from an AdButler zone. + +# Test Parameters +``` + var adUnits = [ + { + code: 'display-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "adbutler", + params: { + accountID: '181556', + zoneID: '705374', + keyword: 'red', //optional + minCPM: '1.00', //optional + maxCPM: '5.00' //optional + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/adbutlerBidAdapter_spec.js b/test/spec/modules/adbutlerBidAdapter_spec.js new file mode 100644 index 00000000000..6c38de717a3 --- /dev/null +++ b/test/spec/modules/adbutlerBidAdapter_spec.js @@ -0,0 +1,329 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adbutlerBidAdapter.js'; + +describe('AdButler adapter', function () { + let validBidRequests; + + beforeEach(function () { + validBidRequests = [ + { + bidder: 'adbutler', + params: { + accountID: '181556', + zoneID: '705374', + keyword: 'red', + minCPM: '1.00', + maxCPM: '5.00', + }, + placementCode: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729', + }, + ]; + }); + + describe('for requests', function () { + describe('without account ID', function () { + it('rejects the bid', function () { + const invalidBid = { + bidder: 'adbutler', + params: { + zoneID: '210093', + }, + }; + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('without a zone ID', function () { + it('rejects the bid', function () { + const invalidBid = { + bidder: 'adbutler', + params: { + accountID: '167283', + }, + }; + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.equal(false); + }); + }); + + describe('with a valid bid', function () { + describe('with a custom domain', function () { + it('uses the custom domain', function () { + validBidRequests[0].params.domain = 'customadbutlerdomain.com'; + + const requests = spec.buildRequests(validBidRequests); + const requestURL = requests[0].url; + + expect(requestURL).to.have.string('customadbutlerdomain.com'); + }); + }); + + it('accepts the bid', function () { + const validBid = { + bidder: 'adbutler', + params: { + accountID: '167283', + zoneID: '210093', + }, + }; + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.equal(true); + }); + + it('sets default domain', function () { + const requests = spec.buildRequests(validBidRequests); + const request = requests[0]; + + let [domain] = request.url.split('/adserve/'); + + expect(domain).to.equal('https://servedbyadbutler.com'); + }); + + it('sets the keyword parameter', function () { + const requests = spec.buildRequests(validBidRequests); + const requestURL = requests[0].url; + + expect(requestURL).to.have.string(';kw=red;'); + }); + + describe('with extra params', function () { + beforeEach(function() { + validBidRequests[0].params.extra = { + foo: 'bar', + }; + }); + + it('sets the extra parameter', function () { + const requests = spec.buildRequests(validBidRequests); + const requestURL = requests[0].url; + + expect(requestURL).to.have.string(';foo=bar;'); + }); + }); + + describe('with multiple bids to the same zone', function () { + it('increments the place count', function () { + const requests = spec.buildRequests([validBidRequests[0], validBidRequests[0]]); + const firstRequest = requests[0].url; + const secondRequest = requests[1].url; + + expect(firstRequest).to.have.string(';place=0;'); + expect(secondRequest).to.have.string(';place=1;'); + }); + }); + }); + }); + + describe('for server responses', function () { + let serverResponse; + + describe('with no body', function () { + beforeEach(function() { + serverResponse = { + body: null, + }; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with an incorrect size', function () { + beforeEach(function() { + serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210083, + cpm: 1.5, + width: 728, + height: 90, + place: 0, + }, + }; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with a failed status', function () { + beforeEach(function() { + serverResponse = { + body: { + status: 'NO_ELIGIBLE_ADS', + zone_id: 210083, + width: 300, + height: 250, + place: 0, + }, + }; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with low CPM', function () { + beforeEach(function() { + serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 0.75, + width: 300, + height: 250, + place: 0, + ad_code: '', + tracking_pixels: [], + }, + } + }); + + describe('with a minimum CPM', function () { + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + expect(bids).to.be.length(0); + }); + }); + + describe('with no minimum CPM', function () { + beforeEach(function() { + delete validBidRequests[0].params.minCPM; + }); + + it('returns a bid', function() { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(1); + }); + }); + }); + + describe('with high CPM', function () { + beforeEach(function() { + serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 999, + width: 300, + height: 250, + place: 0, + ad_code: '', + tracking_pixels: [], + }, + } + }); + + describe('with a maximum CPM', function () { + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with no maximum CPM', function () { + beforeEach(function() { + delete validBidRequests[0].params.maxCPM; + }); + + it('returns a bid', function() { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(1); + }); + }); + }); + + describe('with a valid ad', function () { + beforeEach(function() { + serverResponse = { + body: { + status: 'SUCCESS', + account_id: 167283, + zone_id: 210093, + cpm: 1.5, + width: 300, + height: 250, + place: 0, + ad_code: '', + tracking_pixels: [ + 'http://tracking.pixel.com/params=info', + ], + }, + }; + }); + + it('returns a complete bid', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(1); + expect(bids[0].cpm).to.equal(1.5); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[0].ad).to.have.length.above(1); + expect(bids[0].ad).to.have.string('http://tracking.pixel.com/params=info'); + }); + + describe('for a bid request without banner media type', function () { + beforeEach(function() { + delete validBidRequests[0].mediaTypes.banner; + }); + + it('does not return any bids', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(0); + }); + }); + + describe('with advertiser meta', function () { + beforeEach(function() { + serverResponse.body.advertiser = { + id: 123, + name: 'Advertiser Name', + domain: 'advertiser.com', + }; + }); + + it('returns a bid including advertiser meta', function () { + const bids = spec.interpretResponse(serverResponse, { bidRequest: validBidRequests[0] }); + + expect(bids).to.be.length(1); + expect(bids[0]).to.have.property('meta'); + expect(bids[0].meta.advertiserId).to.equal(123); + expect(bids[0].meta.advertiserName).to.equal('Advertiser Name'); + expect(bids[0].meta.advertiserDomains).to.contain('advertiser.com'); + }); + }); + }); + }); +}); From aaf295103387e0e900a6d8a79a425e321eb2b5b1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Mon, 19 Feb 2024 16:50:26 +0100 Subject: [PATCH 465/760] define split between exploratory and non exploratory sides of the deterministic sampling hash (#11104) --- modules/greenbidsAnalyticsAdapter.js | 20 ++++++++++++++++--- .../modules/greenbidsAnalyticsAdapter_spec.js | 16 +++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index 9b23dba01b9..f60dee99efe 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -27,18 +27,24 @@ export const BIDDER_STATUS = { const analyticsOptions = {}; -export const isSampled = function(greenbidsId, samplingRate) { +export const isSampled = function(greenbidsId, samplingRate, exploratorySamplingSplit) { if (samplingRate < 0 || samplingRate > 1) { logWarn('Sampling rate must be between 0 and 1'); return true; } + const exploratorySamplingRate = samplingRate * exploratorySamplingSplit; + const throttledSamplingRate = samplingRate * (1.0 - exploratorySamplingSplit); const hashInt = parseInt(greenbidsId.slice(-4), 16); - return hashInt < samplingRate * (0xFFFF + 1); + const isPrimarySampled = hashInt < exploratorySamplingRate * (0xFFFF + 1); + if (isPrimarySampled) return true; + const isExtraSampled = hashInt >= (1 - throttledSamplingRate) * (0xFFFF + 1); + return isExtraSampled; } export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), { cachedAuctions: {}, + exploratorySamplingSplit: 0.9, initConfig(config) { analyticsOptions.options = deepClone(config.options); @@ -68,6 +74,14 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER analyticsOptions.options.greenbidsSampling = 1; } + /** + * Add optional debug parameter to override exploratorySamplingSplit + */ + if (typeof analyticsOptions.options.exploratorySamplingSplit === 'number') { + logInfo('Greenbids Analytics: Overriding "exploratorySamplingSplit".'); + this.exploratorySamplingSplit = analyticsOptions.options.exploratorySamplingSplit; + } + analyticsOptions.pbuid = config.options.pbuid analyticsOptions.server = ANALYTICS_SERVER; @@ -172,7 +186,7 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER logInfo("Couldn't find Greenbids RTD info, assuming analytics only"); cachedAuction.greenbidsId = generateUUID(); } - cachedAuction.isSampled = isSampled(cachedAuction.greenbidsId, analyticsOptions.options.greenbidsSampling); + cachedAuction.isSampled = isSampled(cachedAuction.greenbidsId, analyticsOptions.options.greenbidsSampling, this.exploratorySamplingSplit); }, handleAuctionEnd(auctionEndArgs) { const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 30361ca5661..7b68b0dea46 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -404,16 +404,24 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { describe('isSampled', function() { it('should return true for invalid sampling rates', function() { - expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', -1)).to.be.true; - expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 1.2)).to.be.true; + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', -1, 0.0)).to.be.true; + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 1.2, 0.0)).to.be.true; }); it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function() { - expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001)).to.be.false; + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.false; }); it('should return determinist true value for valid sampling rate given the predifined id and rate', function() { - expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999)).to.be.true; + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0)).to.be.true; + }); + + it('should return determinist true value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0, 1.0)).to.be.true; + }); + + it('should return determinist false value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0, 1.0)).to.be.false; }); }); }); From 67210fa06895b4cac33932b0d343bb8dec2329cf Mon Sep 17 00:00:00 2001 From: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:55:47 +0200 Subject: [PATCH 466/760] Taboola Bid Adapter: fix cookie look up logic and gpp extracting (#11109) * cookie-look-up-logic-fix-gpp-fix * pass-version --- modules/taboolaBidAdapter.js | 16 +++- test/spec/modules/taboolaBidAdapter_spec.js | 101 +++++++++++++++++++- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 3b83514b6fd..f0f11ea113e 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -18,6 +18,7 @@ const USER_ID = 'user-id'; const STORAGE_KEY = `taboola global:${USER_ID}`; const COOKIE_KEY = 'trc_cookie_storage'; const TGID_COOKIE_KEY = 't_gid'; +const TGID_PT_COOKIE_KEY = 't_pt_gid'; const TBLA_ID_COOKIE_KEY = 'tbla_id'; export const EVENT_ENDPOINT = 'https://beacon.bidder.taboola.com'; @@ -43,7 +44,10 @@ export const userData = { const {cookiesAreEnabled, getCookie} = userData.storageManager; if (cookiesAreEnabled()) { const cookieData = getCookie(COOKIE_KEY); - let userId = userData.getCookieDataByKey(cookieData, USER_ID); + let userId; + if (cookieData) { + userId = userData.getCookieDataByKey(cookieData, USER_ID); + } if (userId) { return userId; } @@ -51,6 +55,10 @@ export const userData = { if (userId) { return userId; } + userId = getCookie(TGID_PT_COOKIE_KEY); + if (userId) { + return userId; + } const tblaId = getCookie(TBLA_ID_COOKIE_KEY); if (tblaId) { return tblaId; @@ -58,6 +66,9 @@ export const userData = { } }, getCookieDataByKey(cookieData, key) { + if (!cookieData) { + return undefined; + } const [, value = ''] = cookieData.split(`${key}=`) return value; }, @@ -166,7 +177,7 @@ export const spec = { } if (gppConsent) { - queryParams.push('gpp=' + encodeURIComponent(gppConsent)); + queryParams.push('gpp=' + encodeURIComponent(gppConsent.gppString || '') + '&gpp_sid=' + encodeURIComponent((gppConsent.applicableSections || []).join(','))); } if (syncOptions.iframeEnabled) { @@ -258,6 +269,7 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data) { data.user = user; data.regs = regs; deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType); + deepSetValue(data, 'ext.prebid.version', '$prebid.version$'); } function fillTaboolaImpData(bid, imp) { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index dd91c410d08..8a121865cf2 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -6,6 +6,10 @@ import {server} from '../../mocks/xhr' describe('Taboola Adapter', function () { let sandbox, hasLocalStorage, cookiesAreEnabled, getDataFromLocalStorage, localStorageIsEnabled, getCookie, commonBidRequest; + const COOKIE_KEY = 'trc_cookie_storage'; + const TGID_COOKIE_KEY = 't_gid'; + const TGID_PT_COOKIE_KEY = 't_pt_gid'; + const TBLA_ID_COOKIE_KEY = 'tbla_id'; beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -214,7 +218,11 @@ describe('Taboola Adapter', function () { 'ext': {}, }, 'regs': {'coppa': 0, 'ext': {}}, - 'ext': {} + 'ext': { + 'prebid': { + 'version': '$prebid.version$' + } + } }; expect(res.url).to.equal(`${END_POINT_URL}?publisher=${commonBidRequest.params.publisherId}`); @@ -471,6 +479,90 @@ describe('Taboola Adapter', function () { expect(res.data.user.buyeruid).to.equal('12121212'); }); + it('should get user id from cookie if local storage isn`t defined, only TGID_COOKIE_KEY exists', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.callsFake(function (cookieKey) { + if (cookieKey === COOKIE_KEY) { + return 'should:not:return:this'; + } + if (cookieKey === TGID_COOKIE_KEY) { + return 'user:12121212'; + } + return undefined; + }); + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.buyeruid).to.equal('user:12121212'); + }); + + it('should get user id from cookie if local storage isn`t defined, only TGID_PT_COOKIE_KEY exists', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.callsFake(function (cookieKey) { + if (cookieKey === TGID_PT_COOKIE_KEY) { + return 'user:12121212'; + } + return undefined; + }); + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.buyeruid).to.equal('user:12121212'); + }); + + it('should get user id from cookie if local storage isn`t defined, only TBLA_ID_COOKIE_KEY exists', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.callsFake(function (cookieKey) { + if (cookieKey === TBLA_ID_COOKIE_KEY) { + return 'user:tbla:12121212'; + } + return undefined; + }); + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.buyeruid).to.equal('user:tbla:12121212'); + }); + + it('should get user id from cookie if local storage isn`t defined, all cookie keys exist', function () { + getDataFromLocalStorage.returns(51525152); + hasLocalStorage.returns(false); + localStorageIsEnabled.returns(false); + cookiesAreEnabled.returns(true); + getCookie.callsFake(function (cookieKey) { + if (cookieKey === COOKIE_KEY) { + return 'taboola%20global%3Auser-id=cookie:1'; + } + if (cookieKey === TGID_COOKIE_KEY) { + return 'cookie:2'; + } + if (cookieKey === TGID_PT_COOKIE_KEY) { + return 'cookie:3'; + } + if (cookieKey === TBLA_ID_COOKIE_KEY) { + return 'cookie:4'; + } + return undefined; + }); + const bidderRequest = { + ...commonBidderRequest + }; + const res = spec.buildRequests([defaultBidRequest], bidderRequest); + expect(res.data.user.buyeruid).to.equal('cookie:1'); + }); + it('should get user id from tgid cookie if local storage isn`t defined', function () { getDataFromLocalStorage.returns(51525152); hasLocalStorage.returns(false); @@ -892,8 +984,11 @@ describe('Taboola Adapter', function () { expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT')).to.deep.equal([{ type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT` }]); - expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', 'GPP_STRING')).to.deep.equal([{ - type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING` + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', {gppString: 'GPP_STRING', applicableSections: []})).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=` + }]); + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined, 'USP_CONSENT', {gppString: 'GPP_STRING', applicableSections: [32, 51]})).to.deep.equal([{ + type: 'image', url: `${usersyncUrl}?us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51` }]); }); }) From 969e417cf43f8a3f9dd5981c580f17c5913baab2 Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:24:03 +0900 Subject: [PATCH 467/760] add schain support (#11111) --- modules/ajaBidAdapter.js | 31 +++++++++++++++++++++---- test/spec/modules/ajaBidAdapter_spec.js | 27 +++++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index fcbe0c12c6e..e02ab920707 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -71,10 +71,8 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'cdep', bidRequest.ortb2?.device?.ext?.cdep) queryString = tryAppendQueryString(queryString, 'prebid_id', bidRequest.bidId); queryString = tryAppendQueryString(queryString, 'prebid_ver', '$prebid.version$'); - - if (pageUrl) { - queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); - } + queryString = tryAppendQueryString(queryString, 'page_url', pageUrl); + queryString = tryAppendQueryString(queryString, 'schain', spec.serializeSupplyChain(bidRequest.schain || [])) const adFormatIDs = pickAdFormats(bidRequest) if (adFormatIDs && adFormatIDs.length > 0) { @@ -172,6 +170,31 @@ export const spec = { return syncs; }, + + /** + * Serialize supply chain object + * @param {Object} supplyChain + * @returns {String | undefined} + */ + serializeSupplyChain: function(supplyChain) { + if (!supplyChain || !supplyChain.nodes) return undefined + const { ver, complete, nodes } = supplyChain + return `${ver},${complete}!${spec.serializeSupplyChainNodes(nodes)}` + }, + + /** + * Serialize each supply chain nodes + * @param {Array} nodes + * @returns {String} + */ + serializeSupplyChainNodes: function(nodes) { + const fields = ['asi', 'sid', 'hp', 'rid', 'name', 'domain'] + return nodes.map((n) => { + return fields.map((f) => { + return encodeURIComponent(n[f] || '').replace(/!/g, '%21') + }).join(',') + }).join('!') + } } function pickAdFormats(bidRequest) { diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index 3137c9dc24e..dbc72d113f4 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -73,9 +73,32 @@ describe('AjaAdapter', function () { tid: 'cea1eb09-d970-48dc-8585-634d3a7b0544', gpid: '/1111/homepage#300x250' } - } + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'publisher', + domain: 'publisher.com' + }, + { + asi: 'exchange2.com', + sid: 'abcd', + hp: 1, + rid: 'bid-request-2', + name: 'intermediary', + domain: 'intermediary.com' + } + ] + }, } ]; + const serializedSchain = encodeURIComponent('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com') const bidderRequest = { refererInfo: { @@ -87,7 +110,7 @@ describe('AjaAdapter', function () { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests[0].url).to.equal(ENDPOINT); expect(requests[0].method).to.equal('GET'); - expect(requests[0].data).to.equal('asi=123456&skt=5&gpid=%2F1111%2Fhomepage%23300x250&tid=cea1eb09-d970-48dc-8585-634d3a7b0544&cdep=example_label_1&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&ad_format_ids=2&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&'); + expect(requests[0].data).to.equal(`asi=123456&skt=5&gpid=%2F1111%2Fhomepage%23300x250&tid=cea1eb09-d970-48dc-8585-634d3a7b0544&cdep=example_label_1&prebid_id=30b31c1838de1e&prebid_ver=$prebid.version$&page_url=https%3A%2F%2Fhoge.com&schain=${serializedSchain}&ad_format_ids=2&sua=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22Android%22%2C%22version%22%3A%5B%228%22%2C%220%22%2C%220%22%5D%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Not_A%20Brand%22%2C%22version%22%3A%5B%2299%22%2C%220%22%2C%220%22%2C%220%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%2C%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22109%22%2C%220%22%2C%225414%22%2C%22119%22%5D%7D%5D%2C%22mobile%22%3A1%2C%22model%22%3A%22SM-G955U%22%2C%22bitness%22%3A%2264%22%2C%22architecture%22%3A%22%22%7D&`); }); }); From 8cce0b8a4c53eafffafb0bbabd0e6b57537effd5 Mon Sep 17 00:00:00 2001 From: readpeak-user <63640438+readpeak-user@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:43:19 +0200 Subject: [PATCH 468/760] Readpeak Bid Adapter : remove click url encoding (#11120) * Add banner support to readpeak bid adapter * Add onBidWon callback to trigger burl * Remove .only from test * Fix merge * Revert package-lock.json version update * Remove encoding of click target URL --------- Co-authored-by: Tuomo Tilli Co-authored-by: readpeaktuomo <66239046+readpeaktuomo@users.noreply.github.com> --- modules/readpeakBidAdapter.js | 2 +- test/spec/modules/readpeakBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 718d6504b56..4ff51aeb43e 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -349,7 +349,7 @@ function nativeResponse(imp, bid) { keys.cta = asset.data && asset.id === 5 ? asset.data.value : keys.cta; }); if (nativeAd.link) { - keys.clickUrl = encodeURIComponent(nativeAd.link.url); + keys.clickUrl = nativeAd.link.url; } const trackers = nativeAd.imptrackers || []; trackers.unshift(replaceAuctionPrice(bid.burl, bid.price)); diff --git a/test/spec/modules/readpeakBidAdapter_spec.js b/test/spec/modules/readpeakBidAdapter_spec.js index 8772aeac88f..32a4d991054 100644 --- a/test/spec/modules/readpeakBidAdapter_spec.js +++ b/test/spec/modules/readpeakBidAdapter_spec.js @@ -376,7 +376,7 @@ describe('ReadPeakAdapter', function() { height: 500 }); expect(bidResponse.native.clickUrl).to.equal( - 'http%3A%2F%2Furl.to%2Ftarget' + 'http://url.to/target' ); expect(bidResponse.native.impressionTrackers).to.contain( 'http://url.to/pixeltracker' From 7995508149f460b9660a4525634c0570ee99f632 Mon Sep 17 00:00:00 2001 From: Serhii Mozhaiskyi <57671975+smozhaiskyi-rubi@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:55:09 +0200 Subject: [PATCH 469/760] fix handling of default settings for rubiconBidAdapter (#11114) Co-authored-by: Serhii Mozhaiskyi --- modules/rubiconBidAdapter.js | 19 +++++++++++++------ test/spec/modules/rubiconBidAdapter_spec.js | 10 ++++++---- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 813db65717e..973c6660ece 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -829,7 +829,14 @@ function renderBid(bid) { hideSmartAdServerIframe(adUnitElement); // configure renderer - const config = bid.renderer.getConfig(); + const defaultConfig = { + align: 'center', + position: 'append', + closeButton: false, + label: undefined, + collapse: true + }; + const config = { ...defaultConfig, ...bid.renderer.getConfig() }; bid.renderer.push(() => { window.MagniteApex.renderAd({ width: bid.width, @@ -837,12 +844,12 @@ function renderBid(bid) { vastUrl: bid.vastUrl, placement: { attachTo: adUnitElement, - align: config.align || 'center', - position: config.position || 'append' + align: config.align, + position: config.position }, - closeButton: config.closeButton || false, - label: config.label || undefined, - collapse: config.collapse || true + closeButton: config.closeButton, + label: config.label, + collapse: config.collapse }); }); } diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 62e494ea295..0312114e75b 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -3899,7 +3899,8 @@ describe('the rubicon adapter', function () { config.setConfig({rubicon: { rendererConfig: { align: 'left', - closeButton: true + closeButton: true, + collapse: false }, rendererUrl: 'https://example.test/renderer.js' }}); @@ -3971,7 +3972,8 @@ describe('the rubicon adapter', function () { expect(typeof bids[0].renderer).to.equal('object'); expect(bids[0].renderer.getConfig()).to.deep.equal({ align: 'left', - closeButton: true + closeButton: true, + collapse: false }); expect(bids[0].renderer.url).to.equal('https://example.test/renderer.js'); }); @@ -4025,7 +4027,7 @@ describe('the rubicon adapter', function () { const renderCall = window.MagniteApex.renderAd.getCall(0); expect(renderCall.args[0]).to.deep.equal({ closeButton: true, - collapse: true, + collapse: false, height: 320, label: undefined, placement: { @@ -4094,7 +4096,7 @@ describe('the rubicon adapter', function () { const renderCall = window.MagniteApex.renderAd.getCall(0); expect(renderCall.args[0]).to.deep.equal({ closeButton: true, - collapse: true, + collapse: false, height: 480, label: undefined, placement: { From be08dc842a7d5a12ba60fe17a1fd11984cf29e2c Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:56:03 +0100 Subject: [PATCH 470/760] Zeta Global Ssp Adapter: remove null values from payload (#11092) * ZetaGlobalSsp: remove null values from payload * unit test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 4 ++++ .../modules/zeta_global_sspBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 295108f31b4..af73865c484 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -381,6 +381,10 @@ function provideMediaType(zetaBid, bid, bidRequest) { function clearEmpties(o) { for (let k in o) { + if (o[k] === null) { + delete o[k]; + continue; + } if (!o[k] || typeof o[k] !== 'object') { continue; } diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index b06a6700ebf..81617b93d3c 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -49,6 +49,12 @@ describe('Zeta Ssp Bid Adapter', function () { }, tags: { someTag: 444, + emptyTag: {}, + nullTag: null, + complexEmptyTag: { + empty: {}, + nullValue: null + } }, sid: 'publisherId', tagid: 'test_tag_id', @@ -657,4 +663,15 @@ describe('Zeta Ssp Bid Adapter', function () { expect(payload.device.w).to.not.be.undefined; expect(payload.device.h).to.not.be.undefined; }); + + it('Test that all empties are removed', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const payload = JSON.parse(request.data); + + expect(payload.ext.tags.someTag).to.eql(444); + + expect(payload.ext.tags.emptyTag).to.be.undefined; + expect(payload.ext.tags.nullTag).to.be.undefined; + expect(payload.ext.tags.complexEmptyTag).to.be.undefined; + }); }); From 0606e77e0baa9cb75e819412154c0a40fff6abc3 Mon Sep 17 00:00:00 2001 From: Solta Date: Tue, 20 Feb 2024 19:02:24 +0300 Subject: [PATCH 471/760] Kimberlite Bidder Adapter: initial commit (#11032) * Kimberlite bid adapter (#1) * initial: bid adapter * styling * Fix: lint (#2) * Fix: lint (#4) * review fixes (#6) * Change: filling request.ext.prebid section (#7) --------- Co-authored-by: Oleg --- modules/kimberliteBidAdapter.js | 71 ++++++++ modules/kimberliteBidAdapter.md | 36 ++++ .../spec/modules/kimberliteBidAdapter_spec.js | 171 ++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 modules/kimberliteBidAdapter.js create mode 100644 modules/kimberliteBidAdapter.md create mode 100644 test/spec/modules/kimberliteBidAdapter_spec.js diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js new file mode 100644 index 00000000000..72df921e18f --- /dev/null +++ b/modules/kimberliteBidAdapter.js @@ -0,0 +1,71 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { deepSetValue } from '../src/utils.js'; + +const VERSION = '1.0.0'; + +const BIDDER_CODE = 'kimberlite'; +const METHOD = 'POST'; +const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; + +const VERSION_INFO = { + ver: '$prebid.version$', + adapterVer: `${VERSION}` +}; + +const converter = ortbConverter({ + context: { + mediaType: BANNER, + netRevenue: true, + ttl: 300 + }, + + request(buildRequest, imps, bidderRequest, context) { + const bidRequest = buildRequest(imps, bidderRequest, context); + deepSetValue(bidRequest, 'site.publisher.domain', bidderRequest.refererInfo.domain); + deepSetValue(bidRequest, 'site.page', bidderRequest.refererInfo.page); + deepSetValue(bidRequest, 'ext.prebid.ver', VERSION_INFO.ver); + deepSetValue(bidRequest, 'ext.prebid.adapterVer', VERSION_INFO.adapterVer); + bidRequest.at = 1; + return bidRequest; + }, + + imp (buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + imp.tagid = bidRequest.params.placementId; + return imp; + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: (bidRequest = {}) => { + const { params, mediaTypes } = bidRequest; + let isValid = Boolean(params && params.placementId); + if (mediaTypes && mediaTypes[BANNER]) { + isValid = isValid && Boolean(mediaTypes[BANNER].sizes); + } else { + isValid = false; + } + + return isValid; + }, + + buildRequests: function (bidRequests, bidderRequest) { + return { + method: METHOD, + url: ENDPOINT_URL, + data: converter.toORTB({ bidderRequest, bidRequests }) + } + }, + + interpretResponse(serverResponse, bidRequest) { + const bids = converter.fromORTB({response: serverResponse.body, request: bidRequest.data}).bids; + return bids; + } +}; + +registerBidder(spec); diff --git a/modules/kimberliteBidAdapter.md b/modules/kimberliteBidAdapter.md new file mode 100644 index 00000000000..c165f1073aa --- /dev/null +++ b/modules/kimberliteBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +```markdown +Module Name: Kimberlite Bid Adapter +Module Type: Bidder Adapter +Maintainer: dev@solta.io +``` + +# Description + +Kimberlite exchange adapter. + +# Test Parameters + +## Banner AdUnit + +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[320, 250], [640, 480]], + } + }, + bids: [ + { + bidder: "kimberlite", + params: { + placementId: 'testBanner' + } + } + ] + } +] +``` diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js new file mode 100644 index 00000000000..1480f1cc768 --- /dev/null +++ b/test/spec/modules/kimberliteBidAdapter_spec.js @@ -0,0 +1,171 @@ +import { spec } from 'modules/kimberliteBidAdapter.js'; +import { assert } from 'chai'; +import { BANNER } from '../../../src/mediaTypes.js'; + +const BIDDER_CODE = 'kimberlite'; + +describe('kimberliteBidAdapter', function () { + const sizes = [[640, 480]]; + + describe('isBidRequestValid', function () { + let bidRequest; + + beforeEach(function () { + bidRequest = { + mediaTypes: { + [BANNER]: { + sizes: [[320, 240]] + } + }, + params: { + placementId: 'test-placement' + } + }; + }); + + it('pass on valid bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequest)); + }); + + it('fails on missed placementId', function () { + delete bidRequest.params.placementId; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + + it('fails on empty banner', function () { + delete bidRequest.mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + + it('fails on empty banner.sizes', function () { + delete bidRequest.mediaTypes.banner.sizes; + assert.isFalse(spec.isBidRequestValid(bidRequest)); + }); + + it('fails on empty request', function () { + assert.isFalse(spec.isBidRequestValid()); + }); + }); + + describe('buildRequests', function () { + let bidRequests, bidderRequest; + + beforeEach(function () { + bidRequests = [{ + mediaTypes: { + [BANNER]: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } + }]; + + bidderRequest = { + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [{ + mediaTypes: { + [BANNER]: {sizes: sizes} + } + }] + }; + }); + + it('valid bid request', function () { + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.ok(bidRequest.data); + + const requestData = bidRequest.data; + expect(requestData.site.page).to.equal(bidderRequest.refererInfo.page); + expect(requestData.site.publisher.domain).to.equal(bidderRequest.refererInfo.domain); + + expect(requestData.imp).to.be.an('array').and.is.not.empty; + + expect(requestData.ext).to.be.an('Object').and.have.all.keys('prebid'); + expect(requestData.ext.prebid).to.be.an('Object').and.have.all.keys('ver', 'adapterVer'); + + const impData = requestData.imp[0]; + expect(impData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); + + const bannerData = impData.banner; + expect(bannerData.format).to.be.an('array').and.is.not.empty; + + const formatData = bannerData.format[0]; + expect(formatData).to.be.an('Object').and.have.all.keys('w', 'h'); + + assert.equal(formatData.w, sizes[0][0]); + assert.equal(formatData.h, sizes[0][1]); + }); + }); + + describe('interpretResponse', function () { + let bidderResponse, bidderRequest, bidRequest, expectedBid; + + const requestId = '07fba8b0-8812-4dc6-b91e-4a525d81729c'; + const bidId = '222209853178'; + const impId = 'imp-id'; + const crId = 'creative-id'; + const adm = 'landing'; + + beforeEach(function () { + bidderResponse = { + body: { + id: requestId, + seatbid: [{ + bid: [{ + crid: crId, + id: bidId, + impid: impId, + price: 1, + adm: adm + }] + }] + } + }; + + bidderRequest = { + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', + }, + bids: [{ + bidId: impId, + mediaTypes: { + [BANNER]: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } + }] + }; + + expectedBid = { + mediaType: 'banner', + requestId: 'imp-id', + seatBidId: '222209853178', + cpm: 1, + creative_id: 'creative-id', + creativeId: 'creative-id', + ttl: 300, + netRevenue: true, + ad: adm, + meta: {} + }; + + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + }); + + it('pass on valid request', function () { + const bids = spec.interpretResponse(bidderResponse, bidRequest); + assert.deepEqual(bids[0], expectedBid); + }); + + it('fails on empty response', function () { + const bids = spec.interpretResponse({body: ''}, bidRequest); + assert.empty(bids); + }); + }); +}); From 6ecd4a99b52fcac4d6e03ce115e8df602683be51 Mon Sep 17 00:00:00 2001 From: Wiem Zine El Abidine Date: Tue, 20 Feb 2024 18:06:25 +0100 Subject: [PATCH 472/760] Pass TTD cookie through prebid endpoint (#11119) --- modules/liveIntentIdSystem.js | 17 ++++++++++ modules/userId/eids.md | 11 +++++++ test/spec/modules/eids_spec.js | 33 +++++++++++++++++++ .../modules/liveIntentIdMinimalSystem_spec.js | 27 ++++++++++----- test/spec/modules/liveIntentIdSystem_spec.js | 12 +++++++ 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index ad71a584e42..4e0a62cca0a 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -12,6 +12,7 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; +import { getRefererInfo } from '../src/refererDetection.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -234,6 +235,10 @@ export const liveIntentIdSubmodule = { result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } } + if (value.thetradedesk) { + result.thetradedesk = { 'id': value.thetradedesk, ext: { provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } + } + return result } @@ -371,6 +376,18 @@ 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/userId/eids.md b/modules/userId/eids.md index c8d27cc9d52..11400f4007f 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -172,6 +172,17 @@ userIdAsEids = [ } }] }, + + { + source: 'adserver.org', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { + provider: 'liveintent.com' + } + }] + }, { source: 'rubiconproject.com', diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index 25e70f12ced..b27775bb887 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -403,6 +403,39 @@ 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/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index ad21d7b6763..6002e827593 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -2,6 +2,7 @@ import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; +import * as refererDetection from '../../../src/refererDetection.js'; const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID} }; @@ -14,6 +15,7 @@ describe('LiveIntentMinimalId', function() { let getCookieStub; let getDataFromLocalStorageStub; let imgStub; + let refererInfoStub; beforeEach(function() { liveIntentIdSubmodule.setModuleMode('minimal'); @@ -23,6 +25,7 @@ describe('LiveIntentMinimalId', function() { logErrorStub = sinon.stub(utils, 'logError'); uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); }); afterEach(function() { @@ -32,6 +35,7 @@ describe('LiveIntentMinimalId', function() { logErrorStub.restore(); uspConsentDataStub.restore(); gdprConsentDataStub.restore(); + refererInfoStub.restore(); liveIntentIdSubmodule.setModuleMode('minimal'); resetLiveIntentIdSubmodule(); }); @@ -241,7 +245,7 @@ describe('LiveIntentMinimalId', function() { expect(callBackSpy.calledOnce).to.be.true; }); - it('should decode a uid2 to a seperate object when present', function() { + it('should decode a uid2 to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); @@ -251,41 +255,48 @@ describe('LiveIntentMinimalId', function() { expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a bidswitch id to a seperate object when present', function() { + it('should decode a bidswitch id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a medianet id to a seperate object when present', function() { + it('should decode a medianet id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a sovrn id to a seperate object when present', function() { + it('should decode a sovrn id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode a magnite id to a seperate object when present', function() { + it('should decode a magnite id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an index id to a seperate object when present', function() { + it('should decode an index id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an openx id to a seperate object when present', function () { + it('should decode an openx id to a separate object when present', function () { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); - it('should decode an pubmatic id to a seperate object when present', function() { + it('should decode an pubmatic id to a separate object when present', function() { const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a thetradedesk id to a separate object when present', 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}}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 3af598c5d4e..23f7bcda36b 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -2,6 +2,8 @@ import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } f import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; +import * as refererDetection from '../../../src/refererDetection.js'; + resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; @@ -16,6 +18,7 @@ describe('LiveIntentId', function() { let getCookieStub; let getDataFromLocalStorageStub; let imgStub; + let refererInfoStub; beforeEach(function() { liveIntentIdSubmodule.setModuleMode('standard'); @@ -26,6 +29,7 @@ describe('LiveIntentId', function() { uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); }); afterEach(function() { @@ -36,6 +40,7 @@ describe('LiveIntentId', function() { uspConsentDataStub.restore(); gdprConsentDataStub.restore(); gppConsentDataStub.restore(); + refererInfoStub.restore(); resetLiveIntentIdSubmodule(); }); @@ -423,6 +428,13 @@ describe('LiveIntentId', function() { expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); + it('should decode a thetradedesk id to a separate object when present', 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}}}); + }); + it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { From ff58535df6f60c2289e73ea4b832aef8afed046e Mon Sep 17 00:00:00 2001 From: Scott Sundahl <37344964+ssundahlTTD@users.noreply.github.com> Date: Tue, 20 Feb 2024 10:41:08 -0700 Subject: [PATCH 473/760] Euid id module: cstg opt out enforcement (#11075) * initial technical implementation * initial technical implementation * test and doc update * optout check in encrypted payload * fixed cstg example config --- integrationExamples/gpt/cstg_example.html | 317 ++++++++++++++++++++++ modules/euidIdSystem.js | 12 +- modules/euidIdSystem.md | 9 +- modules/uid2IdSystem_shared.js | 18 +- test/spec/modules/euidIdSystem_spec.js | 14 + 5 files changed, 360 insertions(+), 10 deletions(-) create mode 100644 integrationExamples/gpt/cstg_example.html diff --git a/integrationExamples/gpt/cstg_example.html b/integrationExamples/gpt/cstg_example.html new file mode 100644 index 00000000000..8ca049a0ed0 --- /dev/null +++ b/integrationExamples/gpt/cstg_example.html @@ -0,0 +1,317 @@ + + + + + UID2 and EUID Prebid.js Integration Example + + + + +

UID2 and EUID Prebid.js Integration Examples

+ +

+ This example demonstrates how a content publisher can integrate with UID2 and Prebid.js using the UID2 Client-Side Integration Guide for Prebid.js, which includes generating UID2 tokens within the browser.
+ This example is configured to hit endpoints at https://operator-integ.uidapi.com. Calls to this endpoint will be rejected if made from localhost.
+ A working sample subscription_id and client_key are declared in the javascript. Please override them in set[Uid2|Euid]Config() to test with your own CSTG credentials.
+ Note Generation of UID2 after EUID will fail due to consent settings on pbjs config. + +

+ +

UID2 Example

+
+ + + + + + + + + + + + + +
CSTG Subscription Id:
CSTG Public Key:
Email Address (DII): + +
+ +
+
+ + + + + + + + + +
Ready for Targeted Advertising:
UID2 Advertising Token:
+
+ +
+
+
+

EUID Example

+
+ + + + + + + + + + + + + +
CSTG Subscription Id:
CSTG Public Key:
Email Address (DII): + +
+ +
+
+ + + + + + + + + +
Ready for Targeted Advertising:
EUID Advertising Token:
+
+ +
+ + diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index fa6113250a8..d98dc02cdce 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -14,13 +14,6 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // eslint-disable-next-line prebid/validate-imports import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - * @typedef {import('../modules/userId/index.js').euidId} euidId - */ - const MODULE_NAME = 'euid'; const MODULE_REVISION = Uid2CodeVersion; const PREBID_VERSION = '$prebid.version$'; @@ -110,6 +103,7 @@ export const euidIdSubmodule = { mappedConfig.cstg = { serverPublicKey: config?.params?.serverPublicKey, subscriptionId: config?.params?.subscriptionId, + optoutCheck: 1, ...extractIdentityFromParams(config?.params ?? {}) } } @@ -135,6 +129,10 @@ function decodeImpl(value) { const result = { euid: { id: value } }; return result; } + if (value.latestToken === 'optout') { + _logInfo('Found optout token. Refresh is unavailable for this token.'); + return { euid: { optout: true } }; + } if (Date.now() < value.latestToken.identity_expires) { return { euid: { id: value.latestToken.advertising_token } }; } diff --git a/modules/euidIdSystem.md b/modules/euidIdSystem.md index 72e40b8ce7b..9c3f730da83 100644 --- a/modules/euidIdSystem.md +++ b/modules/euidIdSystem.md @@ -4,10 +4,10 @@ The EUID module handles storing, providing, and optionally refreshing tokens. Wh *Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. -*Client-Side Token Generation* mode is included in EUID module by default. However, it's important to note that this mode is created and made available recently. For publishers who do not intend to use it, you have the option to instruct the build to exclude the code related to this feature: +*Client-Side Token Generation* mode is included in EUID module by default. However, it's important to note that this mode was created and made available recently. For publishers who do not intend to use it, you have the option to instruct the build to exclude the code related to this feature: ``` - $ gulp build --modules=uid2IdSystem --disable UID2_CSTG + $ gulp build --modules=euidIdSystem --disable UID2_CSTG ``` If you do plan to use Client-Side Token Generation (CSTG) mode, please consult the EUID Team first as they will provide required configuration values for you to use (see the Client-Side Token Generation (CSTG) mode section below for details) @@ -157,6 +157,11 @@ The module stores a number of internal values. By default, all values are stored `{`
  `"advertising_token": "...",`
  `"refresh_token": "...",`
  `"identity_expires": 1633643601000,`
  `"refresh_from": 1633643001000,`
  `"refresh_expires": 1636322000000,`
  `"refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw=="`
`}` +## Optout response + +`{`
  `"optout": "true",`
`}` + + ### Notes If you are trying to limit the size of cookies, provide the token in configuration and use the default option of local storage. diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index acc440eafc5..102d217a658 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -254,6 +254,9 @@ if (FEATURES.UID2_CSTG) { isStoredTokenInvalid(cstgIdentity, storedTokens, _logInfo, _logWarn) { if (storedTokens) { + if (storedTokens.latestToken === 'optout') { + return true; + } const identity = Object.values(cstgIdentity)[0]; if (!this.isStoredTokenFromSameIdentity(storedTokens, identity)) { _logInfo( @@ -386,6 +389,7 @@ if (FEATURES.UID2_CSTG) { this._baseUrl = opts.baseUrl; this._serverPublicKey = opts.cstg.serverPublicKey; this._subscriptionId = opts.cstg.subscriptionId; + this._optoutCheck = opts.cstg.optoutCheck; this._logInfo = logInfo; this._logWarn = logWarn; } @@ -402,6 +406,12 @@ if (FEATURES.UID2_CSTG) { ); } + isCstgApiOptoutResponse(response) { + return ( + this.hasStatusResponse(response) && + response.status === 'optout'); + } + isCstgApiClientErrorResponse(response) { return ( this.hasStatusResponse(response) && @@ -437,7 +447,8 @@ if (FEATURES.UID2_CSTG) { } async generateToken(cstgIdentity) { - const request = await this.generateCstgRequest(cstgIdentity); + const requestIdentity = await this.generateCstgRequest(cstgIdentity); + const request = { optout_check: this._optoutCheck, ...requestIdentity }; this._logInfo('Building CSTG request for', request); const box = await UID2CstgBox.build( this.stripPublicKeyPrefix(this._serverPublicKey) @@ -491,6 +502,11 @@ if (FEATURES.UID2_CSTG) { status: 'success', identity: response.body, }); + } else if (this.isCstgApiOptoutResponse(response)) { + resolvePromise({ + status: 'optout', + identity: 'optout', + }); } else { // A 200 should always be a success response. // Something has gone wrong. diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index 98770fa80bc..9ad2b69e89c 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -22,6 +22,7 @@ const refreshedToken = 'refreshed-advertising-token'; const auctionDelayMs = 10; const makeEuidIdentityContainer = (token) => ({euid: {id: token}}); +const makeEuidOptoutContainer = (token) => ({euid: {optout: true}}); const useLocalStorage = true; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ @@ -30,12 +31,15 @@ const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ( const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } const clientSideGeneratedToken = 'client-side-generated-advertising-token'; +const optoutToken = 'optout-token'; const apiUrl = 'https://prod.euid.eu/v2/token/refresh'; const cstgApiUrl = 'https://prod.euid.eu/v2/token/client-generate'; const headers = { 'Content-Type': 'application/json' }; const makeSuccessResponseBody = (token) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); +const makeOptoutResponseBody = (token) => btoa(JSON.stringify({ status: 'optout', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeEuidIdentityContainer(token)); +const expectOptout = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeEuidOptoutContainer(token)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); describe('EUID module', function() { @@ -146,5 +150,15 @@ describe('EUID module', function() { const bid = await runAuction(); expectToken(bid, clientSideGeneratedToken); }); + it('Should receive an optout response when the user has opted out.', async function() { + setGdprApplies(true); + const euidToken = apiHelpers.makeTokenResponse(initialToken, true, true); + configureEuidCstgResponse(200, makeOptoutResponseBody(optoutToken)); + config.setConfig(makePrebidConfig({ euidToken, ...cstgConfigParams, email: 'optout@test.com' })); + apiHelpers.respondAfterDelay(1, server); + + const bid = await runAuction(); + expectOptout(bid, optoutToken); + }); } }); From f58100ae2367953a32e7902ebedda9bd04354bdd Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:26:08 -0800 Subject: [PATCH 474/760] Conversant Adapter: fix response handling (#11122) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 5 ++++- test/spec/modules/conversantBidAdapter_spec.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index dffc1a9f33b..ebcad38d866 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -94,7 +94,10 @@ const converter = ortbConverter({ const bidResponse = buildBidResponse(bid, context); return bidResponse; }, - + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context); + return response.bids; + }, overrides: { imp: { banner(fillBannerImp, imp, bidRequest, context) { diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 73d1978a9d9..9503a050092 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -465,7 +465,7 @@ describe('Conversant adapter tests', function() { before(() => { request = spec.buildRequests(bidRequests, {}); - response = spec.interpretResponse(bidResponses, request).bids; + response = spec.interpretResponse(bidResponses, request); }); it('Banner', function() { From 25053223342cb21349c24b6a25b61db977d09ac5 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Wed, 21 Feb 2024 03:16:42 +0300 Subject: [PATCH 475/760] Yieldmo Bid Adapter: send topics as string for request (#11121) * Convert topics to string * Update unit test --- modules/yieldmoBidAdapter.js | 2 +- test/spec/modules/yieldmoBidAdapter_spec.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 7fee4af422c..4082ed996fe 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -98,7 +98,7 @@ export const spec = { us_privacy: deepAccess(bidderRequest, 'uspConsent') || '', }; if (topicsData) { - serverRequest.topics = topicsData; + serverRequest.topics = JSON.stringify(topicsData); } const gpc = getGPCSignal(bidderRequest); if (gpc) { diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 43daa9aca41..68cf3459c5f 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -406,11 +406,12 @@ describe('YieldmoAdapter', function () { }, ], }}})); - expect(biddata[0].data.topics).to.deep.equal({ + + expect(biddata[0].data.topics).to.equal(JSON.stringify({ taxonomy: 600, classifier: '2206021246', topics: [7, 8, 9], - }); + })); }); it('should send gpc in the banner bid request', function () { From 708bc86e4596bdc65caa818c6c016356e6a7b9ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 04:28:40 -0700 Subject: [PATCH 476/760] Bump ip from 1.1.8 to 1.1.9 (#11124) Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9. - [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 592c1984509..544d0bdae5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.35.0-pre", + "version": "8.38.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -17522,9 +17522,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "node_modules/ipaddr.js": { @@ -25472,9 +25472,9 @@ } }, "node_modules/socks/node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "node_modules/source-list-map": { @@ -42611,9 +42611,9 @@ "dev": true }, "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "ipaddr.js": { @@ -48635,9 +48635,9 @@ }, "dependencies": { "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true } } From f5b71cd0b9320513632c55240c326a7136e5db2c Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 21 Feb 2024 10:34:05 -0800 Subject: [PATCH 477/760] Core: rendering logic overhaul, PUC-less native rendering (#10819) * Refactor rendering to go through a single code path * Build creative together with js * Fix pubUrl / pubDomain * Update dev tasks for creative building * Cross-domain render * Clean up empty fn * Autogenerated cross-domain creative example * Update text * Refactor creative * fix lint * Add test case for custom renderer * use URL instead of a tag * avoid using document.write * build creative together with bundle * direct rendering through display renderer * move mkFrame in base creative * do not share code between creative and core * lint cross-imports between creative and core * dynamic renderer in remote creative * remove support for non-messageChannel * take window instead of document in renderers * separate native rendering data from messaging logic * include native rendering data in response messages * move message rendering data into native rendering module * move video module render logic to video module * extract resize logic * extract native resizing & tracking messages * refactor creative renderers * WIP: native renderer * native rendering and messages * use results/rejections to emit ad render succeeded/failed * use offsetHeight, not clientHeight * refactor placeholder replacement logic * Fix firefox promises, add integ examples * update creative/README.md * fix integ examples * update README * native renderer: small size improvements --- .eslintrc.js | 1 + allowedModules.js | 8 +- creative/README.md | 44 ++ creative/constants.js | 9 + creative/crossDomain.js | 92 ++++ creative/renderers/display/constants.js | 4 + creative/renderers/display/renderer.js | 21 + creative/renderers/native/constants.js | 14 + creative/renderers/native/renderer.js | 88 ++++ gulpfile.js | 51 ++- .../gpt/x-domain/creative.html | 4 +- .../noadserver/native_noadserver.html | 68 +-- .../native_renderer/custom_renderer.html | 90 ++++ .../noadserver/native_renderer/renderer.js | 69 +++ .../creative-renderer-display/renderer.js | 2 + .../creative-renderer-native/renderer.js | 2 + libraries/creativeRender/constants.js | 10 - libraries/creativeRender/crossDomain.js | 57 --- libraries/creativeRender/direct.js | 62 --- libraries/creativeRender/writer.js | 34 -- modules/nativeRendering.js | 27 ++ modules/videoModule/index.js | 16 + package-lock.json | 419 +++++++++++++++--- package.json | 2 + plugins/eslint/validateImports.js | 11 + src/adRendering.js | 193 ++++++-- src/auction.js | 19 +- src/constants.json | 6 + src/creativeRenderers.js | 24 + src/native.js | 91 ++-- src/prebid.js | 2 +- src/secureCreatives.js | 83 ++-- src/utils.js | 48 +- .../spec/creative/crossDomainCreative_spec.js | 238 +++++----- test/spec/creative/displayRenderer_spec.js | 55 +++ test/spec/creative/nativeRenderer_spec.js | 298 +++++++++++++ test/spec/native_spec.js | 130 +++--- test/spec/unit/adRendering_spec.js | 248 +++++++++++ test/spec/unit/pbjs_api_spec.js | 39 +- test/spec/unit/secureCreatives_spec.js | 241 +++++----- webpack.creative.js | 8 +- 41 files changed, 2152 insertions(+), 776 deletions(-) create mode 100644 creative/README.md create mode 100644 creative/constants.js create mode 100644 creative/crossDomain.js create mode 100644 creative/renderers/display/constants.js create mode 100644 creative/renderers/display/renderer.js create mode 100644 creative/renderers/native/constants.js create mode 100644 creative/renderers/native/renderer.js create mode 100644 integrationExamples/noadserver/native_renderer/custom_renderer.html create mode 100644 integrationExamples/noadserver/native_renderer/renderer.js create mode 100644 libraries/creative-renderer-display/renderer.js create mode 100644 libraries/creative-renderer-native/renderer.js delete mode 100644 libraries/creativeRender/constants.js delete mode 100644 libraries/creativeRender/crossDomain.js delete mode 100644 libraries/creativeRender/direct.js delete mode 100644 libraries/creativeRender/writer.js create mode 100644 modules/nativeRendering.js create mode 100644 src/creativeRenderers.js create mode 100644 test/spec/creative/displayRenderer_spec.js create mode 100644 test/spec/creative/nativeRenderer_spec.js create mode 100644 test/spec/unit/adRendering_spec.js diff --git a/.eslintrc.js b/.eslintrc.js index 511e78048e4..f17c7a0063d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,6 +42,7 @@ module.exports = { sourceType: 'module', ecmaVersion: 2018, }, + ignorePatterns: ['libraries/creative-renderer*'], rules: { 'comma-dangle': 'off', diff --git a/allowedModules.js b/allowedModules.js index 3e6e3039fa2..75ad4141a6c 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,22 +1,18 @@ -const sharedWhiteList = [ -]; - module.exports = { 'modules': [ - ...sharedWhiteList, 'criteo-direct-rsa-validate', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], 'src': [ - ...sharedWhiteList, 'fun-hooks/no-eval', 'just-clone', 'dlv', 'dset' ], 'libraries': [ - ...sharedWhiteList // empty for now, but keep it to enable linting + ], + 'creative': [ ] }; diff --git a/creative/README.md b/creative/README.md new file mode 100644 index 00000000000..76f0be833e3 --- /dev/null +++ b/creative/README.md @@ -0,0 +1,44 @@ +## Dynamic creative renderers + +The contents of this directory are compiled separately from the rest of Prebid, and intended to be dynamically injected +into creative frames: + +- `crossDomain.js` (compiled into `build/creative/creative.js`, also exposed in `integrationExamples/gpt/x-domain/creative.html`) + is the logic that should be statically set up in the creative. +- At build time, each folder under 'renderers' is compiled into a source string made available from a corresponding +`creative-renderer-*` library. These libraries are committed in source so that they are available to NPM consumers. +- At render time, Prebid passes the appropriate renderer's source string to the remote creative, which then runs it. + +The goal is to have a creative script that is as simple, lightweight, and unchanging as possible, but still allow the possibility +of complex or frequently updated rendering logic. Compared to the approach taken by [PUC](https://github.com/prebid/prebid-universal-creative), this: + +- should perform marginally better: the creative only runs logic that is pertinent (for example, it sees native logic only on native bids); +- avoids the problem of synchronizing deployments when the rendering logic is updated (see https://github.com/prebid/prebid-universal-creative/issues/187), since it's bundled together with the rest of Prebid; +- is easier to embed directly in the creative (saving a network call), since the static "shell" is designed to change as infrequently as possible; +- allows the same rendering logic to be used both in remote (cross-domain) and local (`pbjs.renderAd`) frames, since it's directly available to Prebid; +- requires Prebid.js - meaning it does not support AMP/App/Mobile (but it's still possible for something like PUC to run the same dynamic renderers + when it receives them from Prebid, and fall back to separate AMP/App/Mobile logic otherwise). + +### Renderer interface + +A creative renderer (not related to other types of renderers in the codebase) is a script that exposes a global `window.render` function: + +```javascript +window.render = function(data, {mkFrame, sendMessage}, win) { ... } +``` + +where: + + - `data` is rendering data about the winning bid, and varies depending on the bid type - see `getRenderingData` in `adRendering.js`; + - `mkFrame(document, attributes)` is a utility that creates a frame with the given attributes and convenient defaults (no border, margin, and scrolling); + - `sendMessage(messageType, payload)` is the mechanism by which the renderer/creative can communicate back with Prebid - see `creativeMessageHandler` in `adRendering.js`; + - `win` is the window to render into; note that this is not the same window that runs the renderer. + +The function may return a promise; if it does and the promise rejects, or if the function throws, an AD_RENDER_FAILED event is emitted in Prebid. Otherwise an AD_RENDER_SUCCEEDED is fired +when the promise resolves (or when `render` returns anything other than a promise). + +### Renderer development + +Since renderers are compiled into source, they use production settings even during development builds. You can toggle this with +the `--creative-dev` CLI option (e.g., `gulp serve-fast --creative-dev`), which disables the minifier and generates source maps; if you do, take care +to not commit the resulting `creative-renderer-*` libraries (or run a normal build before you do). diff --git a/creative/constants.js b/creative/constants.js new file mode 100644 index 00000000000..6bb92cfe3c2 --- /dev/null +++ b/creative/constants.js @@ -0,0 +1,9 @@ +// eslint-disable-next-line prebid/validate-imports +import CONSTANTS from '../src/constants.json'; + +export const MESSAGE_REQUEST = CONSTANTS.MESSAGES.REQUEST; +export const MESSAGE_RESPONSE = CONSTANTS.MESSAGES.RESPONSE; +export const MESSAGE_EVENT = CONSTANTS.MESSAGES.EVENT; +export const EVENT_AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; +export const EVENT_AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED; +export const ERROR_EXCEPTION = CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION; diff --git a/creative/crossDomain.js b/creative/crossDomain.js new file mode 100644 index 00000000000..a851885bfc0 --- /dev/null +++ b/creative/crossDomain.js @@ -0,0 +1,92 @@ +import { + ERROR_EXCEPTION, + EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED, + MESSAGE_EVENT, + MESSAGE_REQUEST, + MESSAGE_RESPONSE +} from './constants.js'; + +const mkFrame = (() => { + const DEFAULTS = { + frameBorder: 0, + scrolling: 'no', + marginHeight: 0, + marginWidth: 0, + topMargin: 0, + leftMargin: 0, + allowTransparency: 'true', + }; + return (doc, attrs) => { + const frame = doc.createElement('iframe'); + Object.entries(Object.assign({}, attrs, DEFAULTS)) + .forEach(([k, v]) => frame.setAttribute(k, v)); + return frame; + }; +})(); + +export function renderer(win) { + return function ({adId, pubUrl, clickUrl}) { + const pubDomain = new URL(pubUrl, window.location).origin; + + function sendMessage(type, payload, responseListener) { + const channel = new MessageChannel(); + channel.port1.onmessage = guard(responseListener); + win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]); + } + + function onError(e) { + sendMessage(MESSAGE_EVENT, { + event: EVENT_AD_RENDER_FAILED, + info: { + reason: e?.reason || ERROR_EXCEPTION, + message: e?.message + } + }); + // eslint-disable-next-line no-console + e?.stack && console.error(e); + } + + function guard(fn) { + return function () { + try { + return fn.apply(this, arguments); + } catch (e) { + onError(e); + } + }; + } + + function onMessage(ev) { + let data; + try { + data = JSON.parse(ev.data); + } catch (e) { + return; + } + if (data.message === MESSAGE_RESPONSE && data.adId === adId) { + const renderer = mkFrame(win.document, { + width: 0, + height: 0, + style: 'display: none', + srcdoc: `` + }); + renderer.onload = guard(function () { + const W = renderer.contentWindow; + // NOTE: on Firefox, `Promise.resolve(P)` or `new Promise((resolve) => resolve(P))` + // does not appear to work if P comes from another frame + W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then( + () => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}), + onError + ) + }); + win.document.body.appendChild(renderer); + } + } + + sendMessage(MESSAGE_REQUEST, { + options: {clickUrl} + }, onMessage); + }; +} + +window.pbRender = renderer(window); diff --git a/creative/renderers/display/constants.js b/creative/renderers/display/constants.js new file mode 100644 index 00000000000..d291c79bb34 --- /dev/null +++ b/creative/renderers/display/constants.js @@ -0,0 +1,4 @@ +// eslint-disable-next-line prebid/validate-imports +import CONSTANTS from '../../../src/constants.json'; + +export const ERROR_NO_AD = CONSTANTS.AD_RENDER_FAILED_REASON.NO_AD; diff --git a/creative/renderers/display/renderer.js b/creative/renderers/display/renderer.js new file mode 100644 index 00000000000..e031679b116 --- /dev/null +++ b/creative/renderers/display/renderer.js @@ -0,0 +1,21 @@ +import {ERROR_NO_AD} from './constants.js'; + +export function render({ad, adUrl, width, height}, {mkFrame}, win) { + if (!ad && !adUrl) { + throw { + reason: ERROR_NO_AD, + message: 'Missing ad markup or URL' + }; + } else { + const doc = win.document; + const attrs = {width, height}; + if (adUrl && !ad) { + attrs.src = adUrl; + } else { + attrs.srcdoc = ad; + } + doc.body.appendChild(mkFrame(doc, attrs)); + } +} + +window.render = render; diff --git a/creative/renderers/native/constants.js b/creative/renderers/native/constants.js new file mode 100644 index 00000000000..ac20275fca8 --- /dev/null +++ b/creative/renderers/native/constants.js @@ -0,0 +1,14 @@ +// eslint-disable-next-line prebid/validate-imports +import CONSTANTS from '../../../src/constants.json'; + +export const MESSAGE_NATIVE = CONSTANTS.MESSAGES.NATIVE; +export const ACTION_RESIZE = 'resizeNativeHeight'; +export const ACTION_CLICK = 'click'; +export const ACTION_IMP = 'fireNativeImpressionTrackers'; + +export const ORTB_ASSETS = { + title: 'text', + data: 'value', + img: 'url', + video: 'vasttag' +} diff --git a/creative/renderers/native/renderer.js b/creative/renderers/native/renderer.js new file mode 100644 index 00000000000..5cc8f100108 --- /dev/null +++ b/creative/renderers/native/renderer.js @@ -0,0 +1,88 @@ +import {ACTION_CLICK, ACTION_IMP, ACTION_RESIZE, MESSAGE_NATIVE, ORTB_ASSETS} from './constants.js'; + +export function getReplacer(adId, {assets = [], ortb, nativeKeys = {}}) { + const assetValues = Object.fromEntries((assets).map(({key, value}) => [key, value])); + let repl = Object.fromEntries( + Object.entries(nativeKeys).flatMap(([name, key]) => { + const value = assetValues.hasOwnProperty(name) ? assetValues[name] : undefined; + return [ + [`##${key}##`, value], + [`${key}:${adId}`, value] + ]; + }) + ); + if (ortb) { + Object.assign(repl, + { + '##hb_native_linkurl##': ortb.link?.url, + '##hb_native_privacy##': ortb.privacy + }, + Object.fromEntries( + (ortb.assets || []).flatMap(asset => { + const type = Object.keys(ORTB_ASSETS).find(type => asset[type]); + return [ + type && [`##hb_native_asset_id_${asset.id}##`, asset[type][ORTB_ASSETS[type]]], + asset.link?.url && [`##hb_native_asset_link_id_${asset.id}##`, asset.link.url] + ].filter(e => e); + }) + ) + ); + } + repl = Object.entries(repl).concat([[/##hb_native_asset_(link_)?id_\d+##/g]]); + + return function (template) { + return repl.reduce((text, [pattern, value]) => text.replaceAll(pattern, value || ''), template); + }; +} + +function loadScript(url, doc) { + return new Promise((resolve, reject) => { + const script = doc.createElement('script'); + script.onload = resolve; + script.onerror = reject; + script.src = url; + doc.body.appendChild(script); + }); +} + +export function getAdMarkup(adId, nativeData, replacer, win, load = loadScript) { + const {rendererUrl, assets, ortb, adTemplate} = nativeData; + const doc = win.document; + if (rendererUrl) { + return load(rendererUrl, doc).then(() => { + if (typeof win.renderAd !== 'function') { + throw new Error(`Renderer from '${rendererUrl}' does not define renderAd()`); + } + const payload = assets || []; + payload.ortb = ortb; + return win.renderAd(payload); + }); + } else { + return Promise.resolve(replacer(adTemplate ?? doc.body.innerHTML)); + } +} + +export function render({adId, native}, {sendMessage}, win, getMarkup = getAdMarkup) { + const {head, body} = win.document; + const resize = () => sendMessage(MESSAGE_NATIVE, { + action: ACTION_RESIZE, + height: body.offsetHeight, + width: body.offsetWidth + }); + const replacer = getReplacer(adId, native); + head && (head.innerHTML = replacer(head.innerHTML)); + return getMarkup(adId, native, replacer, win).then(markup => { + body.innerHTML = markup; + if (typeof win.postRenderAd === 'function') { + win.postRenderAd({adId, ...native}); + } + win.document.querySelectorAll('.pb-click').forEach(el => { + const assetId = el.getAttribute('hb_native_asset_id'); + el.addEventListener('click', () => sendMessage(MESSAGE_NATIVE, {action: ACTION_CLICK, assetId})); + }); + sendMessage(MESSAGE_NATIVE, {action: ACTION_IMP}); + win.document.readyState === 'complete' ? resize() : win.onload = resize; + }); +} + +window.render = render; diff --git a/gulpfile.js b/gulpfile.js index d035da4b0fc..f3d44243ef8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,6 +25,8 @@ const path = require('path'); const execa = require('execa'); const {minify} = require('terser'); const Vinyl = require('vinyl'); +const wrap = require('gulp-wrap'); +const rename = require('gulp-rename'); var prebid = require('./package.json'); var port = 9999; @@ -83,6 +85,7 @@ function lint(done) { 'src/**/*.js', 'modules/**/*.js', 'libraries/**/*.js', + 'creative/**/*.js', 'test/**/*.js', 'plugins/**/*.js', '!plugins/**/node_modules/**', @@ -170,10 +173,29 @@ function makeWebpackPkg(extraConfig = {}) { } } -function buildCreative() { - return gulp.src(['**/*']) - .pipe(webpackStream(require('./webpack.creative.js'))) - .pipe(gulp.dest('build/creative')) +function buildCreative(mode = 'production') { + const opts = {mode}; + if (mode === 'development') { + opts.devtool = 'inline-source-map' + } + return function() { + return gulp.src(['**/*']) + .pipe(webpackStream(Object.assign(require('./webpack.creative.js'), opts))) + .pipe(gulp.dest('build/creative')) + } +} + +function updateCreativeRenderers() { + return gulp.src(['build/creative/renderers/**/*']) + .pipe(wrap('// this file is autogenerated, see creative/README.md\nexport const RENDERER = <%= JSON.stringify(contents.toString()) %>')) + .pipe(rename(function (path) { + return { + dirname: `creative-renderer-${path.basename}`, + basename: 'renderer', + extname: '.js' + } + })) + .pipe(gulp.dest('libraries')) } function updateCreativeExample(cb) { @@ -440,6 +462,8 @@ function watchTaskMaker(options = {}) { var mainWatcher = gulp.watch([ 'src/**/*.js', 'libraries/**/*.js', + '!libraries/creative-renderer-*/**/*.js', + 'creative/**/*.js', 'modules/**/*.js', ].concat(options.alsoWatch)); @@ -450,8 +474,8 @@ function watchTaskMaker(options = {}) { } } -const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test, buildCreative))}); -const watchFast = watchTaskMaker({livereload: false, task: () => gulp.parallel('build-bundle-dev', buildCreative)}); +const watch = watchTaskMaker({alsoWatch: ['test/**/*.js'], task: () => gulp.series(clean, gulp.parallel(lint, 'build-bundle-dev', test))}); +const watchFast = watchTaskMaker({livereload: false, task: () => gulp.series('build-bundle-dev')}); // support tasks gulp.task(lint); @@ -461,8 +485,11 @@ gulp.task(clean); gulp.task(escapePostbidConfig); -gulp.task('build-bundle-dev', gulp.series(makeDevpackPkg, gulpBundle.bind(null, true))); -gulp.task('build-bundle-prod', gulp.series(makeWebpackPkg(), gulpBundle.bind(null, false))); +gulp.task('build-creative-dev', gulp.series(buildCreative(argv.creativeDev ? 'development' : 'production'), updateCreativeRenderers)); +gulp.task('build-creative-prod', gulp.series(buildCreative(), updateCreativeRenderers)); + +gulp.task('build-bundle-dev', gulp.series('build-creative-dev', makeDevpackPkg, gulpBundle.bind(null, true))); +gulp.task('build-bundle-prod', gulp.series('build-creative-prod', makeWebpackPkg(), gulpBundle.bind(null, false))); // build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects // of dead code elimination. gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ @@ -482,23 +509,21 @@ gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ } }), gulpBundle.bind(null, false))); -gulp.task('build-creative', gulp.series(buildCreative, updateCreativeExample)); - // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); gulp.task('test-all-features-disabled', testTaskMaker({disableFeatures: require('./features.json'), oneBrowser: 'chrome', watch: false})); -gulp.task('test', gulp.series(clean, lint, gulp.parallel('build-creative', gulp.series('test-all-features-disabled', 'test-only')))); +gulp.task('test', gulp.series(clean, lint, 'test-all-features-disabled', 'test-only')); gulp.task('test-coverage', gulp.series(clean, testCoverage)); gulp.task(viewCoverage); gulp.task('coveralls', gulp.series('test-coverage', coveralls)); -gulp.task('build', gulp.series(clean, 'build-bundle-prod', 'build-creative')); +gulp.task('build', gulp.series(clean, 'build-bundle-prod', updateCreativeExample)); gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); -gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', buildCreative, watchFast))); +gulp.task('serve-fast', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast))); gulp.task('serve-prod', gulp.series(clean, gulp.parallel('build-bundle-prod', startLocalServer))); gulp.task('serve-and-test', gulp.series(clean, gulp.parallel('build-bundle-dev', watchFast, testTaskMaker({watch: true})))); gulp.task('serve-e2e', gulp.series(clean, 'build-bundle-prod', gulp.parallel(() => startIntegServer(), startLocalServer))); diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index f1c0c647e72..bf2bd5f3fad 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,10 +2,10 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + + + + +

Prebid Native w/custom renderer

+
+
+ +
+
+ + + + diff --git a/integrationExamples/noadserver/native_renderer/renderer.js b/integrationExamples/noadserver/native_renderer/renderer.js new file mode 100644 index 00000000000..d1c754f20b7 --- /dev/null +++ b/integrationExamples/noadserver/native_renderer/renderer.js @@ -0,0 +1,69 @@ +window.renderAd = function (data) { + data = Object.fromEntries(data.map(({key, value}) => [key, value])); + return ` + +
+
+
+

+ ${data.title} +

+
+
+ ${data.sponsoredBy} +
+
+
`; +}; diff --git a/libraries/creative-renderer-display/renderer.js b/libraries/creative-renderer-display/renderer.js new file mode 100644 index 00000000000..72f3658fe79 --- /dev/null +++ b/libraries/creative-renderer-display/renderer.js @@ -0,0 +1,2 @@ +// this file is autogenerated, see creative/README.md +export const RENDERER = "!function(){\"use strict\";window.render=function({ad:d,adUrl:i,width:n,height:e},{mkFrame:o},r){if(!d&&!i)throw{reason:\"noAd\",message:\"Missing ad markup or URL\"};{const t=r.document,s={width:n,height:e};i&&!d?s.src=i:s.srcdoc=d,t.body.appendChild(o(t,s))}}}();" \ No newline at end of file diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js new file mode 100644 index 00000000000..509f7943af4 --- /dev/null +++ b/libraries/creative-renderer-native/renderer.js @@ -0,0 +1,2 @@ +// this file is autogenerated, see creative/README.md +export const RENDERER = "!function(){\"use strict\";const e=JSON.parse('{\"X3\":{\"B5\":\"Prebid Native\"}}').X3.B5,t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}}();" \ No newline at end of file diff --git a/libraries/creativeRender/constants.js b/libraries/creativeRender/constants.js deleted file mode 100644 index 7b67f8ed5cd..00000000000 --- a/libraries/creativeRender/constants.js +++ /dev/null @@ -1,10 +0,0 @@ -import events from '../../src/constants.json'; - -export const PREBID_NATIVE = 'Prebid Native'; -export const PREBID_REQUEST = 'Prebid Request'; -export const PREBID_RESPONSE = 'Prebid Response'; -export const PREBID_EVENT = 'Prebid Event'; -export const AD_RENDER_SUCCEEDED = events.EVENTS.AD_RENDER_SUCCEEDED; -export const AD_RENDER_FAILED = events.EVENTS.AD_RENDER_FAILED; -export const NO_AD = events.AD_RENDER_FAILED_REASON.NO_AD; -export const EXCEPTION = events.AD_RENDER_FAILED_REASON.EXCEPTION; diff --git a/libraries/creativeRender/crossDomain.js b/libraries/creativeRender/crossDomain.js deleted file mode 100644 index ffa8b468f12..00000000000 --- a/libraries/creativeRender/crossDomain.js +++ /dev/null @@ -1,57 +0,0 @@ -import {mkFrame, writeAd} from './writer.js'; -import { - AD_RENDER_FAILED, - AD_RENDER_SUCCEEDED, - PREBID_EVENT, - PREBID_RESPONSE, - PREBID_REQUEST, - EXCEPTION -} from './constants.js'; - -export function renderer(win = window) { - return function ({adId, pubUrl, clickUrl}) { - const pubDomain = (function() { - const a = win.document.createElement('a'); - a.href = pubUrl; - return a.protocol + '//' + a.host; - })(); - function sendMessage(type, payload, transfer) { - win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, transfer); - } - function cb(err) { - sendMessage(PREBID_EVENT, { - event: err == null ? AD_RENDER_SUCCEEDED : AD_RENDER_FAILED, - info: err - }); - } - function onMessage(ev) { - let data = {}; - try { - data = JSON.parse(ev[ev.message ? 'message' : 'data']); - } catch (e) { - return; - } - if (data.message === PREBID_RESPONSE && data.adId === adId) { - try { - let doc = win.document - if (data.ad) { - doc = mkFrame(doc, {width: data.width, height: data.height}).contentDocument; - doc.open(); - } - writeAd(data, cb, doc); - } catch (e) { - // eslint-disable-next-line standard/no-callback-literal - cb({ reason: EXCEPTION, message: e.message }) - } - } - } - - const channel = new MessageChannel(); - channel.port1.onmessage = onMessage; - sendMessage(PREBID_REQUEST, { - options: {clickUrl} - }, [channel.port2]); - win.addEventListener('message', onMessage, false); - } -} -window.renderAd = renderer(); diff --git a/libraries/creativeRender/direct.js b/libraries/creativeRender/direct.js deleted file mode 100644 index 19d34e16844..00000000000 --- a/libraries/creativeRender/direct.js +++ /dev/null @@ -1,62 +0,0 @@ -import {emitAdRenderFail, emitAdRenderSucceeded, handleRender} from '../../src/adRendering.js'; -import {writeAd} from './writer.js'; -import {auctionManager} from '../../src/auctionManager.js'; -import CONSTANTS from '../../src/constants.json'; -import {inIframe, insertElement} from '../../src/utils.js'; -import {getGlobal} from '../../src/prebidGlobal.js'; -import {EXCEPTION} from './constants.js'; - -export function renderAdDirect(doc, adId, options) { - let bid; - function cb(err) { - if (err != null) { - emitAdRenderFail(Object.assign({id: adId, bid}, err)); - } else { - emitAdRenderSucceeded({doc, bid, adId}) - } - } - function renderFn(adData) { - writeAd(adData, cb, doc); - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = adData.width; - doc.defaultView.frameElement.height = adData.height; - } - // TODO: this is almost certainly the wrong way to do this - const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); - insertElement(creativeComment, doc, 'html'); - } - try { - if (!adId || !doc) { - // eslint-disable-next-line standard/no-callback-literal - cb({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, - message: `missing ${adId ? 'doc' : 'adId'}` - }); - } else { - bid = auctionManager.findBidByAdId(adId); - - if (FEATURES.VIDEO) { - // TODO: could the video module implement this as a custom renderer, rather than a special case in here? - const adUnit = bid && auctionManager.index.getAdUnit(bid); - const videoModule = getGlobal().videoModule; - if (adUnit?.video && videoModule) { - videoModule.renderBid(adUnit.video.divId, bid); - return; - } - } - - if ((doc === document && !inIframe())) { - // eslint-disable-next-line standard/no-callback-literal - cb({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, - message: `renderAd was prevented from writing to the main document.` - }) - } else { - handleRender(renderFn, {adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); - } - } - } catch (e) { - // eslint-disable-next-line standard/no-callback-literal - cb({reason: EXCEPTION, message: e.message}) - } -} diff --git a/libraries/creativeRender/writer.js b/libraries/creativeRender/writer.js deleted file mode 100644 index 80bb0592a1f..00000000000 --- a/libraries/creativeRender/writer.js +++ /dev/null @@ -1,34 +0,0 @@ -import {NO_AD} from './constants.js'; - -const IFRAME_ATTRS = { - frameBorder: 0, - scrolling: 'no', - marginHeight: 0, - marginWidth: 0, - topMargin: 0, - leftMargin: 0, - allowTransparency: 'true', -}; - -export function mkFrame(doc, attrs) { - const frame = doc.createElement('iframe'); - attrs = Object.assign({}, attrs, IFRAME_ATTRS); - Object.entries(attrs).forEach(([k, v]) => frame.setAttribute(k, v)); - doc.body.appendChild(frame); - return frame; -} - -export function writeAd({ad, adUrl, width, height}, cb, doc = document) { - if (!ad && !adUrl) { - // eslint-disable-next-line standard/no-callback-literal - cb({reason: NO_AD, message: 'Missing ad markup or URL'}); - } else { - if (adUrl && !ad) { - mkFrame(doc, {width, height, src: adUrl}) - } else { - doc.write(ad); - doc.close(); - } - cb(); - } -} diff --git a/modules/nativeRendering.js b/modules/nativeRendering.js new file mode 100644 index 00000000000..8e6b6baab55 --- /dev/null +++ b/modules/nativeRendering.js @@ -0,0 +1,27 @@ +import {getRenderingData} from '../src/adRendering.js'; +import {getNativeRenderingData, isNativeResponse} from '../src/native.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {RENDERER} from '../libraries/creative-renderer-native/renderer.js'; +import {getCreativeRendererSource} from '../src/creativeRenderers.js'; + +function getRenderingDataHook(next, bidResponse, options) { + if (isNativeResponse(bidResponse)) { + next.bail({ + native: getNativeRenderingData(bidResponse, auctionManager.index.getAdUnit(bidResponse)) + }) + } else { + next(bidResponse, options) + } +} +function getRendererSourceHook(next, bidResponse) { + if (isNativeResponse(bidResponse)) { + next.bail(RENDERER); + } else { + next(bidResponse); + } +} + +if (FEATURES.NATIVE) { + getRenderingData.before(getRenderingDataHook) + getCreativeRendererSource.before(getRendererSourceHook); +} diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index bfb239210db..28f5c90d326 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -21,6 +21,9 @@ import { gamSubmoduleFactory } from './gamAdServerSubmodule.js'; import { videoImpressionVerifierFactory } from './videoImpressionVerifier.js'; import { AdQueueCoordinator } from './adQueue.js'; import { getExternalVideoEventName, getExternalVideoEventPayload } from '../../libraries/video/shared/helpers.js' +import {VIDEO} from '../../src/mediaTypes.js'; +import {auctionManager} from '../../src/auctionManager.js'; +import {doRender} from '../../src/adRendering.js'; const allVideoEvents = Object.keys(videoEvents).map(eventKey => videoEvents[eventKey]); events.addEvents(allVideoEvents.concat([AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, AUCTION_AD_LOAD_ABORT, BID_IMPRESSION, BID_ERROR]).map(getExternalVideoEventName)); @@ -261,6 +264,18 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent } } +function videoRenderHook(next, args) { + if (args.bidResponse.mediaType === VIDEO) { + const adUnit = auctionManager.index.getAdUnit(args.bidResponse); + if (adUnit?.video) { + getGlobal().videoModule.renderBid(adUnit.video.divId, args.bidResponse); + next.bail(); + return; + } + } + next(args); +} + export function pbVideoFactory() { const videoCore = videoCoreFactory(); const adQueueCoordinator = AdQueueCoordinator(videoCore, events); @@ -268,6 +283,7 @@ export function pbVideoFactory() { const pbVideo = PbVideo(videoCore, config.getConfig, pbGlobal, events, allVideoEvents, gamSubmoduleFactory, videoImpressionVerifierFactory, adQueueCoordinator); pbVideo.init(); pbGlobal.videoModule = pbVideo; + doRender.before(videoRenderHook); return pbVideo; } diff --git a/package-lock.json b/package-lock.json index 544d0bdae5b..4965e554935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "dset": "3.1.2", "express": "^4.15.4", "fun-hooks": "^0.9.9", + "gulp-wrap": "^0.15.0", "just-clone": "^1.0.2", "live-connect-js": "^6.3.4" }, @@ -61,6 +62,7 @@ "gulp-eslint": "^6.0.0", "gulp-if": "^3.0.0", "gulp-js-escape": "^1.0.1", + "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", "gulp-shell": "^0.8.0", "gulp-sourcemaps": "^3.0.0", @@ -7194,7 +7196,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7293,7 +7294,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -7564,7 +7564,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8312,8 +8311,7 @@ "node_modules/bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" }, "node_modules/body": { "version": "5.1.0", @@ -8621,6 +8619,38 @@ "node": ">=0.2.0" } }, + "node_modules/bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", + "dependencies": { + "readable-stream": "^1.0.33" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -9408,6 +9438,18 @@ "node": ">= 0.6" } }, + "node_modules/consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", + "dependencies": { + "bluebird": "^3.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -9505,8 +9547,7 @@ "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -11234,8 +11275,7 @@ "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "node_modules/es6-promisify": { "version": "5.0.0", @@ -13992,6 +14032,14 @@ "xtend": "~4.0.1" } }, + "node_modules/fs-readfile-promise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", + "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, "node_modules/fs.extra": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", @@ -15090,8 +15138,7 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -16218,6 +16265,15 @@ "minimatch": "^3.0.3" } }, + "node_modules/gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/gulp-replace": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", @@ -16678,6 +16734,89 @@ "node": ">= 0.9" } }, + "node_modules/gulp-wrap": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", + "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "dependencies": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "engines": { + "node": ">=6.14", + "npm": ">=1.4.3" + } + }, + "node_modules/gulp-wrap/node_modules/ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dependencies": { + "ansi-wrap": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gulp-wrap/node_modules/plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dependencies": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-wrap/node_modules/through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + }, "node_modules/gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -17535,6 +17674,14 @@ "node": ">= 0.10" } }, + "node_modules/is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==", + "engines": { + "node": "*" + } + }, "node_modules/is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -17755,7 +17902,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "dependencies": { "is-plain-object": "^2.0.4" }, @@ -17767,7 +17913,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -18194,7 +18339,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18934,7 +19078,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -18947,7 +19090,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -19832,8 +19974,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash._basecopy": { "version": "3.0.1", @@ -22281,6 +22422,18 @@ "strict-event-emitter": "^0.1.0" } }, + "node_modules/node.extend": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "dependencies": { + "has": "^1.0.3", + "is": "^3.2.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -23471,8 +23624,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/progress": { "version": "2.0.3", @@ -23954,7 +24106,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -23968,14 +24119,12 @@ "node_modules/readable-stream/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/readable-stream/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/readdir-glob": { "version": "1.1.2", @@ -25636,8 +25785,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/sshpk": { "version": "1.17.0", @@ -25891,7 +26039,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -25899,8 +26046,7 @@ "node_modules/string_decoder/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/string-template": { "version": "0.2.1", @@ -26675,6 +26821,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -27324,8 +27475,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -27673,6 +27823,14 @@ "node": ">= 0.10" } }, + "node_modules/vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha512-yCCIoTf26Q9SQ0L9cDSavSL7Nt6wgQw8TU1B/bb9b9Z4A3XTypXCGdc5BvXl4ObQvVY8JrDkFnWa/UqBqwM2IA==", + "dependencies": { + "bufferstreams": "1.0.1" + } + }, "node_modules/vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", @@ -34489,8 +34647,7 @@ "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "dev": true + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==" }, "anymatch": { "version": "3.1.2", @@ -34573,7 +34730,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -34783,8 +34939,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" }, "ast-types": { "version": "0.13.4", @@ -35405,8 +35560,7 @@ "bluebird": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" }, "body": { "version": "5.1.0", @@ -35652,6 +35806,37 @@ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", "dev": true }, + "bufferstreams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", + "requires": { + "readable-stream": "^1.0.33" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + } + } + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -36252,6 +36437,14 @@ "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", "dev": true }, + "consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "requires": { + "bluebird": "^3.1.1" + } + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -36323,8 +36516,7 @@ "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "cors": { "version": "2.8.5", @@ -37668,8 +37860,7 @@ "es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" }, "es6-promisify": { "version": "5.0.0", @@ -39813,6 +40004,14 @@ } } }, + "fs-readfile-promise": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-readfile-promise/-/fs-readfile-promise-3.0.1.tgz", + "integrity": "sha512-LsSxMeaJdYH27XrW7Dmq0Gx63mioULCRel63B5VeELYLavi1wF5s0XfsIdKDFdCL9hsfQ2qBvXJszQtQJ9h17A==", + "requires": { + "graceful-fs": "^4.1.11" + } + }, "fs.extra": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", @@ -40695,8 +40894,7 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "grapheme-splitter": { "version": "1.0.4", @@ -41620,6 +41818,12 @@ "minimatch": "^3.0.3" } }, + "gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "dev": true + }, "gulp-replace": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", @@ -41993,6 +42197,72 @@ } } }, + "gulp-wrap": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/gulp-wrap/-/gulp-wrap-0.15.0.tgz", + "integrity": "sha512-f17zkGObA+hE/FThlg55gfA0nsXbdmHK1WqzjjB2Ytq1TuhLR7JiCBJ3K4AlMzCyoFaCjfowos+VkToUNE0WTQ==", + "requires": { + "consolidate": "^0.15.1", + "es6-promise": "^4.2.6", + "fs-readfile-promise": "^3.0.1", + "js-yaml": "^3.13.0", + "lodash": "^4.17.11", + "node.extend": "2.0.2", + "plugin-error": "^1.0.1", + "through2": "^3.0.1", + "tryit": "^1.0.1", + "vinyl-bufferstream": "^1.0.1" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } + } + } + }, "gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -42621,6 +42891,11 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "is": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", + "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" + }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -42767,7 +43042,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" }, @@ -42776,7 +43050,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -43072,8 +43345,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "isstream": { "version": "0.1.2", @@ -43630,7 +43902,6 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -43639,8 +43910,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" } } }, @@ -44354,8 +44624,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash._basecopy": { "version": "3.0.1", @@ -46191,6 +46460,15 @@ "strict-event-emitter": "^0.1.0" } }, + "node.extend": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-2.0.2.tgz", + "integrity": "sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ==", + "requires": { + "has": "^1.0.3", + "is": "^3.2.1" + } + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -47066,8 +47344,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -47425,7 +47702,6 @@ "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -47439,14 +47715,12 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -48798,8 +49072,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "sshpk": { "version": "1.17.0", @@ -49010,7 +49283,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" }, @@ -49018,8 +49290,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, @@ -49629,6 +49900,11 @@ "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", "dev": true }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha512-6C5h3CE+0qjGp+YKYTs74xR0k/Nw/ePtl/Lp6CCf44hqBQ66qnH1sDFR5mV/Gc48EsrHLB53lCFSffQCkka3kg==" + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -50135,8 +50411,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utils-merge": { "version": "1.0.1", @@ -50413,6 +50688,14 @@ } } }, + "vinyl-bufferstream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vinyl-bufferstream/-/vinyl-bufferstream-1.0.1.tgz", + "integrity": "sha512-yCCIoTf26Q9SQ0L9cDSavSL7Nt6wgQw8TU1B/bb9b9Z4A3XTypXCGdc5BvXl4ObQvVY8JrDkFnWa/UqBqwM2IA==", + "requires": { + "bufferstreams": "1.0.1" + } + }, "vinyl-fs": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", diff --git a/package.json b/package.json index 60a42e6afd6..3e20acbeab9 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "gulp-eslint": "^6.0.0", "gulp-if": "^3.0.0", "gulp-js-escape": "^1.0.1", + "gulp-rename": "^2.0.0", "gulp-replace": "^1.0.0", "gulp-shell": "^0.8.0", "gulp-sourcemaps": "^3.0.0", @@ -132,6 +133,7 @@ "dset": "3.1.2", "express": "^4.15.4", "fun-hooks": "^0.9.9", + "gulp-wrap": "^0.15.0", "just-clone": "^1.0.2", "live-connect-js": "^6.3.4" }, diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index 324b75c4ce7..b936f44aee7 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -3,6 +3,7 @@ const path = require('path'); const _ = require('lodash'); const resolveFrom = require('resolve-from'); const MODULES_PATH = path.resolve(__dirname, '../../modules'); +const CREATIVE_PATH = path.resolve(__dirname, '../../creative'); function isInDirectory(filename, dir) { const rel = path.relative(dir, filename); @@ -31,6 +32,16 @@ function flagErrors(context, node, importPath) { context.report(node, `import "${importPath}": importing from modules is not allowed`); } + // do not allow imports into `creative` + if (isInDirectory(absImportPath, CREATIVE_PATH) && !isInDirectory(absFileDir, CREATIVE_PATH) && absFileDir !== CREATIVE_PATH) { + context.report(node, `import "${importPath}": importing from creative is not allowed`); + } + + // do not allow imports outside `creative` + if (isInDirectory(absFileDir, CREATIVE_PATH) && !isInDirectory(absImportPath, CREATIVE_PATH) && absImportPath !== CREATIVE_PATH) { + context.report(node, `import "${importPath}": importing from outside creative is not allowed`); + } + // don't allow extension-less local imports if ( !importPath.match(/^\w+/) && diff --git a/src/adRendering.js b/src/adRendering.js index 37bbf20d432..a6d509bea77 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -1,12 +1,16 @@ -import {deepAccess, logError, logWarn, replaceMacros} from './utils.js'; +import {createIframe, deepAccess, inIframe, insertElement, logError, logWarn, replaceMacros} from './utils.js'; import * as events from './events.js'; -import constants from './constants.json'; +import CONSTANTS from './constants.json'; import {config} from './config.js'; import {executeRenderer, isRendererRequired} from './Renderer.js'; import {VIDEO} from './mediaTypes.js'; import {auctionManager} from './auctionManager.js'; +import {getCreativeRenderer} from './creativeRenderers.js'; +import {hook} from './hook.js'; +import {fireNativeTrackers} from './native.js'; -const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = constants.EVENTS; +const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = CONSTANTS.EVENTS; +const {EXCEPTION} = CONSTANTS.AD_RENDER_FAILED_REASON; /** * Emit the AD_RENDER_FAILED event. @@ -46,16 +50,108 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { events.emit(AD_RENDER_SUCCEEDED, data); } -export function handleRender(renderFn, {adId, options, bidResponse, doc}) { +export function handleCreativeEvent(data, bidResponse) { + switch (data.event) { + case CONSTANTS.EVENTS.AD_RENDER_FAILED: + emitAdRenderFail({ + bid: bidResponse, + id: bidResponse.adId, + reason: data.info.reason, + message: data.info.message + }); + break; + case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + emitAdRenderSucceeded({ + doc: null, + bid: bidResponse, + id: bidResponse.adId + }); + break; + default: + logError(`Received event request for unsupported event: '${data.event}' (adId: '${bidResponse.adId}')`); + } +} + +export function handleNativeMessage(data, bidResponse, {resizeFn, fireTrackers = fireNativeTrackers}) { + switch (data.action) { + case 'resizeNativeHeight': + resizeFn(data.width, data.height); + break; + default: + fireTrackers(data, bidResponse); + } +} + +const HANDLERS = { + [CONSTANTS.MESSAGES.EVENT]: handleCreativeEvent +} + +if (FEATURES.NATIVE) { + HANDLERS[CONSTANTS.MESSAGES.NATIVE] = handleNativeMessage; +} + +function creativeMessageHandler(deps) { + return function (type, data, bidResponse) { + if (HANDLERS.hasOwnProperty(type)) { + HANDLERS[type](data, bidResponse, deps); + } + } +} + +export const getRenderingData = hook('sync', function (bidResponse, options) { + const {ad, adUrl, cpm, originalCpm, width, height} = bidResponse + const repl = { + AUCTION_PRICE: originalCpm || cpm, + CLICKTHROUGH: options?.clickUrl || '' + } + return { + ad: replaceMacros(ad, repl), + adUrl: replaceMacros(adUrl, repl), + width, + height + }; +}) + +export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options}) { + if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { + emitAdRenderFail({ + reason: CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, + message: 'Cannot render video ad', + bid: bidResponse, + id: bidResponse.adId + }); + return; + } + const data = getRenderingData(bidResponse, options); + renderFn(Object.assign({adId: bidResponse.adId}, data)); + const {width, height} = data; + if ((width ?? height) != null) { + resizeFn(width, height); + } +}); + +doRender.before(function (next, args) { + // run renderers from a high priority hook to allow the video module to insert itself between this and "normal" rendering. + const {bidResponse, doc} = args; + if (isRendererRequired(bidResponse.renderer)) { + executeRenderer(bidResponse.renderer, bidResponse, doc); + emitAdRenderSucceeded({doc, bid: bidResponse, id: bidResponse.adId}) + next.bail(); + } else { + next(args); + } +}, 100) + +export function handleRender({renderFn, resizeFn, adId, options, bidResponse, doc}) { if (bidResponse == null) { emitAdRenderFail({ - reason: constants.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + reason: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, message: `Cannot find ad '${adId}'`, id: adId }); return; } - if (bidResponse.status === constants.BID_STATUS.RENDERED) { + if (bidResponse.status === CONSTANTS.BID_STATUS.RENDERED) { logWarn(`Ad id ${adId} has been rendered before`); events.emit(STALE_RENDER, bidResponse); if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { @@ -63,44 +159,67 @@ export function handleRender(renderFn, {adId, options, bidResponse, doc}) { } } try { - const {adId, ad, adUrl, width, height, renderer, cpm, originalCpm, mediaType} = bidResponse; - // rendering for outstream safeframe - if (isRendererRequired(renderer)) { - executeRenderer(renderer, bidResponse, doc); - emitAdRenderSucceeded({doc, bid: bidResponse, id: adId}) - } else if (adId) { - if (mediaType === VIDEO) { - emitAdRenderFail({ - reason: constants.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, - message: 'Cannot render video ad', - bid: bidResponse, - id: adId - }); - return; - } - const repl = { - AUCTION_PRICE: originalCpm || cpm, - CLICKTHROUGH: options?.clickUrl || '' - }; - renderFn({ - ad: replaceMacros(ad, repl), - adUrl: replaceMacros(adUrl, repl), - adId, - width, - height - }); - } + doRender({renderFn, resizeFn, bidResponse, options, doc}); } catch (e) { emitAdRenderFail({ - reason: constants.AD_RENDER_FAILED_REASON.EXCEPTION, + reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, message: e.message, id: adId, bid: bidResponse }); - return; } - // save winning bids auctionManager.addWinningBid(bidResponse); - events.emit(BID_WON, bidResponse); } + +export function renderAdDirect(doc, adId, options) { + let bid; + function fail(reason, message) { + emitAdRenderFail(Object.assign({id: adId, bid}, {reason, message})); + } + function resizeFn(width, height) { + if (doc.defaultView && doc.defaultView.frameElement) { + width && (doc.defaultView.frameElement.width = width); + height && (doc.defaultView.frameElement.height = height); + } + } + const messageHandler = creativeMessageHandler({resizeFn}); + function renderFn(adData) { + if (adData.ad) { + doc.write(adData.ad); + doc.close(); + emitAdRenderSucceeded({doc, bid, adId: bid.adId}); + } else { + getCreativeRenderer(bid) + .then(render => render(adData, { + sendMessage: (type, data) => messageHandler(type, data, bid), + mkFrame: createIframe, + }, doc.defaultView)) + .then( + () => emitAdRenderSucceeded({doc, bid, adId: bid.adId}), + (e) => { + fail(e?.reason || CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) + e?.stack && logError(e); + } + ); + } + // TODO: this is almost certainly the wrong way to do this + const creativeComment = document.createComment(`Creative ${bid.creativeId} served by ${bid.bidder} Prebid.js Header Bidding`); + insertElement(creativeComment, doc, 'html'); + } + try { + if (!adId || !doc) { + fail(CONSTANTS.AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); + } else { + bid = auctionManager.findBidByAdId(adId); + + if ((doc === document && !inIframe())) { + fail(CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); + } else { + handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); + } + } + } catch (e) { + fail(EXCEPTION, e.message); + } +} diff --git a/src/auction.js b/src/auction.js index cc42e54a2b1..c2758fdd571 100644 --- a/src/auction.js +++ b/src/auction.js @@ -80,7 +80,7 @@ import { timestamp } from './utils.js'; import {getPriceBucketString} from './cpmBucketManager.js'; -import {getNativeTargeting, toLegacyResponse} from './native.js'; +import {getNativeTargeting, isNativeResponse, setNativeResponseProperties} from './native.js'; import {getCacheUrl, store} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; @@ -468,10 +468,8 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { tryAddVideoBid(auctionInstance, bidResponse, done); } else { - if (FEATURES.NATIVE && bidResponse.native != null && typeof bidResponse.native === 'object') { - // NOTE: augment bidResponse.native even if bidResponse.mediaType !== NATIVE; it's possible - // to treat banner responses as native - addLegacyFieldsIfNeeded(bidResponse); + if (FEATURES.NATIVE && isNativeResponse(bidResponse)) { + setNativeResponseProperties(bidResponse, index.getAdUnit(bidResponse)); } addBidToAuction(auctionInstance, bidResponse); done(); @@ -577,17 +575,6 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } -// Native bid response might be in ortb2 format - adds legacy field for backward compatibility -const addLegacyFieldsIfNeeded = (bidResponse) => { - const nativeOrtbRequest = auctionManager.index.getAdUnit(bidResponse)?.nativeOrtbRequest; - const nativeOrtbResponse = bidResponse.native?.ortb - - if (nativeOrtbRequest && nativeOrtbResponse) { - const legacyResponse = toLegacyResponse(nativeOrtbResponse, nativeOrtbRequest); - Object.assign(bidResponse.native, legacyResponse); - } -} - const _storeInCache = (batch) => { store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { cacheIds.forEach((cacheId, i) => { diff --git a/src/constants.json b/src/constants.json index af7e80ec0b4..ceac779a508 100644 --- a/src/constants.json +++ b/src/constants.json @@ -186,5 +186,11 @@ "SUCCESS": "success", "ERROR": "error", "TIMEOUT": "timeout" + }, + "MESSAGES": { + "REQUEST": "Prebid Request", + "RESPONSE": "Prebid Response", + "NATIVE": "Prebid Native", + "EVENT": "Prebid Event" } } diff --git a/src/creativeRenderers.js b/src/creativeRenderers.js new file mode 100644 index 00000000000..8331c23c8de --- /dev/null +++ b/src/creativeRenderers.js @@ -0,0 +1,24 @@ +import {GreedyPromise} from './utils/promise.js'; +import {createInvisibleIframe} from './utils.js'; +import {RENDERER} from '../libraries/creative-renderer-display/renderer.js'; +import {hook} from './hook.js'; + +export const getCreativeRendererSource = hook('sync', function (bidResponse) { + return RENDERER; +}) + +export const getCreativeRenderer = (function() { + const renderers = {}; + return function (bidResponse) { + const src = getCreativeRendererSource(bidResponse); + if (!renderers.hasOwnProperty(src)) { + renderers[src] = new GreedyPromise((resolve) => { + const iframe = createInvisibleIframe(); + iframe.srcdoc = ``; + iframe.onload = () => resolve(iframe.contentWindow.render); + document.body.appendChild(iframe); + }) + } + return renderers[src]; + } +})(); diff --git a/src/native.js b/src/native.js index affdc855353..1b6e13c77fc 100644 --- a/src/native.js +++ b/src/native.js @@ -1,6 +1,6 @@ import { deepAccess, - deepClone, + deepClone, getDefinedParams, insertHtmlIntoIframe, isArray, isBoolean, @@ -104,6 +104,12 @@ const TRACKER_EVENTS = { 'viewable-video50': 4, } +export function isNativeResponse(bidResponse) { + // check for native data and not mediaType; it's possible + // to treat banner responses as native + return bidResponse.native && typeof bidResponse.native === 'object'; +} + /** * Recieves nativeParams from an adUnit. If the params were not of type 'type', * passes them on directly. If they were of type 'type', translate @@ -332,6 +338,23 @@ export function fireClickTrackers(nativeResponse, assetId = null, {fetchURL = tr } } +export function setNativeResponseProperties(bid, adUnit) { + const nativeOrtbRequest = adUnit?.nativeOrtbRequest; + const nativeOrtbResponse = bid.native?.ortb; + + if (nativeOrtbRequest && nativeOrtbResponse) { + const legacyResponse = toLegacyResponse(nativeOrtbResponse, nativeOrtbRequest); + Object.assign(bid.native, legacyResponse); + } + + ['rendererUrl', 'adTemplate'].forEach(prop => { + const val = adUnit?.nativeParams?.[prop]; + if (val) { + bid.native[prop] = getAssetValue(val); + } + }); +} + /** * Gets native targeting key-value pairs * @param {Object} bid @@ -340,11 +363,6 @@ export function fireClickTrackers(nativeResponse, assetId = null, {fetchURL = tr export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { let keyValues = {}; const adUnit = index.getAdUnit(bid); - if (deepAccess(adUnit, 'nativeParams.rendererUrl')) { - bid['native']['rendererUrl'] = getAssetValue(adUnit.nativeParams['rendererUrl']); - } else if (deepAccess(adUnit, 'nativeParams.adTemplate')) { - bid['native']['adTemplate'] = getAssetValue(adUnit.nativeParams['adTemplate']); - } const globalSendTargetingKeys = deepAccess( adUnit, @@ -389,41 +407,40 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { return keyValues; } -function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { - const message = { - message: 'assetResponse', - adId: data.adId, - }; - - const adUnit = index.getAdUnit(adObject); - let nativeResp = adObject.native; +function getNativeAssets(nativeProps, keys, ext = false) { + let assets = []; + Object.entries(nativeProps) + .filter(([k, v]) => v && ((ext === false && k === 'ext') || keys == null || keys.includes(k))) + .forEach(([key, value]) => { + if (ext === false && key === 'ext') { + assets.push(...getNativeAssets(value, keys, true)); + } else if (ext || NATIVE_KEYS.hasOwnProperty(key)) { + assets.push({key, value: getAssetValue(value)}); + } + }); + return assets; +} - if (adObject.native.ortb) { - message.ortb = adObject.native.ortb; +export function getNativeRenderingData(bid, adUnit, keys) { + const data = { + ...getDefinedParams(bid.native, ['rendererUrl', 'adTemplate']), + assets: getNativeAssets(bid.native, keys), + nativeKeys: CONSTANTS.NATIVE_KEYS + }; + if (bid.native.ortb) { + data.ortb = bid.native.ortb; } else if (adUnit.mediaTypes?.native?.ortb) { - message.ortb = toOrtbNativeResponse(adObject.native, adUnit.nativeOrtbRequest); + data.ortb = toOrtbNativeResponse(bid.native, adUnit.nativeOrtbRequest); } - message.assets = []; - - (keys == null ? Object.keys(nativeResp) : keys).forEach(function(key) { - if (key === 'adTemplate' && nativeResp[key]) { - message.adTemplate = getAssetValue(nativeResp[key]); - } else if (key === 'rendererUrl' && nativeResp[key]) { - message.rendererUrl = getAssetValue(nativeResp[key]); - } else if (key === 'ext') { - Object.keys(nativeResp[key]).forEach(extKey => { - if (nativeResp[key][extKey]) { - const value = getAssetValue(nativeResp[key][extKey]); - message.assets.push({ key: extKey, value }); - } - }) - } else if (nativeResp[key] && CONSTANTS.NATIVE_KEYS.hasOwnProperty(key)) { - const value = getAssetValue(nativeResp[key]); + return data; +} - message.assets.push({ key, value }); - } - }); - return message; +function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { + return { + message: 'assetResponse', + adId: data.adId, + ...getNativeRenderingData(adObject, index.getAdUnit(adObject), keys) + }; } const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(CONSTANTS.NATIVE_KEYS).map(([k, v]) => [v, k])); diff --git a/src/prebid.js b/src/prebid.js index c06fb6c1979..750a4bdee1a 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -40,7 +40,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; -import {renderAdDirect} from '../libraries/creativeRender/direct.js'; +import {renderAdDirect} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; import {fillVideoDefaults} from './video.js'; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 0ea93e7e4fb..1880f56f474 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -4,25 +4,26 @@ */ import * as events from './events.js'; -import {fireNativeTrackers, getAllAssetsMessage, getAssetMessage} from './native.js'; -import constants from './constants.json'; +import {getAllAssetsMessage, getAssetMessage} from './native.js'; +import CONSTANTS from './constants.json'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; -import {emitAdRenderFail, emitAdRenderSucceeded, handleRender} from './adRendering.js'; -import {PREBID_EVENT, PREBID_NATIVE, PREBID_REQUEST, PREBID_RESPONSE} from '../libraries/creativeRender/constants.js'; +import {handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; +import {getCreativeRendererSource} from './creativeRenderers.js'; -const BID_WON = constants.EVENTS.BID_WON; -const WON_AD_IDS = new WeakSet(); +const {REQUEST, RESPONSE, NATIVE, EVENT} = CONSTANTS.MESSAGES; + +const BID_WON = CONSTANTS.EVENTS.BID_WON; const HANDLER_MAP = { - [PREBID_REQUEST]: handleRenderRequest, - [PREBID_EVENT]: handleEventRequest, + [REQUEST]: handleRenderRequest, + [EVENT]: handleEventRequest, }; if (FEATURES.NATIVE) { Object.assign(HANDLER_MAP, { - [PREBID_NATIVE]: handleNativeRequest, + [NATIVE]: handleNativeRequest, }); } @@ -67,13 +68,24 @@ export function receiveMessage(ev) { } } +function getResizer(bidResponse) { + return function (width, height) { + resizeRemoteCreative({...bidResponse, width, height}); + } +} function handleRenderRequest(reply, message, bidResponse) { - handleRender(function (adData) { - resizeRemoteCreative(bidResponse); - reply(Object.assign({ - message: PREBID_RESPONSE, - }, adData)); - }, {options: message.options, adId: message.adId, bidResponse}); + handleRender({ + renderFn(adData) { + reply(Object.assign({ + message: RESPONSE, + renderer: getCreativeRendererSource(bidResponse) + }, adData)); + }, + resizeFn: getResizer(bidResponse), + options: message.options, + adId: message.adId, + bidResponse + }); } function handleNativeRequest(reply, data, adObject) { @@ -87,8 +99,7 @@ function handleNativeRequest(reply, data, adObject) { return; } - if (!WON_AD_IDS.has(adObject)) { - WON_AD_IDS.add(adObject); + if (adObject.status !== CONSTANTS.BID_STATUS.RENDERED) { auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); } @@ -100,13 +111,8 @@ function handleNativeRequest(reply, data, adObject) { case 'allAssetRequest': reply(getAllAssetsMessage(data, adObject)); break; - case 'resizeNativeHeight': - adObject.height = data.height; - adObject.width = data.width; - resizeRemoteCreative(adObject); - break; default: - fireNativeTrackers(data, adObject); + handleNativeMessage(data, adObject, {resizeFn: getResizer(adObject)}) } } @@ -115,40 +121,25 @@ function handleEventRequest(reply, data, adObject) { logError(`Cannot find ad '${data.adId}' for x-origin event request`); return; } - if (adObject.status !== constants.BID_STATUS.RENDERED) { - logWarn(`Received x-origin event request without corresponding render request for ad '${data.adId}'`); + if (adObject.status !== CONSTANTS.BID_STATUS.RENDERED) { + logWarn(`Received x-origin event request without corresponding render request for ad '${adObject.adId}'`); return; } - switch (data.event) { - case constants.EVENTS.AD_RENDER_FAILED: - emitAdRenderFail({ - bid: adObject, - id: data.adId, - reason: data.info.reason, - message: data.info.message - }); - break; - case constants.EVENTS.AD_RENDER_SUCCEEDED: - emitAdRenderSucceeded({ - doc: null, - bid: adObject, - id: data.adId - }); - break; - default: - logError(`Received x-origin event request for unsupported event: '${data.event}' (adId: '${data.adId}')`); - } + return handleCreativeEvent(data, adObject); } export function resizeRemoteCreative({adId, adUnitCode, width, height}) { + function getDimension(value) { + return value ? value + 'px' : '100%'; + } // resize both container div + iframe ['div', 'iframe'].forEach(elmType => { // not select element that gets removed after dfp render let element = getElementByAdUnit(elmType + ':not([style*="display: none"])'); if (element) { let elementStyle = element.style; - elementStyle.width = width ? width + 'px' : '100%'; - elementStyle.height = height + 'px'; + elementStyle.width = getDimension(width) + elementStyle.height = getDimension(height); } else { logWarn(`Unable to locate matching page element for adUnitCode ${adUnitCode}. Can't resize it to ad's dimensions. Please review setup.`); } diff --git a/src/utils.js b/src/utils.js index 7f71ff299fb..c7ce5f22f9a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -252,25 +252,37 @@ export function debugTurnedOn() { return !!config.getConfig('debug'); } +export const createIframe = (() => { + const DEFAULTS = { + border: '0px', + hspace: '0', + vspace: '0', + marginWidth: '0', + marginHeight: '0', + scrolling: 'no', + frameBorder: '0', + allowtransparency: 'true' + } + return (doc, attrs, style = {}) => { + const f = doc.createElement('iframe'); + Object.assign(f, Object.assign({}, DEFAULTS, attrs)); + Object.assign(f.style, style); + return f; + } +})(); + export function createInvisibleIframe() { - var f = document.createElement('iframe'); - f.id = getUniqueIdentifierStr(); - f.height = 0; - f.width = 0; - f.border = '0px'; - f.hspace = '0'; - f.vspace = '0'; - f.marginWidth = '0'; - f.marginHeight = '0'; - f.style.border = '0'; - f.scrolling = 'no'; - f.frameBorder = '0'; - f.src = 'about:blank'; - f.style.display = 'none'; - f.style.height = '0px'; - f.style.width = '0px'; - f.allowtransparency = 'true'; - return f; + return createIframe(document, { + id: getUniqueIdentifierStr(), + width: 0, + height: 0, + src: 'about:blank' + }, { + display: 'none', + height: '0px', + width: '0px', + border: '0px' + }); } /* diff --git a/test/spec/creative/crossDomainCreative_spec.js b/test/spec/creative/crossDomainCreative_spec.js index 765d5e5311a..f4c98aa7b50 100644 --- a/test/spec/creative/crossDomainCreative_spec.js +++ b/test/spec/creative/crossDomainCreative_spec.js @@ -1,17 +1,18 @@ -import {renderer} from '../../../libraries/creativeRender/crossDomain.js'; +import {renderer} from '../../../creative/crossDomain.js'; import { - AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, EXCEPTION, NO_AD, - PREBID_EVENT, - PREBID_REQUEST, - PREBID_RESPONSE -} from '../../../libraries/creativeRender/constants.js'; + ERROR_EXCEPTION, + EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED, + MESSAGE_EVENT, + MESSAGE_REQUEST, + MESSAGE_RESPONSE +} from '../../../creative/constants.js'; describe('cross-domain creative', () => { - let win, renderAd, messages, mkIframe, listeners; + const ORIGIN = 'https://example.com'; + let win, renderAd, messages, mkIframe; beforeEach(() => { messages = []; - listeners = []; mkIframe = sinon.stub(); win = { document: { @@ -28,7 +29,6 @@ describe('cross-domain creative', () => { } }) }, - addEventListener: sinon.stub().callsFake((_, listener) => listeners.push(listener)), parent: { postMessage: sinon.stub().callsFake((payload, targetOrigin, transfer) => { messages.push({payload: JSON.parse(payload), targetOrigin, transfer}); @@ -44,138 +44,150 @@ describe('cross-domain creative', () => { }); it('generates request message with adId and clickUrl', () => { - renderAd({adId: '123', clickUrl: 'https://click-url.com'}); + renderAd({adId: '123', clickUrl: 'https://click-url.com', pubUrl: ORIGIN}); expect(messages[0].payload).to.eql({ - message: PREBID_REQUEST, + message: MESSAGE_REQUEST, adId: '123', options: { clickUrl: 'https://click-url.com' } }) + }); + + it('runs scripts inserted through iframe srcdoc', (done) => { + const iframe = document.createElement('iframe'); + iframe.setAttribute('srcdoc', ''); + iframe.onload = function () { + expect(iframe.contentWindow.ran).to.be.true; + done(); + } + document.body.appendChild(iframe); }) - Object.entries({ - 'MessageChannel': (msg) => messages[0].transfer[0].postMessage(msg), - 'message listener': (msg) => listeners.forEach((fn) => fn({data: msg})) - }).forEach(([t, transport]) => { - describe(`when using ${t}`, () => { - function reply(msg) { - transport(JSON.stringify(msg)) - }; + describe('listens and', () => { + function reply(msg, index = 0) { + messages[index].transfer[0].postMessage(JSON.stringify(msg)); + } - it('ignores messages that are not a prebid response message', () => { - renderAd({adId: '123'}); - reply({adId: '123', ad: 'markup'}); - sinon.assert.notCalled(mkIframe); - }) + it('ignores messages that are not a prebid response message', () => { + renderAd({adId: '123', pubUrl: ORIGIN}); + reply({adId: '123', ad: 'markup'}); + sinon.assert.notCalled(mkIframe); + }) - describe('signals AD_RENDER_FAILED', () => { - it('on exception', (done) => { - mkIframe.callsFake(() => { throw new Error('error message') }); - renderAd({adId: '123'}); - reply({message: PREBID_RESPONSE, adId: '123', ad: 'markup'}); - setTimeout(() => { - expect(messages[1].payload).to.eql({ - message: PREBID_EVENT, - adId: '123', - event: AD_RENDER_FAILED, - info: { - reason: EXCEPTION, - message: 'error message' - } - }) - done(); - }, 100) - }); + it('signals AD_RENDER_FAILED on exceptions', (done) => { + mkIframe.callsFake(() => { throw new Error('error message') }); + renderAd({adId: '123', pubUrl: ORIGIN}); + reply({message: MESSAGE_RESPONSE, adId: '123', ad: 'markup'}); + setTimeout(() => { + expect(messages[1].payload).to.eql({ + message: MESSAGE_EVENT, + adId: '123', + event: EVENT_AD_RENDER_FAILED, + info: { + reason: ERROR_EXCEPTION, + message: 'error message' + } + }) + done(); + }, 100) + }); + + describe('renderer', () => { + beforeEach(() => { + win.document.createElement.callsFake(document.createElement.bind(document)); + win.document.body.appendChild.callsFake(document.body.appendChild.bind(document.body)); + }); - it('on missing ad', (done) => { - renderAd({adId: '123'}); - reply({message: PREBID_RESPONSE, adId: '123'}); + it('sets up and runs renderer', (done) => { + window._render = sinon.stub(); + const data = { + message: MESSAGE_RESPONSE, + adId: '123', + renderer: 'window.render = window.parent._render' + } + renderAd({adId: '123', pubUrl: ORIGIN}); + reply(data); + setTimeout(() => { + try { + sinon.assert.calledWith(window._render, data, sinon.match.any, win); + done() + } finally { + delete window._render; + } + }, 100) + }); + + Object.entries({ + 'throws (w/error)': ['window.render = function() { throw new Error("msg") }'], + 'throws (w/reason)': ['window.render = function() { throw {reason: "other", message: "msg"}}', 'other'], + 'is missing': [null, ERROR_EXCEPTION, null], + 'rejects (w/error)': ['window.render = function() { return Promise.reject(new Error("msg")) }'], + 'rejects (w/reason)': ['window.render = function() { return Promise.reject({reason: "other", message: "msg"}) }', 'other'], + }).forEach(([t, [renderer, reason = ERROR_EXCEPTION, message = 'msg']]) => { + it(`signals AD_RENDER_FAILED on renderer that ${t}`, (done) => { + renderAd({adId: '123', pubUrl: ORIGIN}); + reply({ + message: MESSAGE_RESPONSE, + adId: '123', + renderer + }); setTimeout(() => { sinon.assert.match(messages[1].payload, { - message: PREBID_EVENT, adId: '123', - event: AD_RENDER_FAILED, + message: MESSAGE_EVENT, + event: EVENT_AD_RENDER_FAILED, info: { - reason: NO_AD, + reason, + message: sinon.match(val => message == null || message === val) } - }) + }); done(); }, 100) }) }); - describe('rendering', () => { - let iframe; - - beforeEach(() => { - iframe = { - attrs: {}, - setAttribute: sinon.stub().callsFake((k, v) => iframe.attrs[k.toLowerCase()] = v), - contentDocument: { - open: sinon.stub(), - write: sinon.stub(), - close: sinon.stub(), - } - } - mkIframe.callsFake(() => iframe); + it('signals AD_RENDER_SUCCEEDED when renderer resolves', (done) => { + renderAd({adId: '123', pubUrl: ORIGIN}); + reply({ + message: MESSAGE_RESPONSE, + adId: '123', + renderer: 'window.render = function() { return new Promise((resolve) => { window.parent._resolve = resolve })}' }); - - it('renders adUrl as iframe src', (done) => { - renderAd({adId: '123'}); - reply({message: PREBID_RESPONSE, adId: '123', adUrl: 'some-url'}); + setTimeout(() => { + expect(messages[1]).to.not.exist; + window._resolve(); setTimeout(() => { - sinon.assert.calledWith(win.document.body.appendChild, iframe); - expect(iframe.attrs.src).to.eql('some-url'); + sinon.assert.match(messages[1].payload, { + adId: '123', + message: MESSAGE_EVENT, + event: EVENT_AD_RENDER_SUCCEEDED + }) + delete window._resolve; done(); }, 100) - }); + }, 100) + }) - it('renders ad through document.write', (done) => { - renderAd({adId: '123'}); - reply({message: PREBID_RESPONSE, adId: '123', ad: 'some-markup'}); + it('is provided a sendMessage that accepts replies', (done) => { + renderAd({adId: '123', pubUrl: ORIGIN}); + window._reply = sinon.stub(); + reply({ + adId: '123', + message: MESSAGE_RESPONSE, + renderer: 'window.render = function(_, {sendMessage}) { sendMessage("test", "data", function(reply) { window.parent._reply(reply) }) }' + }); + setTimeout(() => { + reply('response', 1); setTimeout(() => { - sinon.assert.calledWith(win.document.body.appendChild, iframe); - sinon.assert.called(iframe.contentDocument.open); - sinon.assert.calledWith(iframe.contentDocument.write, 'some-markup'); - sinon.assert.called(iframe.contentDocument.close); - done(); + try { + sinon.assert.calledWith(window._reply, sinon.match({data: JSON.stringify('response')})); + done(); + } finally { + delete window._reply; + } }, 100) - }); - - Object.entries({ - adUrl: 'mock-ad-url', - ad: 'mock-ad-markup' - }).forEach(([prop, propValue]) => { - describe(`when message has ${prop}`, () => { - beforeEach((done) => { - renderAd({adId: '123'}); - reply({ - message: PREBID_RESPONSE, - adId: '123', - [prop]: propValue, - width: 100, - height: 200 - }); - setTimeout(done, 100); - }); - - it('emits AD_RENDER_SUCCEEDED', () => { - expect(messages[1].payload).to.eql({ - message: PREBID_EVENT, - adId: '123', - event: AD_RENDER_SUCCEEDED - }) - }); - - it('sets iframe height / width to ad height / width', () => { - sinon.assert.match(iframe.attrs, { - width: 100, - height: 200 - }) - }) - }) - }) + }, 100) }); }); }); diff --git a/test/spec/creative/displayRenderer_spec.js b/test/spec/creative/displayRenderer_spec.js new file mode 100644 index 00000000000..6be6e90813a --- /dev/null +++ b/test/spec/creative/displayRenderer_spec.js @@ -0,0 +1,55 @@ +import {render} from 'creative/renderers/display/renderer.js'; +import {ERROR_NO_AD} from '../../../creative/renderers/display/constants.js'; + +describe('Creative renderer - display', () => { + let doc, mkFrame, sendMessage; + beforeEach(() => { + mkFrame = sinon.stub().callsFake((doc, attrs) => Object.assign({doc}, attrs)); + sendMessage = sinon.stub(); + doc = { + body: { + appendChild: sinon.stub() + } + }; + }); + + function runRenderer(data) { + return render(data, {sendMessage, mkFrame}, {document: doc}); + } + + it('throws when both ad and adUrl are missing', () => { + expect(() => { + try { + runRenderer({}) + } catch (e) { + expect(e.reason).to.eql(ERROR_NO_AD); + throw e; + } + }).to.throw(); + }) + + Object.entries({ + ad: 'srcdoc', + adUrl: 'src' + }).forEach(([adProp, frameProp]) => { + describe(`when ad has ${adProp}`, () => { + let data; + beforeEach(() => { + data = { + [adProp]: 'ad', + width: 123, + height: 321 + } + }) + it(`drops iframe with ${frameProp} = ${adProp}`, () => { + runRenderer(data); + sinon.assert.calledWith(doc.body.appendChild, { + doc, + [frameProp]: 'ad', + width: data.width, + height: data.height + }) + }) + }) + }) +}) diff --git a/test/spec/creative/nativeRenderer_spec.js b/test/spec/creative/nativeRenderer_spec.js new file mode 100644 index 00000000000..66e81a517c7 --- /dev/null +++ b/test/spec/creative/nativeRenderer_spec.js @@ -0,0 +1,298 @@ +import {getAdMarkup, getReplacements, getReplacer} from '../../../creative/renderers/native/renderer.js'; +import {ACTION_CLICK, ACTION_IMP, ACTION_RESIZE, MESSAGE_NATIVE} from '../../../creative/renderers/native/constants.js'; + +describe('Native creative renderer', () => { + let win; + beforeEach(() => { + win = {}; + }); + + describe('getAdMarkup', () => { + let loadScript; + beforeEach(() => { + loadScript = sinon.stub(); + }); + it('uses rendererUrl if present', () => { + win.document = {} + const data = { + assets: ['1', '2'], + ortb: 'ortb', + rendererUrl: 'renderer' + }; + const renderAd = sinon.stub().returns('markup'); + loadScript.returns(Promise.resolve().then(() => { + win.renderAd = renderAd; + })); + return getAdMarkup('123', data, null, win, loadScript).then((markup) => { + expect(markup).to.eql('markup'); + sinon.assert.calledWith(loadScript, data.rendererUrl, sinon.match(arg => arg === win.document)); + sinon.assert.calledWith(renderAd, sinon.match(arg => { + expect(arg).to.have.members(data.assets); + expect(arg.ortb).to.eql(data.ortb); + return true; + })); + }); + }); + describe('otherwise, calls replacer', () => { + let replacer; + beforeEach(() => { + replacer = sinon.stub().returns('markup'); + }); + it('with adTemplate, if present', () => { + return getAdMarkup('123', {adTemplate: 'tpl'}, replacer, win).then((result) => { + expect(result).to.eql('markup'); + sinon.assert.calledWith(replacer, 'tpl'); + }); + }); + it('with document body otherwise', () => { + win.document = {body: {innerHTML: 'body'}}; + return getAdMarkup('123', {}, replacer, win).then((result) => { + expect(result).to.eql('markup'); + sinon.assert.calledWith(replacer, 'body'); + }) + }) + }) + }); + + describe('getReplacer', () => { + function expectReplacements(replacer, replacements) { + Object.entries(replacements).forEach(([placeholder, repl]) => { + expect(replacer(`.${placeholder}.${placeholder}.`)).to.eql(`.${repl}.${repl}.`); + }) + } + it('uses empty strings for missing legacy assets', () => { + const repl = getReplacer('123', { + nativeKeys: { + 'k': 'hb_native_k' + } + }); + expectReplacements(repl, { + '##hb_native_k##': '', + 'hb_native_k:123': '' + }) + }); + + it('uses empty string for missing ORTB assets', () => { + const repl = getReplacer('', { + ortb: { + assets: [{ + id: 1, + link: {url: 'l1'}, + data: {value: 'v1'} + }] + } + }); + expectReplacements(repl, { + '##hb_native_asset_id_1##': 'v1', + '##hb_native_asset_id_2##': '', + '##hb_native_asset_link_id_1##': 'l1', + '##hb_native_asset_link_id_2##': '' + }); + }); + + it('replaces placeholders for for legacy assets', () => { + const repl = getReplacer('123', { + assets: [ + {key: 'k1', value: 'v1'}, {key: 'k2', value: 'v2'} + ], + nativeKeys: { + k1: 'hb_native_k1', + k2: 'hb_native_k2' + } + }); + expectReplacements(repl, { + '##hb_native_k1##': 'v1', + 'hb_native_k1:123': 'v1', + '##hb_native_k2##': 'v2', + 'hb_native_k2:123': 'v2' + }) + }); + + describe('ORTB response top-level (non-asset) fields', () => { + const ortb = { + link: { + url: 'link.url' + }, + privacy: 'privacy.url' + }; + const expected = { + '##hb_native_linkurl##': 'link.url', + '##hb_native_privacy##': 'privacy.url' + }; + it('replaces placeholders', () => { + const repl = getReplacer('123', { + ortb + }); + expectReplacements(repl, expected); + }); + it('gives them precedence over legacy counterparts', () => { + const repl = getReplacer('123', { + ortb, + assets: [ + {key: 'clickUrl', value: 'overridden'}, + {key: 'privacyLink', value: 'overridden'} + ], + nativeKeys: { + clickUrl: 'hb_native_linkurl', + privacyLink: 'hb_native_privacy' + } + }); + expectReplacements(repl, expected); + }); + it('uses empty string for missing assets', () => { + const repl = getReplacer('123', { + ortb: {} + }); + expectReplacements(repl, { + '##hb_native_linkurl##': '', + '##hb_native_privacy##': '', + }) + }); + }); + + Object.entries({ + title: {text: 'val'}, + data: {value: 'val'}, + img: {url: 'val'}, + video: {vasttag: 'val'} + }).forEach(([type, contents]) => { + describe(`for ortb ${type} asset`, () => { + let ortb; + beforeEach(() => { + ortb = { + assets: [ + { + id: 123, + [type]: contents + } + ] + }; + }); + it('replaces placeholder', () => { + const repl = getReplacer('', {ortb}); + expectReplacements(repl, { + '##hb_native_asset_id_123##': 'val' + }) + }); + it('replaces link placeholders', () => { + ortb.assets[0].link = {url: 'link'}; + const repl = getReplacer('', {ortb}); + expectReplacements(repl, { + '##hb_native_asset_link_id_123##': 'link' + }) + }); + }); + }); + }); + + describe('render', () => { + let getMarkup, sendMessage, adId, nativeData, exc; + beforeEach(() => { + adId = '123'; + nativeData = {} + getMarkup = sinon.stub(); + sendMessage = sinon.stub() + exc = sinon.stub(); + win.document = { + querySelectorAll() { return [] }, + body: {} + } + }); + + function runRender() { + return render({adId, native: nativeData}, {sendMessage, exc}, win, getMarkup) + } + + it('replaces placeholders in head, if present', () => { + getMarkup.returns(Promise.resolve('')) + win.document.head = {innerHTML: '##hb_native_asset_id_1##'}; + nativeData.ortb = { + assets: [ + {id: 1, data: {value: 'repl'}} + ] + }; + return runRender().then(() => { + expect(win.document.head.innerHTML).to.eql('repl'); + }) + }); + + it('drops markup on body, and fires imp trackers', () => { + getMarkup.returns(Promise.resolve('markup')); + return runRender().then(() => { + expect(win.document.body.innerHTML).to.eql('markup'); + sinon.assert.calledWith(sendMessage, MESSAGE_NATIVE, {action: ACTION_IMP}); + }) + }); + + it('runs postRenderAd if defined', () => { + win.postRenderAd = sinon.stub(); + getMarkup.returns(Promise.resolve('markup')); + return runRender().then(() => { + sinon.assert.calledWith(win.postRenderAd, sinon.match({ + adId, + ...nativeData + })) + }) + }) + + it('rejects on error', (done) => { + const err = new Error('failure'); + getMarkup.returns(Promise.reject(err)); + runRender().catch((e) => { + expect(e).to.eql(err); + done(); + }) + }); + + describe('requests resize', () => { + beforeEach(() => { + getMarkup.returns(Promise.resolve('markup')); + win.document.body.offsetHeight = 123; + win.document.body.offsetWidth = 321; + }); + + it('immediately, if document is loaded', () => { + win.document.readyState = 'complete'; + return runRender().then(() => { + sinon.assert.calledWith(sendMessage, MESSAGE_NATIVE, {action: ACTION_RESIZE, height: 123, width: 321}) + }) + }); + + it('on document load otherwise', () => { + return runRender().then(() => { + sinon.assert.neverCalledWith(sendMessage, MESSAGE_NATIVE, sinon.match({action: ACTION_RESIZE})); + win.onload(); + sinon.assert.calledWith(sendMessage, MESSAGE_NATIVE, {action: ACTION_RESIZE, height: 123, width: 321}); + }) + }) + }) + + describe('click trackers', () => { + let iframe; + beforeEach(() => { + iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + win.document = iframe.contentDocument; + }) + afterEach(() => { + document.body.removeChild(iframe); + }) + + it('are fired on click', () => { + getMarkup.returns(Promise.resolve('
')); + return runRender().then(() => { + win.document.querySelector('#target').click(); + sinon.assert.calledWith(sendMessage, MESSAGE_NATIVE, sinon.match({action: ACTION_CLICK})); + }) + }); + + it('pass assetId if provided', () => { + getMarkup.returns(Promise.resolve('
')); + return runRender().then(() => { + win.document.querySelector('#target').click(); + sinon.assert.calledWith(sendMessage, MESSAGE_NATIVE, {action: ACTION_CLICK, assetId: '123'}) + }); + }); + }); + }); +}); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index bf1c745d8cc..9184601a76d 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -9,7 +9,12 @@ import { decorateAdUnitsWithNativeParams, isOpenRTBBidRequestValid, isNativeOpenRTBBidValid, - toOrtbNativeRequest, toOrtbNativeResponse, legacyPropertiesToOrtbNative, fireImpressionTrackers, fireClickTrackers, + toOrtbNativeRequest, + toOrtbNativeResponse, + legacyPropertiesToOrtbNative, + fireImpressionTrackers, + fireClickTrackers, + setNativeResponseProperties, } from 'src/native.js'; import CONSTANTS from 'src/constants.json'; import { stubAuctionIndex } from '../helpers/indexStub.js'; @@ -345,73 +350,10 @@ describe('native.js', function () { ]); }); - it('should copy over rendererUrl to bid object and include it in targeting', function () { - const adUnit = { - adUnitId: 'au', - nativeParams: { - image: { - required: true, - sizes: [150, 50], - }, - title: { - required: true, - len: 80, - }, - rendererUrl: { - url: 'https://www.renderer.com/', - }, - }, - }; - const targeting = getNativeTargeting(bid, deps(adUnit)); - - expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title, - CONSTANTS.NATIVE_KEYS.body, - CONSTANTS.NATIVE_KEYS.cta, - CONSTANTS.NATIVE_KEYS.image, - CONSTANTS.NATIVE_KEYS.icon, - CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl, - CONSTANTS.NATIVE_KEYS.privacyLink, - CONSTANTS.NATIVE_KEYS.rendererUrl, - ]); - - expect(bid.native.rendererUrl).to.deep.equal('https://www.renderer.com/'); - delete bid.native.rendererUrl; - }); - - it('should copy over adTemplate to bid object and include it in targeting', function () { - const adUnit = { - adUnitId: 'au', - nativeParams: { - image: { - required: true, - sizes: [150, 50], - }, - title: { - required: true, - len: 80, - }, - adTemplate: '

##hb_native_body##

', - }, - }; - const targeting = getNativeTargeting(bid, deps(adUnit)); - - expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title, - CONSTANTS.NATIVE_KEYS.body, - CONSTANTS.NATIVE_KEYS.cta, - CONSTANTS.NATIVE_KEYS.image, - CONSTANTS.NATIVE_KEYS.icon, - CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl, - CONSTANTS.NATIVE_KEYS.privacyLink, - ]); - - expect(bid.native.adTemplate).to.deep.equal( - '

##hb_native_body##

' - ); - delete bid.native.adTemplate; + it('should include rendererUrl in targeting', function () { + const rendererUrl = 'https://www.renderer.com/'; + const targeting = getNativeTargeting({...bid, native: {...bid.native, rendererUrl: {url: rendererUrl}}}, deps({})); + expect(targeting[CONSTANTS.NATIVE_KEYS.rendererUrl]).to.eql(rendererUrl); }); it('fires impression trackers', function () { @@ -646,6 +588,58 @@ describe('native.js', function () { expect(actual.impressionTrackers).to.contain('https://sample-imp.com'); }); }); + + describe('setNativeResponseProperties', () => { + let adUnit; + beforeEach(() => { + adUnit = { + mediaTypes: { + native: {}, + }, + nativeParams: {} + }; + }); + it('sets legacy response', () => { + adUnit.nativeOrtbRequest = { + assets: [{ + id: 1, + data: { + type: 2 + } + }] + }; + const ortbBid = { + ...bid, + native: { + ortb: { + link: { + url: 'clickurl' + }, + assets: [{ + id: 1, + data: { + value: 'body' + } + }] + } + } + }; + setNativeResponseProperties(ortbBid, adUnit); + expect(ortbBid.native.clickUrl).to.eql('clickurl'); + expect(ortbBid.native.body).to.eql('body'); + }); + + it('sets rendererUrl', () => { + adUnit.nativeParams.rendererUrl = {url: 'renderer'}; + setNativeResponseProperties(bid, adUnit); + expect(bid.native.rendererUrl).to.eql('renderer'); + }); + it('sets adTemplate', () => { + adUnit.nativeParams.adTemplate = 'template'; + setNativeResponseProperties(bid, adUnit); + expect(bid.native.adTemplate).to.eql('template'); + }); + }); }); describe('validate native openRTB', function () { diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js new file mode 100644 index 00000000000..c2f62842c7e --- /dev/null +++ b/test/spec/unit/adRendering_spec.js @@ -0,0 +1,248 @@ +import * as events from 'src/events.js'; +import * as utils from 'src/utils.js'; +import { + doRender, + getRenderingData, + handleCreativeEvent, + handleNativeMessage, + handleRender +} from '../../../src/adRendering.js'; +import CONSTANTS from 'src/constants.json'; +import {expect} from 'chai/index.mjs'; +import {config} from 'src/config.js'; +import {VIDEO} from '../../../src/mediaTypes.js'; +import {auctionManager} from '../../../src/auctionManager.js'; + +describe('adRendering', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'logWarn'); + sandbox.stub(utils, 'logError'); + }) + afterEach(() => { + sandbox.restore(); + }) + + describe('getRenderingData', () => { + let bidResponse; + beforeEach(() => { + bidResponse = {}; + }); + + ['ad', 'adUrl'].forEach((prop) => { + describe(`on ${prop}`, () => { + it('replaces AUCTION_PRICE macro', () => { + bidResponse[prop] = 'pre${AUCTION_PRICE}post'; + bidResponse.cpm = 123; + const result = getRenderingData(bidResponse); + expect(result[prop]).to.eql('pre123post'); + }); + it('replaces CLICKTHROUGH macro', () => { + bidResponse[prop] = 'pre${CLICKTHROUGH}post'; + const result = getRenderingData(bidResponse, {clickUrl: 'clk'}); + expect(result[prop]).to.eql('preclkpost'); + }); + it('defaults CLICKTHROUGH to empty string', () => { + bidResponse[prop] = 'pre${CLICKTHROUGH}post'; + const result = getRenderingData(bidResponse); + expect(result[prop]).to.eql('prepost'); + }); + }); + }); + }) + + describe('rendering logic', () => { + let bidResponse, renderFn, resizeFn, adId; + beforeEach(() => { + sandbox.stub(events, 'emit'); + renderFn = sinon.stub(); + resizeFn = sinon.stub(); + adId = 123; + bidResponse = { + adId + } + }); + + function expectAdRenderFailedEvent(reason) { + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({adId, reason})); + } + + describe('doRender', () => { + let getRenderingDataStub; + function getRenderingDataHook(next, ...args) { + next.bail(getRenderingDataStub(...args)); + } + before(() => { + getRenderingData.before(getRenderingDataHook, 999); + }) + after(() => { + getRenderingData.getHooks({hook: getRenderingDataHook}).remove(); + }); + beforeEach(() => { + getRenderingDataStub = sinon.stub(); + }) + + describe('when the ad has a renderer', () => { + let bidResponse; + beforeEach(() => { + bidResponse = { + adId: 'mock-ad-id', + renderer: { + url: 'some-custom-renderer', + render: sinon.stub() + } + } + }); + + it('does not invoke renderFn, but the renderer instead', () => { + doRender({renderFn, bidResponse}); + sinon.assert.notCalled(renderFn); + sinon.assert.called(bidResponse.renderer.render); + }); + + it('emits AD_RENDER_SUCCEDED', () => { + doRender({renderFn, bidResponse}); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ + bid: bidResponse, + adId: bidResponse.adId + })); + }); + }); + + if (FEATURES.VIDEO) { + it('should emit AD_RENDER_FAILED on video bids', () => { + bidResponse.mediaType = VIDEO; + doRender({renderFn, bidResponse}); + expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT) + }); + } + + it('invokes renderFn with rendering data', () => { + const data = {ad: 'creative'}; + getRenderingDataStub.returns(data); + doRender({renderFn, resizeFn, bidResponse}); + sinon.assert.calledWith(renderFn, sinon.match({ + adId: bidResponse.adId, + ...data + })) + }); + + it('invokes resizeFn with w/h from rendering data', () => { + getRenderingDataStub.returns({width: 123, height: 321}); + doRender({renderFn, resizeFn, bidResponse}); + sinon.assert.calledWith(resizeFn, 123, 321); + }); + + it('does not invoke resizeFn if rendering data has no w/h', () => { + getRenderingDataStub.returns({}); + doRender({renderFn, resizeFn, bidResponse}); + sinon.assert.notCalled(resizeFn); + }) + }); + + describe('handleRender', () => { + let doRenderStub + function doRenderHook(next, ...args) { + next.bail(doRenderStub(...args)); + } + before(() => { + doRender.before(doRenderHook, 999); + }) + after(() => { + doRender.getHooks({hook: doRenderHook}).remove(); + }) + beforeEach(() => { + sandbox.stub(auctionManager, 'addWinningBid'); + doRenderStub = sinon.stub(); + }) + describe('should emit AD_RENDER_FAILED', () => { + it('when bidResponse is missing', () => { + handleRender({adId}); + expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD); + sinon.assert.notCalled(doRenderStub); + }); + it('on exceptions', () => { + doRenderStub.throws(new Error()); + handleRender({adId, bidResponse}); + expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION); + }); + }) + + describe('when bid was already rendered', () => { + beforeEach(() => { + bidResponse.status = CONSTANTS.BID_STATUS.RENDERED; + }); + afterEach(() => { + config.resetConfig(); + }) + it('should emit STALE_RENDER', () => { + handleRender({adId, bidResponse}); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.STALE_RENDER, bidResponse); + sinon.assert.called(doRenderStub); + }); + it('should skip rendering if suppressStaleRender', () => { + config.setConfig({auctionOptions: {suppressStaleRender: true}}); + handleRender({adId, bidResponse}); + sinon.assert.notCalled(doRenderStub); + }) + }); + + it('should mark bid as won and emit BID_WON', () => { + handleRender({renderFn, bidResponse}); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.BID_WON, bidResponse); + sinon.assert.calledWith(auctionManager.addWinningBid, bidResponse); + }) + }) + }) + + describe('handleCreativeEvent', () => { + let bid; + beforeEach(() => { + sandbox.stub(events, 'emit'); + bid = { + status: CONSTANTS.BID_STATUS.RENDERED + } + }); + it('emits AD_RENDER_FAILED with given reason', () => { + handleCreativeEvent({event: CONSTANTS.EVENTS.AD_RENDER_FAILED, info: {reason: 'reason', message: 'message'}}, bid); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({bid, reason: 'reason', message: 'message'})); + }); + + it('emits AD_RENDER_SUCCEEDED', () => { + handleCreativeEvent({event: CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED}, bid); + sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({bid})); + }); + + it('logs an error on other events', () => { + handleCreativeEvent({event: 'unsupported'}, bid); + sinon.assert.called(utils.logError); + sinon.assert.notCalled(events.emit); + }); + }); + + describe('handleNativeMessage', () => { + if (!FEATURES.NATIVE) return; + let bid; + beforeEach(() => { + bid = { + adId: '123' + }; + }) + + it('should resize', () => { + const resizeFn = sinon.stub(); + handleNativeMessage({action: 'resizeNativeHeight', height: 100}, bid, {resizeFn}); + sinon.assert.calledWith(resizeFn, undefined, 100); + }); + + it('should fire trackers', () => { + const data = { + action: 'click' + }; + const fireTrackers = sinon.stub(); + handleNativeMessage(data, bid, {fireTrackers}); + sinon.assert.calledWith(fireTrackers, data, bid); + }) + }) +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index e61171c3a22..7f55a2cddf0 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -26,6 +26,8 @@ import {createBid} from '../../../src/bidfactory.js'; import {enrichFPD} from '../../../src/fpd/enrichment.js'; import {mockFpdEnrichments} from '../../helpers/fpd.js'; import {generateUUID} from '../../../src/utils.js'; +import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; + var assert = require('chai').assert; var expect = require('chai').expect; @@ -200,11 +202,13 @@ window.apntag = { describe('Unit: Prebid Module', function () { let bidExpiryStub, sandbox; - before(() => { + before((done) => { hook.ready(); $$PREBID_GLOBAL$$.requestBids.getHooks().remove(); resetDebugging(); sinon.stub(filters, 'isActualBid').returns(true); // stub this out so that we can use vanilla objects as bids + // preload creative renderer + getCreativeRenderer({}).then(() => done()); }); beforeEach(function () { @@ -1086,35 +1090,6 @@ describe('Unit: Prebid Module', function () { expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); }); - it('should find correct gpt slot based on ad id rather than ad unit code when resizing secure creative', function () { - var slots = [ - new Slot('div-not-matching-adunit-code-1', config.adUnitCodes[0]), - new Slot('div-not-matching-adunit-code-2', config.adUnitCodes[0]), - new Slot('div-not-matching-adunit-code-3', config.adUnitCodes[0]) - ]; - - slots[1].setTargeting('hb_adid', ['someAdId']); - slots[1].spyGetSlotElementId.resetHistory(); - window.googletag.pubads().setSlots(slots); - - const mockAdObject = { - adId: 'someAdId', - ad: '', - adUrl: 'http://creative.prebid.org/${AUCTION_PRICE}', - width: 300, - height: 250, - renderer: null, - cpm: '1.00', - adUnitCode: config.adUnitCodes[0], - }; - - resizeRemoteCreative(mockAdObject); - - expect(slots[0].spyGetSlotElementId.called).to.equal(false); - expect(slots[1].spyGetSlotElementId.called).to.equal(true); - expect(slots[2].spyGetSlotElementId.called).to.equal(false); - }); - it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + ' append to key name', function () { var slots = createSlotArray(); @@ -1236,10 +1211,14 @@ describe('Unit: Prebid Module', function () { height: 0 } }, + body: { + appendChild: sinon.stub() + }, getElementsByTagName: sinon.stub(), querySelector: sinon.stub(), createElement: sinon.stub(), }; + doc.defaultView.document = doc; elStub = { insertBefore: sinon.stub() diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 75813245298..a7be4e327f0 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -1,4 +1,4 @@ -import {getReplier, receiveMessage} from 'src/secureCreatives.js'; +import {getReplier, receiveMessage, resizeRemoteCreative} from 'src/secureCreatives.js'; import * as utils from 'src/utils.js'; import {getAdUnits, getBidRequests, getBidResponses} from 'test/fixtures/fixtures.js'; import {auctionManager} from 'src/auctionManager.js'; @@ -7,10 +7,11 @@ import * as native from 'src/native.js'; import {fireNativeTrackers, getAllAssetsMessage} from 'src/native.js'; import * as events from 'src/events.js'; import {config as configObj} from 'src/config.js'; +import * as creativeRenderers from 'src/creativeRenderers.js'; import 'src/prebid.js'; +import 'modules/nativeRendering.js'; import {expect} from 'chai'; -import {handleRender} from '../../../src/adRendering.js'; var CONSTANTS = require('src/constants.json'); @@ -63,78 +64,6 @@ describe('secureCreatives', () => { }); }); - describe('handleRender', () => { - let bidResponse, renderFn, result; - beforeEach(() => { - result = null; - renderFn = sinon.stub().callsFake((r) => { result = r; }); - bidResponse = { - adId: 123 - } - }); - - describe('when the ad has a renderer', () => { - let bidResponse; - beforeEach(() => { - sandbox.stub(events, 'emit'); - bidResponse = { - adId: 'mock-ad-id', - renderer: { - url: 'some-custom-renderer', - render: sinon.stub() - } - } - }); - - it('does not invoke renderFn, but the renderer instead', () => { - handleRender(renderFn, {bidResponse}); - sinon.assert.notCalled(renderFn); - sinon.assert.called(bidResponse.renderer.render); - }); - - it('emits AD_RENDER_SUCCEDED', () => { - handleRender(renderFn, {bidResponse}); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ - bid: bidResponse, - adId: bidResponse.adId - })); - }); - - it('emits AD_RENDER_FAILED', () => { - const err = new Error('error message'); - bidResponse.renderer.render.throws(err); - handleRender(renderFn, {bidResponse}); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ - bid: bidResponse, - adId: bidResponse.adId, - reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, - message: err.message - })); - }) - }); - - ['ad', 'adUrl'].forEach((prop) => { - describe(`on ${prop}`, () => { - it('replaces AUCTION_PRICE macro', () => { - bidResponse[prop] = 'pre${AUCTION_PRICE}post'; - bidResponse.cpm = 123; - handleRender(renderFn, {adId: 123, bidResponse}); - expect(result[prop]).to.eql('pre123post'); - }); - it('replaces CLICKTHROUGH macro', () => { - bidResponse[prop] = 'pre${CLICKTHROUGH}post'; - handleRender(renderFn, {adId: 123, bidResponse, options: {clickUrl: 'clk'}}); - expect(result[prop]).to.eql('preclkpost'); - }); - it('defaults CLICKTHROUGH to empty string', () => { - bidResponse[prop] = 'pre${CLICKTHROUGH}post'; - handleRender(renderFn, {adId: 123, bidResponse}); - expect(result[prop]).to.eql('prepost'); - }); - }); - }); - }); - describe('receiveMessage', function() { const bidId = 1; const warning = `Ad id ${bidId} has been rendered before`; @@ -196,19 +125,15 @@ describe('secureCreatives', () => { }); beforeEach(function() { - spyAddWinningBid = sinon.spy(auctionManager, 'addWinningBid'); - spyLogWarn = sinon.spy(utils, 'logWarn'); - stubFireNativeTrackers = sinon.stub(native, 'fireNativeTrackers').callsFake(message => { return message.action; }); - stubGetAllAssetsMessage = sinon.stub(native, 'getAllAssetsMessage'); - stubEmit = sinon.stub(events, 'emit'); + spyAddWinningBid = sandbox.spy(auctionManager, 'addWinningBid'); + spyLogWarn = sandbox.spy(utils, 'logWarn'); + stubFireNativeTrackers = sandbox.stub(native, 'fireNativeTrackers').callsFake(message => { return message.action; }); + stubGetAllAssetsMessage = sandbox.stub(native, 'getAllAssetsMessage'); + stubEmit = sandbox.stub(events, 'emit'); }); afterEach(function() { - spyAddWinningBid.restore(); - spyLogWarn.restore(); - stubFireNativeTrackers.restore(); - stubGetAllAssetsMessage.restore(); - stubEmit.restore(); + sandbox.restore(); resetAuction(); adResponse.adId = bidId; }); @@ -352,6 +277,66 @@ describe('secureCreatives', () => { adId: bidId })); }); + + it('should include renderers in responses', () => { + sandbox.stub(creativeRenderers, 'getCreativeRendererSource').returns('mock-renderer'); + pushBidResponseToAuction({}); + const ev = makeEvent({ + source: { + postMessage: sinon.stub() + }, + data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) + }); + receiveMessage(ev); + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + }); + + if (FEATURES.NATIVE) { + it('should include native rendering data in responses', () => { + const bid = { + native: { + ortb: { + assets: [ + { + id: 1, + data: { + type: 2, + value: 'vbody' + } + } + ] + }, + body: 'vbody', + adTemplate: 'tpl', + rendererUrl: 'rurl' + } + } + pushBidResponseToAuction(bid); + const ev = makeEvent({ + source: { + postMessage: sinon.stub() + }, + data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) + }) + receiveMessage(ev); + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { + const data = JSON.parse(ob); + ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); + const native = data.native; + sinon.assert.match(native, { + ortb: bid.native.ortb, + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + }) + expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + body: 'vbody' + }); + return true; + })) + }) + } }); describe('Prebid Native', function() { @@ -412,45 +397,6 @@ describe('secureCreatives', () => { receiveMessage(ev); stubEmit.withArgs(CONSTANTS.EVENTS.BID_WON, adResponse).calledOnce; }); - - it('Prebid native should fire trackers', function () { - let adId = 2; - pushBidResponseToAuction({adId}); - - const data = { - adId: adId, - message: 'Prebid Native', - action: 'click', - }; - - const ev = makeEvent({ - data: JSON.stringify(data), - source: { - postMessage: sinon.stub() - }, - origin: 'any origin' - }); - - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); - - resetHistories(ev.source.postMessage); - - delete data.action; - ev.data = JSON.stringify(data); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubFireNativeTrackers); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON); - sinon.assert.notCalled(spyAddWinningBid); - - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); - }); }); describe('Prebid Event', () => { @@ -504,4 +450,53 @@ describe('secureCreatives', () => { }); }); }); + + describe('resizeRemoteCreative', () => { + let origGpt; + before(() => { + origGpt = window.googletag; + }); + after(() => { + window.googletag = origGpt; + }); + function mockSlot(elementId, pathId) { + let targeting = {}; + return { + getSlotElementId: sinon.stub().callsFake(() => elementId), + getAdUnitPath: sinon.stub().callsFake(() => pathId), + setTargeting: sinon.stub().callsFake((key, value) => { + value = Array.isArray(value) ? value : [value]; + targeting[key] = value; + }), + getTargetingKeys: sinon.stub().callsFake(() => Object.keys(targeting)), + getTargeting: sinon.stub().callsFake((key) => targeting[key] || []) + } + } + let slots; + beforeEach(() => { + slots = [ + mockSlot('div1', 'au1'), + mockSlot('div2', 'au2'), + mockSlot('div3', 'au3') + ] + window.googletag = { + pubads: sinon.stub().returns({ + getSlots: sinon.stub().returns(slots) + }) + }; + sandbox.stub(document, 'getElementById'); + }) + + it('should find correct gpt slot based on ad id rather than ad unit code when resizing secure creative', function () { + slots[1].setTargeting('hb_adid', ['adId']); + resizeRemoteCreative({ + adId: 'adId', + width: 300, + height: 250, + }); + [0, 2].forEach((i) => sinon.assert.notCalled(slots[i].getSlotElementId)) + sinon.assert.called(slots[1].getSlotElementId); + sinon.assert.calledWith(document.getElementById, 'div2'); + }); + }) }); diff --git a/webpack.creative.js b/webpack.creative.js index 7279455e155..86f5f24d580 100644 --- a/webpack.creative.js +++ b/webpack.creative.js @@ -10,8 +10,14 @@ module.exports = { }, entry: { 'creative': { - import: './libraries/creativeRender/crossDomain.js', + import: './creative/crossDomain.js', }, + 'renderers/display': { + import: './creative/renderers/display/renderer.js' + }, + 'renderers/native': { + import: './creative/renderers/native/renderer.js' + } }, output: { path: path.resolve('./build/creative'), From adbcf7127ae59a88f4fe319adab11319791888e9 Mon Sep 17 00:00:00 2001 From: macinjosh32 Date: Wed, 21 Feb 2024 13:36:47 -0500 Subject: [PATCH 478/760] 33Across User ID sub-module: Introduce first-party ID support (#10714) * Introduce first-party ID support to 33Across User ID sub-module. Resolves IDG-1216. * Ensure first-party ID is removed for local storage in situations like GPP conssent change * 33Across User ID sub-module: Add cookie storage support for first-party ID, * 33Across User ID sub-module: Also remove first-party ID from cookie storage * remove duplicated 33across ID test * clear 33across ID from localstorage * Add configuration flag for 1PID * Suppress 33across ID requests where GDPR applies --------- Co-authored-by: Joshua Poritz Co-authored-by: Carlos Felix Co-authored-by: Aparna Rao --- modules/33acrossIdSystem.js | 94 +++++- modules/33acrossIdSystem.md | 1 + test/spec/modules/33acrossIdSystem_spec.js | 347 +++++++++++++++++++-- 3 files changed, 408 insertions(+), 34 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 0cb1b1f3382..33086562111 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -5,10 +5,12 @@ * @requires module:modules/userId */ -import { logMessage, logError } from '../src/utils.js'; +import { logMessage, logError, logWarn } from '../src/utils.js'; import { ajaxBuilder } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js'; +import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -20,35 +22,42 @@ const MODULE_NAME = '33acrossId'; const API_URL = 'https://lexicon.33across.com/v1/envelope'; const AJAX_TIMEOUT = 10000; const CALLER_NAME = 'pbjs'; +const GVLID = 58; -function getEnvelope(response) { +const STORAGE_FPID_KEY = '33acrossIdFp'; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +function calculateResponseObj(response) { if (!response.succeeded) { if (response.error == 'Cookied User') { logMessage(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); } else { logError(`${MODULE_NAME}: Unsuccessful response`.concat(' ', response.error)); } - return; + return {}; } if (!response.data.envelope) { logMessage(`${MODULE_NAME}: No envelope was received`); - return; + return {}; } - return response.data.envelope; + return { + envelope: response.data.envelope, + fp: response.data.fp + }; } -function calculateQueryStringParams(pid, gdprConsentData) { +function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { const uspString = uspDataHandler.getConsentData(); - const gdprApplies = Boolean(gdprConsentData?.gdprApplies); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); const params = { pid, - gdpr: Number(gdprApplies), + gdpr: 0, src: CALLER_NAME, ver: '$prebid.version$', coppa: Number(coppaValue) @@ -69,9 +78,49 @@ function calculateQueryStringParams(pid, gdprConsentData) { params.gdpr_consent = gdprConsentData.consentString; } + const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig); + if (fp) { + params.fp = fp; + } + return params; } +function deleteFromStorage(key) { + if (storage.cookiesAreEnabled()) { + const expiredDate = new Date(0).toUTCString(); + + storage.setCookie(key, '', expiredDate, 'Lax'); + } + + storage.removeDataFromLocalStorage(key); +} + +function storeValue(key, value, storageConfig = {}) { + if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { + const expirationInMs = 60 * 60 * 24 * 1000 * storageConfig.expires; + const expirationTime = new Date(Date.now() + expirationInMs); + + storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax'); + } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { + storage.setDataInLocalStorage(key, value); + } +} + +function getStoredValue(key, storageConfig = {}) { + if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { + return storage.getDataFromLocalStorage(key); + } +} + +function handleFpId(fpId, storageConfig = {}) { + fpId + ? storeValue(STORAGE_FPID_KEY, fpId, storageConfig) + : deleteFromStorage(STORAGE_FPID_KEY); +} + /** @type {Submodule} */ export const thirthyThreeAcrossIdSubmodule = { /** @@ -80,7 +129,7 @@ export const thirthyThreeAcrossIdSubmodule = { */ name: MODULE_NAME, - gvlid: 58, + gvlid: GVLID, /** * decode the stored id value for passing to bid requests @@ -102,34 +151,49 @@ export const thirthyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { } }, gdprConsentData) { + getId({ params = { }, storage: storageConfig }, gdprConsentData) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); return; } - const { pid, apiUrl = API_URL } = params; + if (gdprConsentData?.gdprApplies === true) { + logWarn(`${MODULE_NAME}: Submodule cannot be used where GDPR applies`); + + return; + } + + const { pid, storeFpid, apiUrl = API_URL } = params; return { callback(cb) { ajaxBuilder(AJAX_TIMEOUT)(apiUrl, { success(response) { - let envelope; + let responseObj = { }; try { - envelope = getEnvelope(JSON.parse(response)) + responseObj = calculateResponseObj(JSON.parse(response)); } catch (err) { logError(`${MODULE_NAME}: ID reading error:`, err); } - cb(envelope); + + if (!responseObj.envelope) { + deleteFromStorage(MODULE_NAME); + } + + if (storeFpid) { + handleFpId(responseObj.fp, storageConfig); + } + + cb(responseObj.envelope); }, error(err) { logError(`${MODULE_NAME}: ID error response`, err); cb(); } - }, calculateQueryStringParams(pid, gdprConsentData), { method: 'GET', withCredentials: true }); + }, calculateQueryStringParams(pid, gdprConsentData, storageConfig), { method: 'GET', withCredentials: true }); } }; }, diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index 1e4af89344f..930d0c8c824 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -51,3 +51,4 @@ The following settings are available in the `params` property in `userSync.userI | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | +| storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability | `false` (default) or `true` | diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 4f6d7c4a6c5..a54c4590f3f 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -1,4 +1,4 @@ -import { thirthyThreeAcrossIdSubmodule } from 'modules/33acrossIdSystem.js'; +import { thirthyThreeAcrossIdSubmodule, storage } from 'modules/33acrossIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -50,60 +50,300 @@ describe('33acrossIdSystem', () => { expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; }); - context('when GDPR applies', () => { - it('should call endpoint with \'gdpr=1\'', () => { + context('if the use of a first-party ID has been enabled', () => { + context('and the response includes a first-party ID', () => { + context('and the storage type is "cookie"', () => { + it('should store the provided first-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: true + }, + storage: { + type: 'cookie', + expires: 90 + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.true; + + setCookie.restore(); + cookiesAreEnabled.restore(); + }); + }); + + context('and the storage type is "html5"', () => { + it('should store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: true + }, + storage: { + type: 'html5' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + }); + + context('and the response lacks a first-party ID', () => { + it('should wipe any existing first-party ID from storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: true + }, + storage: { + type: 'html5' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo' // no 'fp' field + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledOnceWithExactly('33acrossIdFp')).to.be.true; + expect(setCookie.calledOnceWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax')).to.be.true; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + cookiesAreEnabled.restore(); + }); + }); + }); + + context('if the use of a first-party ID has been disabled (default value)', () => { + context('and the response includes a first-party ID', () => { + it('should not store the provided first-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + // no storeFpid param + }, + storage: { + type: 'cookie', + expires: 90 + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.false; + + setCookie.restore(); + cookiesAreEnabled.restore(); + }); + + it('should not store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + // no storeFpid param + }, + storage: { + type: 'html5' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.false; + + setDataInLocalStorage.restore(); + }); + }); + }); + + context('if the response lacks the 33across "envelope" ID', () => { + it('should wipe any existing "envelope" ID from storage', () => { const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' + }, + storage: { + type: 'html5' } - }, { - gdprApplies: true }); callback(completeCallback); const [request] = server.requests; - expect(request.url).to.contain('gdpr=1'); + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: '' // no 'envelope' field + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWith('33acrossId')).to.be.true; + expect(setCookie.calledWith('33acrossId', '', sinon.match.string, 'Lax')).to.be.true; + + removeDataFromLocalStorage.restore(); + setCookie.restore(); + cookiesAreEnabled.restore(); }); }); - context('when GDPR doesn\'t apply', () => { - it('should call endpoint with \'gdpr=0\'', () => { - const completeCallback = () => {}; - const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + context('when GDPR applies', () => { + it('should log a warning and don\'t expect a call to the endpoint', () => { + const logWarnSpy = sinon.spy(utils, 'logWarn'); + + const result = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } }, { - gdprApplies: false + gdprApplies: true }); - callback(completeCallback); - - const [request] = server.requests; + expect(logWarnSpy.calledOnceWithExactly('33acrossId: Submodule cannot be used where GDPR applies')).to.be.true; + expect(result).to.be.undefined; - expect(request.url).to.contain('gdpr=0'); + logWarnSpy.restore(); }); }); - context('when the GDPR consent string is given', () => { - it('should call endpoint with the GDPR consent string', () => { + context('when GDPR doesn\'t apply', () => { + it('should call endpoint with \'gdpr=0\'', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345' } }, { - consentString: 'foo' + gdprApplies: false }); callback(completeCallback); const [request] = server.requests; - expect(request.url).to.contain('gdpr_consent=foo'); + expect(request.url).to.contain('gdpr=0'); + }); + + context('but the GDPR consent string is given', () => { + it('should call endpoint with the GDPR consent string', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }, { + gdprApplies: false, + consentString: 'foo' + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('gdpr_consent=foo'); + }); }); }); @@ -252,6 +492,75 @@ describe('33acrossIdSystem', () => { }); }); + context('when a first-party ID is present in local storage', () => { + it('should call endpoint with the first-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + storage: { + type: 'html5' + } + }); + + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdFp') + .returns('33acrossIdFpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('fp=33acrossIdFpValue'); + + storage.getDataFromLocalStorage.restore(); + }); + }); + + context('when a first-party ID is present in cookie storage', () => { + it('should call endpoint with the first-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + storage: { + type: 'cookie' + } + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdFp') + .returns('33acrossIdFpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('fp=33acrossIdFpValue'); + + storage.getCookie.restore(); + }); + }); + + context('when a first-party ID is not present in storage', () => { + it('should not call endpoint with the first-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).not.to.contain('fp='); + }); + }); + context('when the partner ID is not given', () => { it('should log an error', () => { const logErrorSpy = sinon.spy(utils, 'logError'); From 11bbd2917d98c7979b401699c63a81ab3e82752d Mon Sep 17 00:00:00 2001 From: xiaochang <106997162@qq.com> Date: Fri, 23 Feb 2024 03:15:52 +0800 Subject: [PATCH 479/760] New bidder adapter : RixEngine (#11035) * RixEngine Bid Adapter: Add RixEngine bid adapter * update rixengineBidAdapter_spec.js * remove the user ID opt in and provide a test endpoint --------- Co-authored-by: Yuanchang --- modules/rixengineBidAdapter.js | 67 +++++++++ modules/rixengineBidAdapter.md | 32 ++++ test/spec/modules/rixengineBidAdapter_spec.js | 141 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 modules/rixengineBidAdapter.js create mode 100644 modules/rixengineBidAdapter.md create mode 100644 test/spec/modules/rixengineBidAdapter_spec.js diff --git a/modules/rixengineBidAdapter.js b/modules/rixengineBidAdapter.js new file mode 100644 index 00000000000..8ffdb55f09b --- /dev/null +++ b/modules/rixengineBidAdapter.js @@ -0,0 +1,67 @@ +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER_CODE = 'rixengine'; + +let ENDPOINT = null; +let SID = null; +let TOKEN = null; + +const DEFAULT_BID_TTL = 30; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; + +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY, + mediaType: BANNER, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + return imp; + }, +}); +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + if ( + Boolean(bid.params.endpoint) && + Boolean(bid.params.sid) && + Boolean(bid.params.token) + ) { + SID = bid.params.sid; + TOKEN = bid.params.token; + ENDPOINT = bid.params.endpoint + '?sid=' + SID + '&token=' + TOKEN; + return true; + } + return false; + }, + + buildRequests(bidRequests, bidderRequest) { + let data = converter.toORTB({ bidRequests, bidderRequest }); + + return [ + { + method: 'POST', + url: ENDPOINT, + data, + options: { contentType: 'application/json;charset=utf-8' }, + }, + ]; + }, + + interpretResponse(response, request) { + const bids = converter.fromORTB({ + response: response.body, + request: request.data, + }).bids; + return bids; + }, +}; + +registerBidder(spec); diff --git a/modules/rixengineBidAdapter.md b/modules/rixengineBidAdapter.md new file mode 100644 index 00000000000..c05648f4b85 --- /dev/null +++ b/modules/rixengineBidAdapter.md @@ -0,0 +1,32 @@ +# Overview + +``` +Module Name: RixEngine Bid Adapter +Module Type: Bidder Adapter +Maintainer: yuanchang@algorix.co +``` + +# Description + +Connects to RixEngine exchange for bids. + +RixEngine bid adapter supports Banner currently. + +# Sample Banner Ad Unit: For Publishers +``` +var adUnits = [ +{ + sizes: [ + [320, 50] + ], + bids: [{ + bidder: 'rixengine', + params: { + endpoint: 'http://demo.svr.rixengine.com/rtb', // required + token: '1e05a767930d7d96ef6ce16318b4ab99', // required + sid: 36540, // required + } + }] +}]; +``` + diff --git a/test/spec/modules/rixengineBidAdapter_spec.js b/test/spec/modules/rixengineBidAdapter_spec.js new file mode 100644 index 00000000000..a400b5c755b --- /dev/null +++ b/test/spec/modules/rixengineBidAdapter_spec.js @@ -0,0 +1,141 @@ +import { spec } from 'modules/rixengineBidAdapter.js'; +const ENDPOINT = 'http://demo.svr.rixengine.com/rtb?sid=36540&token=1e05a767930d7d96ef6ce16318b4ab99'; + +const REQUEST = [ + { + adUnitCode: 'adUnitCode1', + bidId: 'bidId1', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', + mediaTypes: { + banner: {}, + }, + bidder: 'rixengine', + params: { + endpoint: 'http://demo.svr.rixengine.com/rtb', + token: '1e05a767930d7d96ef6ce16318b4ab99', + sid: '36540', + }, + }, +]; + +const RESPONSE = { + headers: null, + body: { + id: 'requestId', + bidid: 'bidId1', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: 'bidId1', + impid: 'bidId1', + adm: '', + cid: '24:17:18', + crid: '40_37_66:30_32_132:31_27_70', + adomain: ['www.rixengine.com'], + price: 10.00, + bundle: + 'com.xinggame.cast.video.screenmirroring.casttotv:https://www.greysa.com.tw/Product/detail/pid/119/?utm_source=popIn&utm_medium=cpc&utm_campaign=neck_202307_300*250:https://www.avaige.top/', + iurl: 'https://crs.rixbeedesk.com/test/kkd2ms/04c6d62912cff9037106fb50ed21b558.png:https://crs.rixbeedesk.com/test/kkd2ms/69a72b23c6c52e703c0c8e3f634e44eb.png:https://crs.rixbeedesk.com/test/kkd2ms/d229c5cd66bcc5856cb26bb2817718c9.png', + w: 300, + h: 250, + exp: 30, + }, + ], + seat: 'Zh2Kiyk=', + }, + ], + }, +}; + +describe('rixengine bid adapter', function () { + describe('isBidRequestValid', function () { + let bid = { + bidder: 'rixengine', + params: { + endpoint: 'http://demo.svr.rixengine.com/rtb', + token: '1e05a767930d7d96ef6ce16318b4ab99', + sid: '36540', + }, + }; + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when missing endpoint', function () { + delete bid.params.endpoint; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing sid', function () { + delete bid.params.sid; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + it('should return false when missing token', function () { + delete bid.params.token; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + describe('buildRequests', function () { + it('creates request data', function () { + const request = spec.buildRequests(REQUEST, { + refererInfo: { + page: 'page', + }, + })[0]; + expect(request).to.exist.and.to.be.a('object'); + }); + it('sends bid request to ENDPOINT via POST', function () { + const request = spec.buildRequests(REQUEST, {})[0]; + expect(request.url).to.equal(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponse', function () { + it('has bids', function () { + let request = spec.buildRequests(REQUEST, {})[0]; + let bids = spec.interpretResponse(RESPONSE, request); + expect(bids).to.be.an('array').that.is.not.empty; + validateBidOnIndex(0); + + function validateBidOnIndex(index) { + expect(bids[index]).to.have.property('currency', 'USD'); + expect(bids[index]).to.have.property( + 'requestId', + RESPONSE.body.seatbid[0].bid[index].id + ); + expect(bids[index]).to.have.property( + 'cpm', + RESPONSE.body.seatbid[0].bid[index].price + ); + expect(bids[index]).to.have.property( + 'width', + RESPONSE.body.seatbid[0].bid[index].w + ); + expect(bids[index]).to.have.property( + 'height', + RESPONSE.body.seatbid[0].bid[index].h + ); + expect(bids[index]).to.have.property( + 'ad', + RESPONSE.body.seatbid[0].bid[index].adm + ); + expect(bids[index]).to.have.property( + 'creativeId', + RESPONSE.body.seatbid[0].bid[index].crid + ); + expect(bids[index]).to.have.property('ttl', 30); + expect(bids[index]).to.have.property('netRevenue', true); + } + }); + + it('handles empty response', function () { + it('No bid response', function() { + var noBidResponse = spec.interpretResponse({ + body: '', + }); + expect(noBidResponse.length).to.equal(0); + }); + }); + }); +}); From b2ace9f59d90c563209d7597b83499c68312d886 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Thu, 22 Feb 2024 13:17:57 -0600 Subject: [PATCH 480/760] change expire recommendation from 90 to 30 (#11130) Co-authored-by: Anthony Lin --- modules/33acrossIdSystem.md | 4 ++-- test/spec/modules/33acrossIdSystem_spec.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index 930d0c8c824..8b73a43069d 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -15,7 +15,7 @@ pbjs.setConfig({ storage: { name: "33acrossId", type: "html5", - expires: 90, + expires: 30, refreshInSeconds: 8*3600 }, params: { @@ -41,7 +41,7 @@ The following settings are available for the `storage` property in the `userSync | --- | --- | --- | --- | --- | | name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | | type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | -| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `90`. | `90` | +| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `30`. | `30` | | refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | ### Params diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index a54c4590f3f..cbc5b277e30 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -63,7 +63,7 @@ describe('33acrossIdSystem', () => { }, storage: { type: 'cookie', - expires: 90 + expires: 30 } }); @@ -184,7 +184,7 @@ describe('33acrossIdSystem', () => { }, storage: { type: 'cookie', - expires: 90 + expires: 30 } }); From 6a8f2d56fefb999d269d83e9045efc012967c58c Mon Sep 17 00:00:00 2001 From: ryohamadaumt <105703275+ryohamadaumt@users.noreply.github.com> Date: Fri, 23 Feb 2024 04:34:46 +0900 Subject: [PATCH 481/760] add required version (#11127) --- modules/adstirBidAdapter.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/adstirBidAdapter.md b/modules/adstirBidAdapter.md index 7485375a09d..5840697a9b0 100644 --- a/modules/adstirBidAdapter.md +++ b/modules/adstirBidAdapter.md @@ -10,6 +10,8 @@ Maintainer: support@ad-stir.com Module that connects to adstir's demand sources +Prebid.js version 8.24.0 or above is required to use this adapter. + # Test Parameters ``` From 9485c6b0a61cbff5e8ca6f3289d2d0c9fe13a6b8 Mon Sep 17 00:00:00 2001 From: bretg Date: Thu, 22 Feb 2024 14:39:44 -0500 Subject: [PATCH 482/760] cleanup references to allowAuctionWithoutConsent (#11129) --- integrationExamples/gpt/gdpr_hello_world.html | 3 +-- modules/consentManagement.js | 2 +- modules/consentManagementGpp.js | 2 +- modules/consentManagementUsp.js | 2 +- test/pages/consent_mgt_gdpr.html | 1 - test/spec/modules/gdprEnforcement_spec.js | 2 -- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/integrationExamples/gpt/gdpr_hello_world.html b/integrationExamples/gpt/gdpr_hello_world.html index c62569cfc4f..e23a866d4fd 100644 --- a/integrationExamples/gpt/gdpr_hello_world.html +++ b/integrationExamples/gpt/gdpr_hello_world.html @@ -54,8 +54,7 @@ pbjs.setConfig({ consentManagement: { cmpApi: 'iab', - timeout: 5000, - allowAuctionWithoutConsent: true + timeout: 5000 }, pubcid: { enable: false diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 05447a890cb..346b241fc1f 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -250,7 +250,7 @@ export function resetConsentData() { /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function - * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + * @param {{cmp:string, timeout:number, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int)) */ export function setConsentConfig(config) { // if `config.gdpr`, `config.usp` or `config.gpp` exist, assume new config format. diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index f696ce25902..416430fb1c9 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -455,7 +455,7 @@ export function resetConsentData() { /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function - * @param {{cmp:string, timeout:number, allowAuctionWithoutConsent:boolean, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) + * @param {{cmp:string, timeout:number, defaultGdprScope:boolean}} config required; consentManagement module config settings; cmp (string), timeout (int)) */ export function setConsentConfig(config) { config = config && config.gpp; diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 1218c3724f4..78ec13cb891 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -202,7 +202,7 @@ export function resetConsentData() { /** * A configuration function that initializes some module variables, as well as add a hook into the requestBids function - * @param {object} config required; consentManagementUSP module config settings; usp (string), timeout (int), allowAuctionWithoutConsent (boolean) + * @param {object} config required; consentManagementUSP module config settings; usp (string), timeout (int) */ export function setConsentConfig(config) { config = config && config.usp; diff --git a/test/pages/consent_mgt_gdpr.html b/test/pages/consent_mgt_gdpr.html index b22d1e958e0..c55a2b9236f 100644 --- a/test/pages/consent_mgt_gdpr.html +++ b/test/pages/consent_mgt_gdpr.html @@ -150,7 +150,6 @@ consentManagement: { gdpr: { cmpApi: 'static', - allowAuctionWithoutConsent: true, consentData: { getConsentData: { 'gdprApplies': true, diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/gdprEnforcement_spec.js index 295b14dd796..4caf0276874 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/gdprEnforcement_spec.js @@ -37,7 +37,6 @@ describe('gdpr enforcement', function () { let staticConfig = { cmpApi: 'static', timeout: 7500, - allowAuctionWithoutConsent: false, consentData: { getTCData: { 'tcString': 'COuqj-POu90rDBcBkBENAZCgAPzAAAPAACiQFwwBAABAA1ADEAbQC4YAYAAgAxAG0A', @@ -894,7 +893,6 @@ describe('gdpr enforcement', function () { setEnforcementConfig({ gdpr: { cmpApi: 'iab', - allowAuctionWithoutConsent: true, timeout: 5000 } }); From e3f03bee3a2b635a681f90e993ef9d25a8331a77 Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Fri, 23 Feb 2024 01:21:06 +0530 Subject: [PATCH 483/760] fix video object null in validate request (#11128) --- modules/insticatorBidAdapter.js | 19 ++++++++++--------- .../spec/modules/insticatorBidAdapter_spec.js | 13 +++++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 3ea95ef8443..4d9b95e5948 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -128,6 +128,10 @@ function buildVideo(bidRequest) { } } + if (plcmt) { + optionalParams['plcmt'] = plcmt; + } + let videoObj = { placement, mimes, @@ -137,9 +141,6 @@ function buildVideo(bidRequest) { ...videoBidderParams // bidder specific overrides for video } - if (plcmt) { - videoObj['plcmt'] = plcmt; - } return videoObj } @@ -386,16 +387,16 @@ function validateBanner(bid) { } function validateVideo(bid) { - const videoParams = deepAccess(bid, 'mediaTypes.video', {}); - const videoBidderParams = deepAccess(bid, 'params.video', {}); + const videoParams = deepAccess(bid, 'mediaTypes.video'); + const videoBidderParams = deepAccess(bid, 'params.video'); let video = { ...videoParams, ...videoBidderParams // bidder specific overrides for video } - // Check if the merged video object is empty - if (Object.keys(video).length === 0 || video === undefined) { - logError('insticator: video object is empty'); - return false; // or handle the case where video parameters are undefined or empty + + // Check if the video object is undefined + if (videoParams === undefined) { + return true; } let w = deepAccess(bid, 'mediaTypes.video.w'); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 149983552f8..86f96834547 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -161,6 +161,19 @@ describe('InsticatorBidAdapter', function () { })).to.be.false; }); + it('should return true if video object is absent/undefined', () => { + expect(spec.isBidRequestValid({ + ...bidRequest, + ...{ + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]], + }, + } + } + })).to.be.true; + }) + it('should return false if video placement is not a number', () => { expect(spec.isBidRequestValid({ ...bidRequest, From 2c7ae8a0ab158db662fef91cb0e8f7619a616a05 Mon Sep 17 00:00:00 2001 From: Jhon Barreiro <90330032+jhon-reset@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:59:26 -0500 Subject: [PATCH 484/760] Reset Digital Bid Adapter: updating users syncs (#11126) * Update resetdigitalBidAdapter.js * updating usersync URL * fix user syncs for test --- modules/resetdigitalBidAdapter.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index 2bac6c6dcba..7ace90744e8 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -178,17 +178,11 @@ export const spec = { return bidResponses; }, getUserSyncs: function (syncOptions, serverResponses, gdprConsent) { - const syncs = []; - + let syncs = []; if (!serverResponses.length || !serverResponses[0].body) { return syncs; } - let pixels = serverResponses[0].body.pixels; - if (!pixels || !pixels.length) { - return syncs; - } - let gdprParams = ''; if (gdprConsent) { if (typeof gdprConsent.gdprApplies === 'boolean') { @@ -200,14 +194,17 @@ export const spec = { } } - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - return [ - { - type: 'iframe', - url: 'https://media.reset-digital.com/prebid/async_usersync.html?' + gdprParams.length ? gdprParams : '', - }, - ]; + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + syncs.push({ + type: 'iframe', + url: + 'https://async.resetdigital.co/async_usersync.html?' + + gdprParams.length + ? gdprParams + : '', + }); } + return syncs; }, }; From de841dabf34da99f948d3edda16a1d624f382ce2 Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Thu, 22 Feb 2024 23:32:27 +0000 Subject: [PATCH 485/760] Alkimi Bid Adapter: add custom user object (#11093) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support * Support new OpenRTB parameters * Support new oRTB2 parameters * remove pos parameter * Add gvl_id into Alkimi adapter * Insert keywords into bid-request param * Resolve AUCTION_PRICE macro on prebid-server for VAST ads * Added support for full page auction * Added custom user object --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: mik --- modules/alkimiBidAdapter.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 53f6460434f..d4e7cab8ed1 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -43,7 +43,11 @@ export const spec = { bidIds.push(bidRequest.bidId) }) - const alkimiConfig = config.getConfig('alkimi'); + const alkimiConfig = config.getConfig('alkimi') + const fullPageAuction = bidderRequest.ortb2?.source?.ext?.full_page_auction + const source = fullPageAuction != undefined ? { ext: { full_page_auction: fullPageAuction } } : undefined + const walletID = alkimiConfig && alkimiConfig.walletID + const user = walletID != undefined ? { ext: { walletID: walletID } } : undefined let payload = { requestId: generateUUID(), @@ -59,6 +63,8 @@ export const spec = { h: screen.height }, ortb2: { + source, + user, site: { keywords: bidderRequest.ortb2?.site?.keywords }, From ce5c5aa1e5dba422affc9a9a66b17e1db4911843 Mon Sep 17 00:00:00 2001 From: mcollins-ttd <118872455+mcollins-ttd@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:04:51 +1100 Subject: [PATCH 486/760] Unified ID 2.0 Module: Update documentation (#11105) * Update UID2 User ID submodule documentation - Link to guides on unifiedid.com - Replace references to "CSTG" with "client-side integration" - Split up params based on integration type - Link to unifiedid.com for normalization and encoding * Address UID2 documentation feedback * Fix uid2_pub_cookie and storage example values * Address review feedback * Restore deleted context about normalizing and encoding * Use a code block for sample token * Fix example for value * Address review feedback --- modules/uid2IdSystem.md | 288 +++++++++------------------------------- 1 file changed, 63 insertions(+), 225 deletions(-) diff --git a/modules/uid2IdSystem.md b/modules/uid2IdSystem.md index e546f6eafe1..c3b38e36531 100644 --- a/modules/uid2IdSystem.md +++ b/modules/uid2IdSystem.md @@ -1,264 +1,102 @@ -## UID2 User ID Submodule +# UID2 User ID Submodule -The UID2 module handles storing, providing, and optionally refreshing tokens. While initial tokens traditionally required server-side generation, the introduction of the *Client-Side Token Generation (CSTG)* mode offers publishers the flexibility to generate UID2 tokens directly from the module, eliminating this need. Publishers can choose to operate the module in one of three distinct modes: *Client Refresh* mode, *Server Only* mode and *Client-Side Token Generation* mode. +## Overview -*Server Only* mode was originally referred to as *legacy mode*, but it is a popular mode for new integrations where publishers prefer to handle token refresh server-side. +UID2 provides a Prebid.js module that supports the following: -*Client-Side Token Generation* mode is included in UID2 module by default. However, it's important to note that this mode is created and made available recently. For publishers who do not intend to use it, you have the option to instruct the build to exclude the code related to this feature: +- [Generating the UID2 token](https://unifiedid.com/docs/guides/integration-prebid#generating-the-uid2-token) +- [Refreshing the UID2 token](https://unifiedid.com/docs/guides/integration-prebid#refreshing-the-uid2-token) +- [Storing the UID2 token in the browser](https://unifiedid.com/docs/guides/integration-prebid#storing-the-uid2-token-in-the-browser) +- [Passing the UID2 token to the bid stream](https://unifiedid.com/docs/guides/integration-prebid#passing-the-uid2-token-to-the-bid-stream) -``` - $ gulp build --modules=uid2IdSystem --disable UID2_CSTG -``` -If you do plan to use Client-Side Token Generation (CSTG) mode, please consult the UID2 Team first as they will provide required configuration values for you to use (see the Client-Side Token Generation (CSTG) mode section below for details) - -**Important information:** UID2 is not designed to be used where GDPR applies. The module checks the passed-in consent data and will not operate if the `gdprApplies` flag is true. - -## Client-Side Token Generation (CSTG) mode - -**This mode is created and made available recently. Please consult UID2 Team first as they will provide required configuration values for you to use.** - -For publishers seeking a purely client-side integration without the complexities of server-side involvement, the CSTG mode is highly recommended. This mode requires the provision of a public key, subscription ID and [directly identifying information (DII)](https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii) - either emails or phone numbers. In the CSTG mode, the module takes on the responsibility of encrypting the DII, generating the UID2 token, and handling token refreshes when necessary. - -To configure the module to use this mode, you must: -1. Set `parmas.serverPublicKey` and `params.subscriptionId` (please reach out to the UID2 team to obtain these values) -2. Provide **ONLY ONE DII** by setting **ONLY ONE** of `params.email`/`params.phone`/`params.emailHash`/`params.phoneHash` - -Below is a table that provides guidance on when to use each directly identifying information (DII) parameter, along with information on whether normalization and hashing are required by the publisher for each parameter. - -| DII param | When to use it | Normalization required by publisher? | Hashing required by publisher? | -|------------------|-------------------------------------------------------|--------------------------------------|--------------------------------| -| params.email | When you have users' email address | No | No | -| params.phone | When you have user's phone number | Yes | No | -| params.emailHash | When you have user's hashed, normalized email address | Yes | Yes | -| params.phoneHash | When you have user's hashed, normalized phone number | Yes | Yes | - - -*Note that setting params.email will normalize email addresses, but params.phone requires phone numbers to be normalized.* - -Refer to [Normalization and Encoding](#normalization-and-encoding) for details on email address normalization, SHA-256 hashing and Base64 encoding. - -### CSTG example - -Configuration: -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'uid2', - params: { - serverPublicKey: '...server public key...', - subscriptionId: '...subcription id...', - email: 'user@email.com', - //phone: '+0000000', - //emailHash: '...email hash...', - //phoneHash: '...phone hash ...' - } - }] - } -}); -``` - -## Client Refresh mode - -This is the recommended mode for most scenarios. In this mode, the full response body from the UID2 Token Generate or Token Refresh endpoint must be provided to the module. As long as the refresh token remains valid, the module will refresh the advertising token as needed. - -To configure the module to use this mode, you must **either**: -1. Set `params.uid2Cookie` to the name of the cookie which contains the response body as a JSON string, **or** -2. Set `params.uid2Token` to the response body as a JavaScript object. - -The `uid2Cookie` param was originally `uid2ServerCookie`. The old name can still be used, however the inclusion of the word 'server' was causing some confusion. If both values are provided, `uid2ServerCookie` will be ignored. - -### Client refresh cookie example - -In this example, the cookie is called `uid2_pub_cookie`. - -Cookie: -``` -uid2_pub_cookie={"advertising_token":"...advertising token...","refresh_token":"...refresh token...","identity_expires":1684741472161,"refresh_from":1684741425653,"refresh_expires":1684784643668,"refresh_response_key":"...response key..."} -``` - -Configuration: -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'uid2', - params: { - uid2Cookie: 'uid2_pub_cookie' - } - }] - } -}); -``` - -### Client refresh uid2Token example +For details, see [UID2 Integration Overview for Prebid.js](https://unifiedid.com/docs/guides/integration-prebid). -Configuration: -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'uid2', - params: { - uid2Token: { - 'advertising_token': '...advertising token...', - 'refresh_token': '...refresh token...', - // etc. - see the Sample Token below for contents of this object - } - } - }] - } -}); -``` +**Important information:** UID2 is not designed to be used where GDPR applies. The module checks the passed-in consent data and does not operate if the `gdprApplies` flag is true. -## Server-Only Mode +Depending on access to [directly identifying information](https://unifiedid.com/docs/ref-info/glossary-uid#d) (DII), there are two methods to generate UID2 tokens for use with Prebid.js, as shown in the following table. -In this mode, only the advertising token is provided to the module. The module will not be able to refresh the token. The publisher is responsible for implementing some other way to refresh the token. +Determine which method is best for you, and then follow the applicable integration guide. -To configure the module to use this mode, you must **either**: -1. Set a cookie named `__uid2_advertising_token` to the advertising token, **or** -2. Set `value` to an ID block containing the advertising token (see "Server only value" example below). - -### Server only cookie example - -Cookie: -``` -__uid2_advertising_token=...advertising token... -``` - -Configuration: -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'uid2' - }] - } -}); -``` - -### Server only value example - -Configuration: -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'uid2' - value: { - 'uid2': { - 'id': '...advertising token...' - } - } - }] - } -}); -``` +| Scenario | Integration Guide | +| :--- | :--- | +| You have access to DII on the client side and want to do front-end development only. | [UID2 Client-Side Integration Guide for Prebid.js](https://unifiedid.com/docs/guides/integration-prebid-client-side). | +| You have access to DII on the server side and can do server-side development. | [UID2 Server-Side Integration Guide for Prebid.js](https://unifiedid.com/docs/guides/integration-prebid-server-side). | ## Storage The module stores a number of internal values. By default, all values are stored in HTML5 local storage. You can switch to cookie storage by setting `params.storage` to `cookie`. The cookie size can be significant and this is not recommended, but is provided as an option if local storage is not an option. -## Sample token - -`{`
  `"advertising_token": "...",`
  `"refresh_token": "...",`
  `"identity_expires": 1633643601000,`
  `"refresh_from": 1633643001000,`
  `"refresh_expires": 1636322000000,`
  `"refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw=="`
`}` - -### Notes - -If you are trying to limit the size of cookies, provide the token in configuration and use the default option of local storage. - -If you provide an expired identity and the module has a valid identity which was refreshed from the identity you provide, it will use the refreshed identity. The module stores the original token used for refreshing the token, and it will use the refreshed tokens as long as the original token matches the one supplied. - -If a new token is supplied which does not match the original token used to generate any refreshed tokens, all stored tokens will be discarded and the new token used instead (refreshed if necessary). - -You can set `params.uid2ApiBase` to `"https://operator-integ.uidapi.com"` during integration testing. Be aware that you must use the same environment (production or integration) here as you use for generating tokens. - ## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the UID2 User ID Module integration. +The following parameters apply only to the UID2 User ID Module integration. | Param under userSync.userIds[] | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | -| name | Required | String | ID value for the UID2 module - `"uid2"` | `"uid2"` | -| value | Optional, Server only | Object | An object containing the value for the advertising token. | See the example above. | -| params.uid2Token | Optional, Client refresh | Object | The initial UID2 token. This should be `body` element of the decrypted response from a call to the `/token/generate` or `/token/refresh` endpoint. | See the sample token above. | -| params.uid2Cookie | Optional, Client refresh | String | The name of a cookie which holds the initial UID2 token, set by the server. The cookie should contain JSON in the same format as the uid2Token param. **If uid2Token is supplied, this param is ignored.** | See the sample token above. | -| params.uid2ApiBase | Optional, Client refresh | String | Overrides the default UID2 API endpoint. | `"https://prod.uidapi.com"` _(default)_| -| params.storage | Optional, Client refresh | String | Specify whether to use `cookie` or `localStorage` for module-internal storage. It is recommended to not provide this and allow the module to use the default. | `localStorage` _(default)_ | -| params.serverPublicKey | Optional, Client-side token generation | String | A public key for encrypting the DII payload for the Operator's CSTG endpoint. **This is required for client-side token generation.** | - | -| params.subscriptionId | Optional, Client-side token generation | String | A publisher Identifier. **This is required for client-side token generation.** | - | -| params.email | Optional, Client-side token generation | String | The user's email address. Provide this parameter if using email as the DII. | `"test@example.com"` | -| params.emailHash | Optional, Client-side token generation | String | A hashed, normalized representation of the user's email. Provide this parameter if using emailHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | -| params.phone | Optional, Client-side token generation | String | The user's phone number. Provide this parameter if using phone as the DII. | `"+15555555555"` | -| params.phoneHash | Optional, Client-side token generation | String | A hashed, normalized representation of the user's phone number. Provide this parameter if using phoneHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | - -# Normalization and Encoding - -This section provides information about normalizing and encoding [directly Identifying information (DII)](https://unifiedid.com/docs/ref-info/glossary-uid#gl-dii). It's important that, in working with UID2, normalizing and encoding are performed correctly. +| name | Required | String | ID value for the UID2 module. Must be `"uid2"`. | `"uid2"` | +| params.uid2ApiBase | Optional | String | Overrides the default UID2 API endpoint. | `"https://prod.uidapi.com"` _(default)_| +| params.storage | Optional | String | Specify whether to use `cookie` or `localStorage` for module-internal storage. It is recommended to not provide this and allow the module to use the default. | `"localStorage"` _(default)_ | -## Introduction -When you're taking user information such as an email address, and following the steps to create a raw UID2 and/or a UID2 advertising token, it's very important that you follow all the required steps. Whether you normalize the information or not, whether you hash it or not, follow the steps exactly. By doing so, you can ensure that the UID2 value you create can be securely and anonymously matched up with other instances of online behavior by the same user. +### Client-Side Integration ->Note: Raw UID2s, and their associated UID2 tokens, are case sensitive. When working with UID2, it's important to pass all IDs and tokens without changing the case. Mismatched IDs can cause ID parsing or token decryption errors. +The following parameters apply to the UID2 User ID Module if you are following the [client-side integration guide](https://unifiedid.com/docs/guides/integration-prebid-client-side). Exactly one of `params.email`, `params.emailHash`, `params.phone`, and `params.phoneHash` must be provided. For information on how to normalize and hash these parameters, refer to [Normalization and Encoding](https://unifiedid.com/docs/getting-started/gs-normalization-encoding). -## Types of Directly Identifying Information -UID2 supports the following types of directly identifying information (DII): -- Email address -- Phone number - -## Email Address Normalization +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| params.serverPublicKey | Required for client-side integration | String | See [Subscription ID and Public Key](https://unifiedid.com/docs/getting-started/gs-credentials#subscription-id-and-public-key). | - | +| params.subscriptionId | Required for client-side integration | String | See [Subscription ID and Public Key](https://unifiedid.com/docs/getting-started/gs-credentials#subscription-id-and-public-key). | - | +| params.email | Optional | String | The user's email address. Provide this parameter if using email as the DII. | `"test@example.com"` | +| params.emailHash | Optional | String | A [hashed, normalized](https://unifiedid.com/docs/getting-started/gs-normalization-encoding) representation of the user's email. Provide this parameter if using emailHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | +| params.phone | Optional | String | A [normalized](https://unifiedid.com/docs/getting-started/gs-normalization-encoding) representation of the user's phone number. Provide this parameter if using phone as the DII. | `"+15555555555"` | +| params.phoneHash | Optional | String | A [hashed, normalized](https://unifiedid.com/docs/getting-started/gs-normalization-encoding) representation of the user's phone number. Provide this parameter if using phoneHash as the DII. | `"tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ="` | -If you send unhashed email addresses to the UID2 Operator Service, the service normalizes the email addresses and then hashes them. If you want to hash the email addresses yourself before sending them, you must normalize them before you hash them. +### Server-Side Integration -> IMPORTANT: Normalizing before hashing ensures that the generated UID2 value will always be the same, so that the data can be matched. If you do not normalize before hashing, this might result in a different UID2, reducing the effectiveness of targeted advertising. +#### Server-Only Mode -To normalize an email address, complete the following steps: +The following parameters apply to the UID2 User ID Module if you are following the [server-side integration guide](https://unifiedid.com/docs/guides/integration-prebid-server-side) with [server-only mode](https://unifiedid.com/docs/guides/integration-prebid-server-side#server-only-mode). -1. Remove leading and trailing spaces. -2. Convert all ASCII characters to lowercase. -3. In `gmail.com` email addresses, remove the following characters from the username part of the email address: - 1. The period (`.` (ASCII code 46)).
For example, normalize `jane.doe@gmail.com` to `janedoe@gmail.com`. - 2. The plus sign (`+` (ASCII code 43)) and all subsequent characters.
For example, normalize `janedoe+home@gmail.com` to `janedoe@gmail.com`. +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| value | Required for server-only mode | Object | An object containing the value for the advertising token. |
{
uid2: {
id: '...advertising token...'
}
}
| -## Email Address Hash Encoding +#### Client Refresh Mode -An email hash is a Base64-encoded SHA-256 hash of a normalized email address. The email address is first normalized, then hashed using the SHA-256 hashing algorithm, and then the resulting bytes of the hash value are encoded using Base64 encoding. Note that the bytes of the hash value are encoded, not the hex-encoded string representation. +The following parameters apply to the UID2 User ID Module if you are following the [server-side integration guide](https://unifiedid.com/docs/guides/integration-prebid-server-side) with [client refresh mode](https://unifiedid.com/docs/guides/integration-prebid-server-side#client-refresh-mode). Either `params.uid2Token` or `params.uid2Cookie` must be provided. -| Type | Example | Comments and Usage | -| :--- | :--- | :--- | -| Normalized email address | `user@example.com` | Normalization is always the first step. | -| SHA-256 hash of normalized email address | `b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514` | This 64-character string is a hex-encoded representation of the 32-byte SHA-256.| -| Hex to Base64 SHA-256 encoding of normalized email address | `tMmiiTI7IaAcPpQPFQ65uMVCWH8av9jw4cwf/F5HVRQ=` | This 44-character string is a Base64-encoded representation of the 32-byte SHA-256.
WARNING: The SHA-256 hash string in the example above is a hex-encoded representation of the hash value. You must Base64-encode the raw bytes of the hash or use a Base64 encoder that takes a hex-encoded value as input.
Use this encoding for `email_hash` values sent in the request body. | +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| params.uid2Token | Optional | Object | The initial UID2 token. This should be the `body` element of the decrypted response from a call to the `/token/generate` or `/token/refresh` endpoint. | See [Sample Token](#sample-token). | +| params.uid2Cookie | Optional | String | The name of a cookie that holds the initial UID2 token, set by the server. The cookie should contain JSON in the same format as the uid2Token param. **If uid2Token is supplied, this param is ignored.** | `"uid2_pub_cookie"` | ->WARNING: When applying Base64 encoding, be sure to Base64-encode the raw bytes of the hash or use a Base64 encoder that takes a hex-encoded value as input. +## Sample Token -## Phone Number Normalization +``` +{ + "advertising_token": "...", + "refresh_token": "...", + "identity_expires": 1633643601000, + "refresh_from": 1633643001000, + "refresh_expires": 1636322000000, + "refresh_response_key": "wR5t6HKMfJ2r4J7fEGX9Gw==" +} +``` -If you send unhashed phone numbers to the UID2 Operator Service, the service normalizes the phone numbers and then hashes them. If you want to hash the phone numbers yourself before sending them, you must normalize them before you hash them. +## Normalization and Encoding -> IMPORTANT: Normalization before hashing ensures that the generated UID2 value will always be the same, so that the data can be matched. If you do not normalize before hashing, this might result in a different UID2, reducing the effectiveness of targeted advertising. +It's important that, in working with UID2, normalizing and encoding are performed correctly. By doing so, you can ensure that the UID2 value you create can be securely and anonymously matched up with other instances of online behavior by the same user. -Here's what you need to know about phone number normalization rules: +For more information, refer to [Normalization and Encoding](https://unifiedid.com/docs/getting-started/gs-normalization-encoding). -- The UID2 Operator accepts phone numbers in the [E.164](https://en.wikipedia.org/wiki/E.164) format, which is the international phone number format that ensures global uniqueness. -- E.164 phone numbers can have a maximum of 15 digits. -- Normalized E.164 phone numbers use the following syntax, with no spaces, hyphens, parentheses, or other special characters:
- `[+] [country code] [subscriber number including area code]` - Examples: - - US: `1 (123) 456-7890` is normalized to `+11234567890`. - - Singapore: `65 1243 5678` is normalized to `+6512345678`. - - Sydney, Australia: `(02) 1234 5678` is normalized to drop the leading zero for the city plus include the country code: `+61212345678`. +## Notes -## Phone Number Hash Encoding +- If you provide an expired identity, and the module has a valid update from refreshing the same identity, the module uses the refreshed identity in place of the expired one you provided. -A phone number hash is a Base64-encoded SHA-256 hash of a normalized phone number. The phone number is first normalized, then hashed using the SHA-256 hashing algorithm, and the resulting hex value is encoded using Base64 encoding. +- If you provide a new token that doesn't match the original token used to generate any refreshed tokens, the module discards all stored tokens and uses the new token instead, and keeps it refreshed. -The example below shows a simple input phone number, and the result as each step is applied to arrive at a secure, opaque, URL-safe value. +- During integration testing, set `params.uid2ApiBase` to `"https://operator-integ.uidapi.com"`. You must set this value to the same environment (production or integration) that you use for generating tokens. -| Type | Example | Comments and Usage | -| :--- | :--- | :--- | -| Normalized phone number | `+12345678901` | Normalization is always the first step. | -| SHA-256 hash of normalized phone number | `10e6f0b47054a83359477dcb35231db6de5c69fb1816e1a6b98e192de9e5b9ee` |This 64-character string is a hex-encoded representation of the 32-byte SHA-256. | -| Hex to Base64 SHA-256 encoding of normalized and hashed phone number | `EObwtHBUqDNZR33LNSMdtt5cafsYFuGmuY4ZLenlue4=` | This 44-character string is a Base64-encoded representation of the 32-byte SHA-256.
NOTE: The SHA-256 hash is a hexadecimal value. You must use a Base64 encoder that takes a hex value as input. Use this encoding for `phone_hash` values sent in the request body. | +- If you are building Prebid.js and following the server-side integration guide, you can create a smaller Prebid.js build by disabling client-side integration functionality. To do this, pass the `--disable UID2_CSTG` flag: ->WARNING: When applying Base64 encoding, be sure to use a function that takes a hex value as input. If you use a function that takes text as input, the result is a longer string which is invalid for the purposes of UID2. +``` + $ gulp build --modules=uid2IdSystem --disable UID2_CSTG +``` \ No newline at end of file From 0906043f0b981a4b54770c976430518a868a0821 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 23 Feb 2024 15:28:37 +0000 Subject: [PATCH 487/760] Prebid 8.38.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4965e554935..df073508cf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.38.0-pre", + "version": "8.38.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 3e20acbeab9..a61ddc8c54c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.38.0-pre", + "version": "8.38.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8c37489669c15f3a2ff3e946cbdc404e1eaf92ea Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 23 Feb 2024 15:28:38 +0000 Subject: [PATCH 488/760] Increment version to 8.39.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index df073508cf3..2aa39fa8b3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.38.0", + "version": "8.39.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a61ddc8c54c..e576149def1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.38.0", + "version": "8.39.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f8691189c8aab43f620251548a11e5531e29d578 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Fri, 23 Feb 2024 17:33:48 +0100 Subject: [PATCH 489/760] GreenbidsAnalyticsAdapter: bump version following previous PR (#11135) --- modules/greenbidsAnalyticsAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index f60dee99efe..b881e868bf3 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -6,7 +6,7 @@ import {deepClone, generateUUID, logError, logInfo, logWarn} from '../src/utils. const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.1.0'; +export const ANALYTICS_VERSION = '2.2.0'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; From ecc3441914e14d9d4543f5c4cc7b531ad2c14d7e Mon Sep 17 00:00:00 2001 From: Andrew Slagle <42588549+spotxslagle@users.noreply.github.com> Date: Fri, 23 Feb 2024 20:49:57 -0700 Subject: [PATCH 490/760] Rubicon Bid Adapter: Pass on carbon segtaxes (#10985) * Pass through Carbon segments * Fix rubiconBidAdapter for unit tests * segtax spec * Fix access issues * Remove dup ortb2 work * Adjust unit tests * Fix lint issues * Add all desired segtaxes * Fix unit tests * Fix linting * Don't concat undefined * Unit test pub added segtaxes * Pull site data from site.content.data * Update unit tests --- modules/rubiconBidAdapter.js | 23 ++++++++++ test/spec/modules/rubiconBidAdapter_spec.js | 50 +++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 973c6660ece..c4f6e7d545d 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -532,6 +532,8 @@ export const spec = { if (bidRequest?.ortb2Imp?.ext?.ae) { data['o_ae'] = 1; } + + addDesiredSegtaxes(bidderRequest, data); // loop through userIds and add to request if (bidRequest.userIdAsEids) { bidRequest.userIdAsEids.forEach(eid => { @@ -1037,6 +1039,27 @@ function applyFPD(bidRequest, mediaType, data) { } } +function addDesiredSegtaxes(bidderRequest, target) { + if (rubiConf.readTopics === false) { + return; + } + let iSegments = [1, 2, 5, 6, 7, 507].concat(rubiConf.sendSiteSegtax?.map(seg => Number(seg)) || []); + let vSegments = [4, 508].concat(rubiConf.sendUserSegtax?.map(seg => Number(seg)) || []); + let userData = bidderRequest.ortb2?.user?.data || []; + let siteData = bidderRequest.ortb2?.site?.content?.data || []; + userData.forEach(iterateOverSegmentData(target, 'v', vSegments)); + siteData.forEach(iterateOverSegmentData(target, 'i', iSegments)); +} + +function iterateOverSegmentData(target, char, segments) { + return (topic) => { + const taxonomy = Number(topic.ext?.segtax); + if (segments.includes(taxonomy)) { + target[`tg_${char}.tax${taxonomy}`] = topic.segment?.map(seg => seg.id).join(','); + } + } +} + /** * @param sizes * @returns {*} diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 0312114e75b..184bea07d04 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2770,6 +2770,56 @@ describe('the rubicon adapter', function () { expect(slotParams['o_ae']).to.equal(1) }); + + it('should pass along desired segtaxes, but not non-desired ones', () => { + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.refererInfo = {domain: 'bob'}; + config.setConfig({ + rubicon: { + sendUserSegtax: [9], + sendSiteSegtax: [10] + } + }); + localBidderRequest.ortb2.user = { + data: [{ + ext: { + segtax: '404' + }, + segment: [{id: 5}, {id: 6}] + }, { + ext: { + segtax: '508' + }, + segment: [{id: 5}, {id: 2}] + }, { + ext: { + segtax: '9' + }, + segment: [{id: 1}, {id: 2}] + }] + } + localBidderRequest.ortb2.site = { + content: { + data: [{ + ext: { + segtax: '10' + }, + segment: [{id: 2}, {id: 3}] + }, { + ext: { + segtax: '507' + }, + segment: [{id: 3}, {id: 4}] + }] + } + } + const slotParams = spec.createSlotParams(bidderRequest.bids[0], localBidderRequest); + expect(slotParams['tg_i.tax507']).is.equal('3,4'); + expect(slotParams['tg_v.tax508']).is.equal('5,2'); + expect(slotParams['tg_v.tax9']).is.equal('1,2'); + expect(slotParams['tg_i.tax10']).is.equal('2,3'); + expect(slotParams['tg_v.tax404']).is.equal(undefined); + }); }); describe('classifiedAsVideo', function () { From ab7068eef0dac3ca2511c68c6c8f086666ff7366 Mon Sep 17 00:00:00 2001 From: PavloMalashnyak <99171840+PavloMalashnyak@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:50:38 +0200 Subject: [PATCH 491/760] Blockthrough Bid Adapter: initial release (#10870) * PE-87: Implement Prebid Adapter (#1) * PE-87: implement BT Bid Adapter * PE-87: rework adapter to use ortbConverter lib, make requested changes * PE-87: update imports * PE-110: Add user sync logic to the Prebid Adapter (#3) * PE-110: add user sync logic * PE-110: update userSync url * PE-110: check if iframe is enabled before setting params * PE-111: BT Prebid Adapter can request AA ads or regular ads (#2) * PE-120: Send Prebid Bidder info to BT Server (#4) * PE-120: add btBidderCode to the bid object * PE-120: use single quotes for logs string * PE-123: Add More Metadata in site.ext.blockthrough (#5) * PE-123: send additional meta data * PE-123: send auctionID under imp.ext.prebid.blockthrough * PE-123: use ortb2 config to set site.ext params * PE-123: sent auctionId in ext.prebid.blockthrough.auctionID * PE-123: update logs for bidderConfig setup * PE-000: check if blockthrough is defined (#6) * PE-87: remove BT specific logic (#7) * Implement Blockthrough Prebid Adapter * PE-87: Implement Prebid Adapter - misc fixes (#9) * PE-87: rename test file, add bidder config * PE-87: increase ttl * PE-000: fix test * BP-74: Change the way we enable debug (#10) * BP-79: Send GPID as a part of `imp[].ext` (#11) * BP-79: send gpid in imp.ext * BP-79: add optional operator * BP-90: Update Cookie Sync Logic (#12) * BP-90: pass bidder to cookie sync * BP-90: update sync logic, fix typo * BP-90: use const for syncs variable * BP-55: Re-add endpoint URLs (#13) * BP-91: Add prebid JS version to auction request (#14) --- modules/BTBidAdapter.js | 204 ++++++++++++++++++++++++ modules/BTBidAdapter.md | 70 +++++++++ test/spec/modules/BTBidAdapter_spec.js | 209 +++++++++++++++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 modules/BTBidAdapter.js create mode 100644 modules/BTBidAdapter.md create mode 100644 test/spec/modules/BTBidAdapter_spec.js diff --git a/modules/BTBidAdapter.js b/modules/BTBidAdapter.js new file mode 100644 index 00000000000..7b50b90124b --- /dev/null +++ b/modules/BTBidAdapter.js @@ -0,0 +1,204 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepSetValue, isPlainObject, logWarn } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'blockthrough'; +const GVLID = 815; +const ENDPOINT_URL = 'https://pbs.btloader.com/openrtb2/auction'; +const SYNC_URL = 'https://cdn.btloader.com/user_sync.html'; + +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 60, + }, + imp, + request, + bidResponse, +}); + +/** + * Builds an impression object for the ORTB 2.5 request. + * + * @param {function} buildImp - The function for building an imp object. + * @param {Object} bidRequest - The bid request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 imp object. + */ +function imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const { params, ortb2Imp } = bidRequest; + + if (params) { + deepSetValue(imp, 'ext', params); + } + if (ortb2Imp?.ext?.gpid) { + deepSetValue(imp, 'ext.gpid', ortb2Imp.ext.gpid); + } + + return imp; +} + +/** + * Builds a request object for the ORTB 2.5 request. + * + * @param {function} buildRequest - The function for building a request object. + * @param {Array} imps - An array of ORTB 2.5 impression objects. + * @param {Object} bidderRequest - The bidder request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'ext.prebid.channel', { + name: 'pbjs', + version: '$prebid.version$', + }); + + if (window.location.href.includes('btServerTest=true')) { + request.test = 1; + } + + return request; +} + +/** + * Processes a bid response using the provided build function, bid, and context. + * + * @param {Function} buildBidResponse - The function to build the bid response. + * @param {Object} bid - The bid object to include in the bid response. + * @param {Object} context - The context object containing additional information. + * @returns {Object} - The processed bid response. + */ +function bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + const { seat } = context.seatbid || {}; + bidResponse.btBidderCode = seat; + + return bidResponse; +} + +/** + * Checks if a bid request is valid. + * + * @param {Object} bid - The bid request object. + * @returns {boolean} True if the bid request is valid, false otherwise. + */ +function isBidRequestValid(bid) { + if (!isPlainObject(bid.params) || !Object.keys(bid.params).length) { + logWarn('BT Bid Adapter: bid params must be provided.'); + return false; + } + + return true; +} + +/** + * Builds the bid requests for the BT Service. + * + * @param {Array} validBidRequests - An array of valid bid request objects. + * @param {Object} bidderRequest - The bidder request object. + * @returns {Array} An array of BT Service bid requests. + */ +function buildRequests(validBidRequests, bidderRequest) { + const data = CONVERTER.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + }); + + return [ + { + method: 'POST', + url: ENDPOINT_URL, + data, + bids: validBidRequests, + }, + ]; +} + +/** + * Interprets the server response and maps it to bids. + * + * @param {Object} serverResponse - The server response object. + * @param {Object} request - The request object. + * @returns {Array} An array of bid objects. + */ +function interpretResponse(serverResponse, request) { + if (!serverResponse || !request) { + return []; + } + + return CONVERTER.fromORTB({ + response: serverResponse.body, + request: request.data, + }).bids; +} + +/** + * Generates user synchronization data based on provided options and consents. + * + * @param {Object} syncOptions - Synchronization options. + * @param {Object[]} serverResponses - An array of server responses. + * @param {Object} gdprConsent - GDPR consent information. + * @param {string} uspConsent - US Privacy consent string. + * @param {Object} gppConsent - Google Publisher Policies (GPP) consent information. + * @returns {Object[]} An array of user synchronization objects. + */ +function getUserSyncs( + syncOptions, + serverResponses, + gdprConsent, + uspConsent, + gppConsent +) { + if (!syncOptions.iframeEnabled || !serverResponses?.length) { + return []; + } + + const bidderCodes = new Set(); + serverResponses.forEach((serverResponse) => { + if (serverResponse?.body?.ext?.responsetimemillis) { + Object.keys(serverResponse.body.ext.responsetimemillis).forEach( + bidderCodes.add, + bidderCodes + ); + } + }); + + if (!bidderCodes.size) { + return []; + } + + const syncs = []; + const syncUrl = new URL(SYNC_URL); + syncUrl.searchParams.set('bidders', [...bidderCodes].join(',')); + + if (gdprConsent) { + syncUrl.searchParams.set('gdpr', Number(gdprConsent.gdprApplies)); + syncUrl.searchParams.set('gdpr_consent', gdprConsent.consentString); + } + if (gppConsent) { + syncUrl.searchParams.set('gpp', gppConsent.gppString); + syncUrl.searchParams.set('gpp_sid', gppConsent.applicableSections); + } + if (uspConsent) { + syncUrl.searchParams.set('us_privacy', uspConsent); + } + + syncs.push({ type: 'iframe', url: syncUrl.href }); + + return syncs; +} + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/modules/BTBidAdapter.md b/modules/BTBidAdapter.md new file mode 100644 index 00000000000..e29bc688b0c --- /dev/null +++ b/modules/BTBidAdapter.md @@ -0,0 +1,70 @@ +# Overview + +**Module Name**: BT Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: engsupport@blockthrough.com + +# Description + +The BT Bidder Adapter provides an interface to the BT Service. The BT Bidder Adapter sends one request to the BT Service per ad unit. Behind the scenes, the BT Service further disperses requests to various configured exchanges. This operational model closely resembles that of Prebid Server, where a single request is made from the client side, and responses are gathered from multiple exchanges. + +The BT adapter requires setup and approval from the Blockthrough team. Please reach out to marketing@blockthrough.com for more information. + +# Bid Params + +| Key | Scope | Type | Description | +| ------ | -------- | ------ | -------------------------------------------------------------- | +| bidder | Required | Object | Bidder configuration. Could configure several bidders this way | + +# Bidder Config + +Make sure to set required ab, orgID, websiteID values received after approval using `pbjs.setBidderConfig`. + +## Example + +```javascript +pbjs.setBidderConfig({ + bidders: ['blockthrough'], + config: { + ortb2: { + site: { + ext: { + blockthrough: { + ab: false, + orgID: '4829301576428910', + websiteID: '5654012389765432', + }, + }, + }, + }, + }, +}); +``` + +## AdUnits configuration example + +```javascript +var adUnits = [ + { + code: 'banner-div-1', + mediaTypes: { + banner: { + sizes: [[728, 90]], + }, + }, + bids: [ + { + bidder: 'blockthrough', + params: { + bidderA: { + publisherId: 55555, + }, + bidderB: { + zoneId: 12, + }, + }, + }, + ], + }, +]; +``` diff --git a/test/spec/modules/BTBidAdapter_spec.js b/test/spec/modules/BTBidAdapter_spec.js new file mode 100644 index 00000000000..e0306abb7f0 --- /dev/null +++ b/test/spec/modules/BTBidAdapter_spec.js @@ -0,0 +1,209 @@ +import { expect } from 'chai'; +import { spec } from 'modules/BTBidAdapter.js'; +import { BANNER } from '../../../src/mediaTypes.js'; +// load modules that register ORTB processors +import 'src/prebid.js'; +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/enrichmentFpdModule.js'; +import 'modules/gdprEnforcement.js'; +import 'modules/gppControl_usnat.js'; +import 'modules/schain.js'; + +describe('BT Bid Adapter', () => { + const ENDPOINT_URL = 'https://pbs.btloader.com/openrtb2/auction'; + const validBidRequests = [ + { + bidId: '2e9f38ea93bb9e', + bidder: 'blockthrough', + adUnitCode: 'adunit-code', + mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, + params: { + bidderA: { + pubId: '11111', + }, + }, + bidderRequestId: 'test-bidder-request-id', + }, + ]; + const bidderRequest = { + bidderCode: 'blockthrough', + bidderRequestId: 'test-bidder-request-id', + bids: validBidRequests, + }; + + describe('isBidRequestValid', function () { + it('should validate bid request with valid params', () => { + const validBid = { + params: { + pubmatic: { + publisherId: 55555, + }, + }, + sizes: [[300, 250]], + bidId: '123', + adUnitCode: 'leaderboard', + }; + + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.be.true; + }); + + it('should not validate bid request with invalid params', () => { + const invalidBid = { + params: {}, + sizes: [[300, 250]], + bidId: '123', + adUnitCode: 'leaderboard', + }; + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + }); + + describe('buildRequests', () => { + it('should build post request when ortb2 fields are present', () => { + const impExtParams = { + bidderA: { + pubId: '11111', + }, + }; + + const requests = spec.buildRequests(validBidRequests, bidderRequest); + + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal(ENDPOINT_URL); + expect(requests[0].data).to.exist; + expect(requests[0].data.ext.prebid.channel).to.deep.equal({ + name: 'pbjs', + version: '$prebid.version$', + }); + expect(requests[0].data.imp[0].ext).to.deep.equal(impExtParams); + }); + }); + + describe('interpretResponse', () => { + it('should return empty array if serverResponse is not defined', () => { + const bidRequest = spec.buildRequests(validBidRequests, bidderRequest); + const bids = spec.interpretResponse(undefined, bidRequest); + + expect(bids.length).to.equal(0); + }); + + it('should return bids array when serverResponse is defined and seatbid array is not empty', () => { + const bidResponse = { + body: { + id: 'bid-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: '2e9f38ea93bb9e', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 1, + adomain: ['test.com'], + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + btBidderCode: 'test-seat', + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'banner', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + requestId: '2e9f38ea93bb9e', + ttl: 60, + width: 300, + }, + ]; + + const request = spec.buildRequests(validBidRequests, bidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + }); + + describe('getUserSyncs', () => { + const SYNC_URL = 'https://cdn.btloader.com/user_sync.html'; + + it('should return an empty array if no sync options are provided', () => { + const syncs = spec.getUserSyncs({}, [], null, null, null); + + expect(syncs).to.deep.equal([]); + }); + + it('should return an empty array if no server responses are provided', () => { + const syncs = spec.getUserSyncs( + { iframeEnabled: true }, + [], + null, + null, + null + ); + + expect(syncs).to.deep.equal([]); + }); + + it('should pass consent parameters and bidder codes in sync URL if they are provided', () => { + const gdprConsent = { + gdprApplies: true, + consentString: 'GDPRConsentString123', + }; + const gppConsent = { + gppString: 'GPPString123', + applicableSections: ['sectionA'], + }; + const us_privacy = '1YNY'; + const expectedSyncUrl = new URL(SYNC_URL); + expectedSyncUrl.searchParams.set('bidders', 'pubmatic,ix'); + expectedSyncUrl.searchParams.set('gdpr', 1); + expectedSyncUrl.searchParams.set( + 'gdpr_consent', + gdprConsent.consentString + ); + expectedSyncUrl.searchParams.set('gpp', gppConsent.gppString); + expectedSyncUrl.searchParams.set('gpp_sid', 'sectionA'); + expectedSyncUrl.searchParams.set('us_privacy', us_privacy); + const syncs = spec.getUserSyncs( + { iframeEnabled: true }, + [ + { body: { ext: { responsetimemillis: { pubmatic: 123 } } } }, + { body: { ext: { responsetimemillis: { pubmatic: 123, ix: 123 } } } }, + ], + gdprConsent, + us_privacy, + gppConsent + ); + + expect(syncs).to.deep.equal([ + { type: 'iframe', url: expectedSyncUrl.href }, + ]); + }); + }); +}); From d93032ba075ac0b546f4d8a43b89ce6bacb65067 Mon Sep 17 00:00:00 2001 From: Alexandru Date: Sat, 24 Feb 2024 05:52:49 +0200 Subject: [PATCH 492/760] OMS Adapter: add new adapter (#10924) * OMS Adapter: add new adapter * OMS Adapter: fix tests * OMS Adapter: required changes * OMS Adapter: change ttl * OMS Adapter: required changes --- libraries/percentInView/percentInView.js | 63 ++++ modules/omsBidAdapter.js | 270 +++++++++++++++ modules/omsBidAdapter.md | 46 +++ test/spec/modules/omsBidAdapter_spec.js | 398 +++++++++++++++++++++++ 4 files changed, 777 insertions(+) create mode 100644 libraries/percentInView/percentInView.js create mode 100644 modules/omsBidAdapter.js create mode 100644 modules/omsBidAdapter.md create mode 100644 test/spec/modules/omsBidAdapter_spec.js diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js new file mode 100644 index 00000000000..13381c5c541 --- /dev/null +++ b/libraries/percentInView/percentInView.js @@ -0,0 +1,63 @@ + +function getBoundingBox(element, {w, h} = {}) { + let {width, height, left, top, right, bottom} = element.getBoundingClientRect(); + + if ((width === 0 || height === 0) && w && h) { + width = w; + height = h; + right = left + w; + bottom = top + h; + } + + return {width, height, left, top, right, bottom}; +} + +function getIntersectionOfRects(rects) { + const bbox = { + left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom + }; + + for (let i = 1; i < rects.length; ++i) { + bbox.left = Math.max(bbox.left, rects[i].left); + bbox.right = Math.min(bbox.right, rects[i].right); + + if (bbox.left >= bbox.right) { + return null; + } + + bbox.top = Math.max(bbox.top, rects[i].top); + bbox.bottom = Math.min(bbox.bottom, rects[i].bottom); + + if (bbox.top >= bbox.bottom) { + return null; + } + } + + bbox.width = bbox.right - bbox.left; + bbox.height = bbox.bottom - bbox.top; + + return bbox; +} + +export const percentInView = (element, topWin, {w, h} = {}) => { + const elementBoundingBox = getBoundingBox(element, {w, h}); + + // Obtain the intersection of the element and the viewport + const elementInViewBoundingBox = getIntersectionOfRects([{ + left: 0, top: 0, right: topWin.innerWidth, bottom: topWin.innerHeight + }, elementBoundingBox]); + + let elementInViewArea, elementTotalArea; + + if (elementInViewBoundingBox !== null) { + // Some or all of the element is in view + elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height; + elementTotalArea = elementBoundingBox.width * elementBoundingBox.height; + + return ((elementInViewArea / elementTotalArea) * 100); + } + + // No overlap between element and the viewport; therefore, the element + // lies completely out of view + return 0; +} diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js new file mode 100644 index 00000000000..bef9a43749f --- /dev/null +++ b/modules/omsBidAdapter.js @@ -0,0 +1,270 @@ +import { + isArray, + getWindowTop, + deepSetValue, + logError, + logWarn, + createTrackPixelHtml, + getWindowSelf, + isFn, + isPlainObject, + getBidIdParameter, + getUniqueIdentifierStr, +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {ajax} from '../src/ajax.js'; +import {percentInView} from '../libraries/percentInView/percentInView.js'; + +const BIDDER_CODE = 'oms'; +const URL = 'https://rt.marphezis.com/hb'; +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' + +export const spec = { + code: BIDDER_CODE, + aliases: ['brightcom', 'bcmssp'], + gvlid: 883, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + onBidderError, + onBidWon, + getUserSyncs, +}; + +function buildRequests(bidReqs, bidderRequest) { + try { + const impressions = bidReqs.map(bid => { + let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; + bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + + const element = document.getElementById(bid.adUnitCode); + const minSize = _getMinSize(processedSizes); + const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) : 'na'; + const viewabilityAmountRounded = isNaN(viewabilityAmount) ? viewabilityAmount : Math.round(viewabilityAmount); + + const imp = { + id: bid.bidId, + banner: { + format: processedSizes, + ext: { + viewability: viewabilityAmountRounded + } + }, + tagid: String(bid.adUnitCode) + }; + + const bidFloor = _getBidFloor(bid); + + if (bidFloor) { + imp.bidfloor = bidFloor; + } + + return imp; + }) + + const referrer = bidderRequest?.refererInfo?.page || ''; + const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); + + const payload = { + id: getUniqueIdentifierStr(), + imp: impressions, + site: { + domain: bidderRequest?.refererInfo?.domain || '', + page: referrer, + publisher: { + id: publisherId + } + }, + device: { + devicetype: _getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), + w: screen.width, + h: screen.height + }, + tmax: bidderRequest?.timeout + }; + + if (bidderRequest?.gdprConsent) { + deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + + const gpp = _getGpp(bidderRequest) + if (gpp) { + deepSetValue(payload, 'regs.ext.gpp', gpp); + } + + if (bidderRequest?.ortb2?.regs?.coppa) { + deepSetValue(payload, 'regs.coppa', 1); + } + + if (bidReqs?.[0]?.schain) { + deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) + } + + if (bidderRequest?.ortb2?.user) { + deepSetValue(payload, 'user', bidderRequest.ortb2.user) + } + + if (bidReqs?.[0]?.userIdAsEids) { + deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) + } + + if (bidReqs?.[0].userId) { + deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) + } + + if (bidderRequest?.ortb2?.site?.content) { + deepSetValue(payload, 'site.content', bidderRequest.ortb2.site.content) + } + + return { + method: 'POST', + url: URL, + data: JSON.stringify(payload), + }; + } catch (e) { + logError(e, {bidReqs, bidderRequest}); + } +} + +function isBidRequestValid(bid) { + if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + return false; + } + + return true; +} + +function interpretResponse(serverResponse) { + let response = []; + if (!serverResponse.body || typeof serverResponse.body != 'object') { + logWarn('OMS server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); + return response; + } + + const {body: {id, seatbid}} = serverResponse; + + try { + if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { + response = seatbid[0].bid.map(bid => { + return { + requestId: bid.impid, + cpm: parseFloat(bid.price), + width: parseInt(bid.w), + height: parseInt(bid.h), + creativeId: bid.crid || bid.id, + currency: 'USD', + netRevenue: true, + mediaType: BANNER, + ad: _getAdMarkup(bid), + ttl: 300, + meta: { + advertiserDomains: bid?.adomain || [] + } + }; + }); + } + } catch (e) { + logError(e, {id, seatbid}); + } + + return response; +} + +// Don't do user sync for now +function getUserSyncs(syncOptions, responses, gdprConsent) { + return []; +} + +function onBidderError(errorData) { + if (errorData === null || !errorData.bidderRequest) { + return; + } + + _trackEvent('error', errorData.bidderRequest) +} + +function onBidWon(bid) { + if (bid === null) { + return; + } + + _trackEvent('bidwon', bid) +} + +function _trackEvent(endpoint, data) { + ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); +} + +function _getDeviceType(ua, sua) { + if (sua?.mobile || (/(ios|ipod|ipad|iphone|android)/i).test(ua)) { + return 1 + } + + if ((/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(ua)) { + return 3 + } + + return 2 +} + +function _getGpp(bidderRequest) { + if (bidderRequest?.gppConsent != null) { + return bidderRequest.gppConsent; + } + + return ( + bidderRequest?.ortb2?.regs?.gpp ?? { gppString: '', applicableSections: '' } + ); +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !_isIframe() && element !== null; +} + +function _getViewability(element, topWin, {w, h} = {}) { + return getWindowTop().document.visibilityState === 'visible' ? percentInView(element, topWin, {w, h}) : 0; +} + +function _isIframe() { + try { + return getWindowSelf() !== getWindowTop(); + } catch (e) { + return true; + } +} + +function _getMinSize(sizes) { + return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min); +} + +function _getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return bid.params.bidFloor ? bid.params.bidFloor : null; + } + + let floor = bid.getFloor({ + currency: 'USD', mediaType: '*', size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + +registerBidder(spec); diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md new file mode 100644 index 00000000000..f1e2d459eca --- /dev/null +++ b/modules/omsBidAdapter.md @@ -0,0 +1,46 @@ +# Overview + +``` +Module Name: OMS Bid Adapter +Module Type: Bidder Adapter +Maintainer: alexandruc@onlinemediasolutions.com +``` + +# Description + +Online media solutions adapter integration to the Prebid library. + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-leaderboard', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bids: [{ + bidder: 'oms', + params: { + publisherId: 2141020, + bidFloor: 0.01 + } + }] + }, { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'oms', + params: { + publisherId: 2141020 + } + }] + } +] +``` diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js new file mode 100644 index 00000000000..a7b7ba09113 --- /dev/null +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -0,0 +1,398 @@ +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {spec} from 'modules/omsBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory.js'; +import {config} from '../../../src/config'; + +const URL = 'https://rt.marphezis.com/hb'; + +describe('omsBidAdapter', function () { + const adapter = newBidder(spec); + let element, win; + let bidRequests; + let sandbox; + + beforeEach(function () { + element = { + x: 0, + y: 0, + + width: 0, + height: 0, + + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + win = { + document: { + visibilityState: 'visible' + }, + + innerWidth: 800, + innerHeight: 600 + }; + bidRequests = [{ + 'bidder': 'oms', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + + sandbox = sinon.sandbox.create(); + sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns(win); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'oms', + 'params': { + 'publisherId': 1234567 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'bidId': '5fb26ac22bde4', + 'bidderRequestId': '4bf93aeb730cb9', + 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when publisherId not passed correctly', function () { + bid.params.publisherId = undefined; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false when require params are not passed', function () { + let bid = Object.assign({}, bid); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('sends bid request to our endpoint via POST', function () { + const request = spec.buildRequests(bidRequests); + expect(request.method).to.equal('POST'); + }); + + it('request url should match our endpoint url', function () { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.equal(URL); + }); + + it('sets the proper banner object', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); + }); + + it('accepts a single array as a size', function () { + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); + }); + + it('sends bidfloor param if present', function () { + bidRequests[0].params.bidFloor = 0.05; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].bidfloor).to.equal(0.05); + }); + + it('sends tagid', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].tagid).to.equal('adunit-code'); + }); + + it('sends publisher id', function () { + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.site.publisher.id).to.equal(1234567); + }); + + it('sends gdpr info if exists', function () { + const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + 'bidderCode': 'oms', + 'auctionId': '1d1a030790a437', + 'bidderRequestId': '22edbae2744bf5', + 'timeout': 3000, + gdprConsent: { + consentString: consentString, + gdprApplies: true + }, + refererInfo: { + page: 'http://example.com/page.html', + domain: 'example.com', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('sends coppa', function () { + const data = JSON.parse(spec.buildRequests(bidRequests, {ortb2: {regs: {coppa: 1}}}).data) + expect(data.regs).to.not.be.undefined; + expect(data.regs.coppa).to.equal(1); + }); + + it('sends schain', function () { + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data).to.not.be.undefined; + expect(data.source).to.not.be.undefined; + expect(data.source.ext).to.not.be.undefined; + expect(data.source.ext.schain).to.not.be.undefined; + expect(data.source.ext.schain.complete).to.equal(1); + expect(data.source.ext.schain.ver).to.equal('1.0'); + expect(data.source.ext.schain.nodes).to.not.be.undefined; + expect(data.source.ext.schain.nodes).to.lengthOf(1); + expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(data.source.ext.schain.nodes[0].hp).to.equal(1); + expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + + it('sends user eid parameters', function () { + bidRequests[0].userIdAsEids = [{ + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.eids).to.not.be.undefined; + expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); + }); + + it('sends user id parameters', function () { + const userId = { + sharedid: { + id: '01*******', + third: '01E*******' + } + }; + + bidRequests[0].userId = userId; + + const data = JSON.parse(spec.buildRequests(bidRequests).data); + expect(data.user).to.not.be.undefined; + expect(data.user.ext).to.not.be.undefined; + expect(data.user.ext.ids).is.deep.equal(userId); + }); + + context('when element is fully in view', function () { + it('returns 100', function () { + Object.assign(element, {width: 600, height: 400}); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(100); + }); + }); + + context('when element is out of view', function () { + it('returns 0', function () { + Object.assign(element, {x: -300, y: 0, width: 207, height: 320}); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + + context('when element is partially in view', function () { + it('returns percentage', function () { + Object.assign(element, {width: 800, height: 800}); + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(75); + }); + }); + + context('when width or height of the element is zero', function () { + it('try to use alternative values', function () { + Object.assign(element, {width: 0, height: 0}); + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(25); + }); + }); + + context('when nested iframes', function () { + it('returns \'na\'', function () { + Object.assign(element, {width: 600, height: 400}); + + utils.getWindowTop.restore(); + utils.getWindowSelf.restore(); + sandbox.stub(utils, 'getWindowTop').returns(win); + sandbox.stub(utils, 'getWindowSelf').returns({}); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal('na'); + }); + }); + + context('when tab is inactive', function () { + it('returns 0', function () { + Object.assign(element, {width: 600, height: 400}); + + utils.getWindowTop.restore(); + win.document.visibilityState = 'hidden'; + sandbox.stub(utils, 'getWindowTop').returns(win); + + const request = spec.buildRequests(bidRequests); + const payload = JSON.parse(request.data); + expect(payload.imp[0].banner.ext.viewability).to.equal(0); + }); + }); + }); + + describe('interpretResponse', function () { + let response; + beforeEach(function () { + response = { + body: { + 'id': '37386aade21a71', + 'seatbid': [{ + 'bid': [{ + 'id': '376874781', + 'impid': '283a9f4cd2415d', + 'price': 0.35743275, + 'nurl': '', + 'adm': '', + 'w': 300, + 'h': 250, + 'adomain': ['example.com'] + }] + }] + } + }; + }); + + it('should get the correct bid response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': '376874781', + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('crid should default to the bid id if not on the response', function () { + let expectedResponse = [{ + 'requestId': '283a9f4cd2415d', + 'cpm': 0.35743275, + 'width': 300, + 'height': 250, + 'creativeId': response.body.seatbid[0].bid[0].id, + 'currency': 'USD', + 'netRevenue': true, + 'mediaType': 'banner', + 'ad': `
`, + 'ttl': 300, + 'meta': { + 'advertiserDomains': ['example.com'] + } + }]; + + let result = spec.interpretResponse(response); + expect(result[0]).to.deep.equal(expectedResponse[0]); + }); + + it('handles empty bid response', function () { + let response = { + body: '' + }; + let result = spec.interpretResponse(response); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs ', () => { + let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + + it('should not return', () => { + let returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + }); +}); From f2eb49b7a716d79c4e478673c663f92c40614d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=90=91=E5=86=9B?= <401154047@qq.com> Date: Sat, 24 Feb 2024 11:57:56 +0800 Subject: [PATCH 493/760] zMaticoo Bid Adapter : add onBidWon function (#11056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:add onBidWon function Signed-off-by: adam * bug:remove bidid and device required logic Signed-off-by: adam --------- Signed-off-by: adam Co-authored-by: adam --- modules/zmaticooBidAdapter.js | 40 ++++++++++--- modules/zmaticooBidAdapter.md | 8 --- test/spec/modules/zmaticooBidAdapter_spec.js | 61 ++++++-------------- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/modules/zmaticooBidAdapter.js b/modules/zmaticooBidAdapter.js index eeb130af7d5..f392b74f672 100644 --- a/modules/zmaticooBidAdapter.js +++ b/modules/zmaticooBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isArray, isBoolean, isNumber, isStr, logWarn} from '../src/utils.js'; +import {deepAccess, isArray, isBoolean, isNumber, isStr, logWarn, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -46,7 +46,7 @@ export const spec = { logWarn('Invalid bid request - missing required mediaTypes'); return false; } - if (!(bid && bid.bidId && bid.params)) { + if (!(bid && bid.params)) { logWarn('Invalid bid request - missing required bid data'); return false; } @@ -55,11 +55,6 @@ export const spec = { logWarn('Invalid bid request - missing required field pubId'); return false; } - - if (!(bid.params.device)) { - logWarn('Invalid bid request - missing required device data'); - return false; - } return true; }, @@ -180,6 +175,7 @@ export const spec = { ttl: TTL, creativeId: zmBid.crid, netRevenue: NET_REV, + nurl: zmBid.nurl, }; bid.meta = { advertiserDomains: (zmBid.adomain && zmBid.adomain.length) ? zmBid.adomain : [] @@ -187,11 +183,41 @@ export const spec = { if (zmBid.ext && zmBid.ext.vast_url) { bid.vastXml = zmBid.ext.vast_url; } + if (zmBid.ext && zmBid.ext.prebid) { + bid.mediaType = zmBid.ext.prebid.type + } else { + bid.mediaType = BANNER + } bidResponses.push(bid); }) }) } return bidResponses; + }, + onBidWon: function (bid) { + if (!bid['nurl']) { + return false + } + const winCpm = (bid.hasOwnProperty('originalCpm')) ? bid.originalCpm : bid.cpm + const winCurr = (bid.hasOwnProperty('originalCurrency') && bid.hasOwnProperty('originalCpm')) ? bid.originalCurrency : bid.currency + const winUrl = bid.nurl.replace( + /\$\{AUCTION_PRICE\}/, + winCpm + ).replace( + /\$\{AUCTION_IMP_ID\}/, + bid.requestId + ).replace( + /\$\{AUCTION_CURRENCY\}/, + winCurr + ).replace( + /\$\{AUCTON_BID_ID\}/, + bid.bidId + ).replace( + /\$\{AUCTION_ID\}/, + bid.auctionId + ) + triggerPixel(winUrl); + return true } } diff --git a/modules/zmaticooBidAdapter.md b/modules/zmaticooBidAdapter.md index 06c54b10b11..98e0371bc11 100644 --- a/modules/zmaticooBidAdapter.md +++ b/modules/zmaticooBidAdapter.md @@ -25,18 +25,11 @@ zMaticoo Bidder Adapter for Prebid.js. bids: [ { bidder: 'zmaticoo', - bidId: '123456', params: { user: { uid: '12345', buyeruid: '12345' }, - device: { - ip: '111.222.33.44', - geo: { - country: 'USA' - } - }, pubId: 'prebid-fgh', test: 1 } @@ -70,7 +63,6 @@ zMaticoo Bidder Adapter for Prebid.js. }, bids: [{ bidder: "zmaticoo", - bidId: '123456', params: { pubId: 'prebid-test', site: {domain: "test.com"} diff --git a/test/spec/modules/zmaticooBidAdapter_spec.js b/test/spec/modules/zmaticooBidAdapter_spec.js index 1634340ac6a..bb89984c738 100644 --- a/test/spec/modules/zmaticooBidAdapter_spec.js +++ b/test/spec/modules/zmaticooBidAdapter_spec.js @@ -1,9 +1,9 @@ import {checkParamDataType, spec} from '../../../modules/zmaticooBidAdapter.js' -import {deepClone} from '../../../src/utils'; +import utils, {deepClone} from '../../../src/utils'; +import {expect} from 'chai'; describe('zMaticoo Bidder Adapter', function () { const bannerRequest = [{ - bidId: '1234511', auctionId: '223', mediaTypes: { banner: { @@ -18,12 +18,6 @@ describe('zMaticoo Bidder Adapter', function () { uid: '12345', buyeruid: '12345' }, - device: { - ip: '111.222.33.44', - geo: { - country: 'USA' - } - }, pubId: 'prebid-test', test: 1, bidfloor: 1, @@ -31,7 +25,6 @@ describe('zMaticoo Bidder Adapter', function () { } }]; const bannerRequest1 = [{ - bidId: '1234511', auctionId: '223', mediaTypes: { banner: { @@ -46,12 +39,6 @@ describe('zMaticoo Bidder Adapter', function () { uid: '12345', buyeruid: '12345' }, - device: { - ip: '111.222.33.44', - geo: { - country: 'USA' - } - }, pubId: 'prebid-test', test: 1, tagid: 'test' @@ -68,7 +55,6 @@ describe('zMaticoo Bidder Adapter', function () { }, }]; const videoRequest = [{ - bidId: '1234511', auctionId: '223', mediaTypes: { video: { @@ -94,12 +80,6 @@ describe('zMaticoo Bidder Adapter', function () { uid: '12345', buyeruid: '12345' }, - device: { - ip: '111.222.33.44', - geo: { - country: 'USA' - } - }, pubId: 'prebid-test', test: 1, tagid: 'test', @@ -108,7 +88,6 @@ describe('zMaticoo Bidder Adapter', function () { }]; const videoRequest1 = [{ - bidId: '1234511', auctionId: '223', mediaTypes: { video: { @@ -131,12 +110,6 @@ describe('zMaticoo Bidder Adapter', function () { uid: '12345', buyeruid: '12345' }, - device: { - ip: '111.222.33.44', - geo: { - country: 'USA' - } - }, pubId: 'prebid-test', test: 1, tagid: 'test', @@ -153,24 +126,12 @@ describe('zMaticoo Bidder Adapter', function () { const invalidBid = spec.isBidRequestValid(null); expect(invalidBid).to.be.false; }); - it('missing required bid.bidId', function () { - const request = deepClone(videoRequest[0]) - delete request.bidId - const invalidBid = spec.isBidRequestValid(request); - expect(invalidBid).to.be.false; - }); it('missing required params.pubId', function () { const request = deepClone(videoRequest[0]) delete request.params.pubId const invalidBid = spec.isBidRequestValid(request); expect(invalidBid).to.be.false; }); - it('missing required device data', function () { - const request = deepClone(videoRequest[0]) - delete request.params.device - const invalidBid = spec.isBidRequestValid(request); - expect(invalidBid).to.be.false; - }); }) describe('buildRequests', function () { it('Test the banner request processing function', function () { @@ -260,8 +221,12 @@ describe('zMaticoo Bidder Adapter', function () { adomain: ['test.com'], h: 50, w: 320, + nurl: 'https://gwbudgetali.iymedia.me/budget.php', ext: { - vast_url: '' + vast_url: '', + prebid: { + type: 'banner' + } } } ] @@ -284,6 +249,18 @@ describe('zMaticoo Bidder Adapter', function () { expect(bid.requestId).to.equal(receivedBid.impid); expect(bid.vastXml).to.equal(receivedBid.ext.vast_url); expect(bid.meta.advertiserDomains).to.equal(receivedBid.adomain); + expect(bid.mediaType).to.equal(receivedBid.ext.prebid.type); + expect(bid.nurl).to.equal(receivedBid.nurl); }); }); + describe('onBidWon', function () { + it('should make an ajax call with the original cpm', function () { + const bid = { + nurl: 'http://test.com/win?auctionPrice=${AUCTION_PRICE}', + cpm: 2.1, + } + const bidWonResult = spec.onBidWon(bid) + expect(bidWonResult).to.equal(true) + }); + }) }); From d607309d6ef7201b01d1748164605907260220e1 Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Sat, 24 Feb 2024 05:11:04 +0100 Subject: [PATCH 494/760] Criteo bid adapter: add fledge timeout and group limits (#11125) Set timeout to 50ms and 60 maximum interest groups per buyer --- modules/criteoBidAdapter.js | 14 ++++++++ test/spec/modules/criteoBidAdapter_spec.js | 40 +++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 40d39dc5618..0618a076193 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -298,6 +298,18 @@ export const spec = { 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) { @@ -311,6 +323,8 @@ export const spec = { sellerSignals, sellerTimeout, perBuyerSignals, + perBuyerTimeout, + perBuyerGroupLimits, auctionSignals: {}, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, interestGroupBuyers: Object.keys(perBuyerSignals), diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 4c599550afb..1139dbf5210 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2572,6 +2572,8 @@ describe('The Criteo bidding adapter', function () { sellerTimeout: 500, sellerSignals: { foo: 'bar', + perBuyerTimeout: { 'buyer1': 100, 'buyer2': 200 }, + perBuyerGroupLimits: { 'buyer1': 300, 'buyer2': 400 }, }, sellerSignalsPerImp: { 'test-bidId': { @@ -2641,6 +2643,16 @@ describe('The Criteo bidding adapter', function () { foo: 'baz' }, }, + perBuyerTimeout: { + '*': 50, + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + '*': 60, + 'buyer1': 300, + 'buyer2': 400 + }, seller: 'https://seller-domain.com', sellerTimeout: 500, sellerSignals: { @@ -2648,8 +2660,16 @@ describe('The Criteo bidding adapter', function () { foo2: 'bar2', floor: 1, currency: 'USD', + perBuyerTimeout: { + 'buyer1': 100, + 'buyer2': 200 + }, + perBuyerGroupLimits: { + 'buyer1': 300, + 'buyer2': 400 + }, }, - sellerCurrency: 'USD' + sellerCurrency: 'USD', }, }); expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ @@ -2666,11 +2686,29 @@ describe('The Criteo bidding adapter', function () { 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: '???' }, From cd6934ca3c14073ca310677987892a064fb109b4 Mon Sep 17 00:00:00 2001 From: Jhon Barreiro <90330032+jhon-reset@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:12:03 -0500 Subject: [PATCH 495/760] Reset Digital Bid Adapter: usersync url (#11138) * Updating image-based syncs url * minor config fix --- modules/resetdigitalBidAdapter.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index 7ace90744e8..1fe4b4d750c 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -194,14 +194,17 @@ export const spec = { } } - if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { + if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: - 'https://async.resetdigital.co/async_usersync.html?' + - gdprParams.length - ? gdprParams - : '', + url: `https://async.resetdigital.co/async_usersync.html?${gdprParams}`, + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://meta.resetdigital.co/pchain${ + gdprParams ? `?${gdprParams}` : '' + }`, }); } return syncs; From 2e4003548b4f51cc7fbe25f142c48a2fd725c4f3 Mon Sep 17 00:00:00 2001 From: Viktor Chernodub <37013688+chernodub@users.noreply.github.com> Date: Sat, 24 Feb 2024 07:12:20 +0300 Subject: [PATCH 496/760] Yandex: use ortb2 info & Core: add webdriver flag (#11110) * feat: add ortb2 types * feat: add ortb2 info to yandex * feat: add device.ext.webdriver to prebid core * fix: remove webdriver detection from yandex adapter * fix: adjust ortb2 types --- modules/yandexBidAdapter.js | 39 +++------ src/auction.js | 1 + src/fpd/enrichment.js | 8 +- src/types/ortb2.d.ts | 59 ++++++++++++++ test/spec/fpd/enrichment_spec.js | 19 ++++- test/spec/modules/yandexBidAdapter_spec.js | 92 +++++++++++++++++----- 6 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 src/types/ortb2.d.ts diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index cb1cbd89c84..e694dbcfe03 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -1,8 +1,8 @@ -import { formatQS, deepAccess, deepSetValue, triggerPixel, _each, _map } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE } from '../src/mediaTypes.js' -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { config } from '../src/config.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -53,7 +53,7 @@ const DEFAULT_CURRENCY = 'EUR'; /** * @type {MediaType[]} */ -const SUPPORTED_MEDIA_TYPES = [ BANNER, NATIVE ]; +const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const SSP_ID = 10500; const IMAGE_ASSET_TYPES = { @@ -121,15 +121,7 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - let referrer = ''; - let domain = ''; - let page = ''; - - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.ref; - domain = bidderRequest.refererInfo.domain; - page = bidderRequest.refererInfo.page; - } + const ortb2 = bidderRequest.ortb2; let timeout = null; if (bidderRequest) { @@ -146,7 +138,7 @@ export const spec = { const queryParams = { 'imp-id': impId, - 'target-ref': targetRef || domain, + 'target-ref': targetRef || ortb2?.site?.domain, 'ssp-id': SSP_ID, }; @@ -177,12 +169,10 @@ export const spec = { const data = { id: bidRequest.bidId, imp: [imp], - site: { - ref: referrer, - page, - domain, - }, + site: ortb2?.site, tmax: timeout, + user: ortb2?.user, + device: ortb2?.device, }; const eids = deepAccess(bidRequest, 'userIdAsEids'); @@ -190,11 +180,6 @@ export const spec = { deepSetValue(data, 'user.ext.eids', eids); } - const userData = deepAccess(bidRequest, 'ortb2.user.data'); - if (userData && userData.length) { - deepSetValue(data, 'user.data', userData); - } - const queryParamsString = formatQS(queryParams); return { method: 'POST', @@ -274,8 +259,8 @@ function getBidfloor(bidRequest) { const floorInfo = bidRequest.getFloor({ currency: DEFAULT_CURRENCY, mediaType: type, - size: bidRequest.sizes || '*' } - ) + size: bidRequest.sizes || '*' + }) floors.push(floorInfo); } }); @@ -332,7 +317,7 @@ function mapNative(bidRequest) { } function mapAsset(assetCode, adUnitAssetParams, nativeAsset) { - const [ nativeAssetId, nativeAssetType ] = nativeAsset; + const [nativeAssetId, nativeAssetType] = nativeAsset; const asset = { id: nativeAssetId, }; diff --git a/src/auction.js b/src/auction.js index c2758fdd571..2d7d350bb7a 100644 --- a/src/auction.js +++ b/src/auction.js @@ -45,6 +45,7 @@ * @property {refererInfo} refererInfo - referer info object * @property {string} [tid] - random UUID (used for s2s) * @property {string} [src] - s2s or client (used for s2s) + * @property {import('./types/ortb2.js').Ortb2.BidRequest} [ortb2] Global (not specific to any adUnit) first party data to use for all requests in this auction. */ /** diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 911509455e0..49e2f7d7cad 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -114,13 +114,19 @@ const ENRICHMENTS = { const w = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; const h = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; - return { + const device = { w, h, dnt: getDNT() ? 1 : 0, ua: win.navigator.userAgent, language: win.navigator.language.split('-').shift(), }; + + if (win.navigator?.webdriver) { + deepSetValue(device, 'ext.webdriver', true); + } + + return device; }) }, regs() { diff --git a/src/types/ortb2.d.ts b/src/types/ortb2.d.ts new file mode 100644 index 00000000000..f38545c0c31 --- /dev/null +++ b/src/types/ortb2.d.ts @@ -0,0 +1,59 @@ +/** + * @see https://iabtechlab.com/standards/openrtb/ + */ +export namespace Ortb2 { + type Site = { + page?: string; + ref?: string; + domain?: string; + publisher?: { + domain?: string; + }; + keywords?: string; + ext?: Record; + }; + + type Device = { + w?: number; + h?: number; + dnt?: 0 | 1; + ua?: string; + language?: string; + sua?: { + source?: number; + platform?: unknown; + browsers?: unknown[]; + mobile?: number; + }; + ext?: { + webdriver?: true; + [key: string]: unknown; + }; + }; + + type Regs = { + coppa?: unknown; + ext?: { + gdpr?: unknown; + us_privacy?: unknown; + [key: string]: unknown; + }; + }; + + type User = { + ext?: Record; + }; + + /** + * Ortb2 info provided in bidder request. Some of the sections are mutually exclusive. + * @see clientSectionChecker + */ + type BidRequest = { + device?: Device; + regs?: Regs; + user?: User; + site?: Site; + app?: unknown; + dooh?: unknown; + }; +} diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js index 40692360dca..80ee0dd6cd2 100644 --- a/test/spec/fpd/enrichment_spec.js +++ b/test/spec/fpd/enrichment_spec.js @@ -186,6 +186,21 @@ describe('FPD enrichment', () => { }); }); + describe('ext.webdriver', () => { + it('when navigator.webdriver is available', () => { + win.navigator.webdriver = true; + return fpd().then(ortb2 => { + expect(ortb2.device.ext?.webdriver).to.eql(true); + }); + }); + + it('when navigator.webdriver is not present', () => { + return fpd().then(ortb2 => { + expect(ortb2.device.ext?.webdriver).to.not.exist; + }); + }); + }); + it('sets ua', () => { win.navigator.userAgent = 'mock-ua'; return fpd().then(ortb2 => { @@ -362,7 +377,7 @@ describe('FPD enrichment', () => { setup(); cdep = Promise.resolve('example-test-label'); return fpd().then(ortb2 => { - expect(ortb2.device.ext).to.not.exist; + expect(ortb2.device.ext?.cdep).to.not.exist; if (navigator.cookieDeprecationLabel) { sinon.assert.notCalled(navigator.cookieDeprecationLabel.getValue); } @@ -373,7 +388,7 @@ describe('FPD enrichment', () => { it('if the navigator API returns a promise that rejects, the enrichment does not halt forever', () => { cdep = Promise.reject(new Error('oops, something went wrong')); return fpd().then(ortb2 => { - expect(ortb2.device.ext).to.not.exist; + expect(ortb2.device.ext?.cdep).to.not.exist; }) }); }); diff --git a/test/spec/modules/yandexBidAdapter_spec.js b/test/spec/modules/yandexBidAdapter_spec.js index c5f088a2306..140be4121ec 100644 --- a/test/spec/modules/yandexBidAdapter_spec.js +++ b/test/spec/modules/yandexBidAdapter_spec.js @@ -1,8 +1,8 @@ import { assert, expect } from 'chai'; -import { spec, NATIVE_ASSETS } from 'modules/yandexBidAdapter.js'; +import { NATIVE_ASSETS, spec } from 'modules/yandexBidAdapter.js'; import * as utils from 'src/utils.js'; -import { BANNER, NATIVE } from '../../../src/mediaTypes'; import { config } from '../../../src/config'; +import { BANNER, NATIVE } from '../../../src/mediaTypes'; describe('Yandex adapter', function () { describe('isBidRequestValid', function () { @@ -41,11 +41,45 @@ describe('Yandex adapter', function () { }); describe('buildRequests', function () { + /** @type {import('../../../src/auction').BidderRequest} */ const bidderRequest = { - refererInfo: { - domain: 'ya.ru', - ref: 'https://ya.ru/', - page: 'https://ya.ru/', + ortb2: { + site: { + domain: 'ya.ru', + ref: 'https://ya.ru/', + page: 'https://ya.ru/', + publisher: { + domain: 'ya.ru', + }, + }, + device: { + w: 1600, + h: 900, + dnt: 0, + ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + language: 'en', + sua: { + source: 1, + platform: { + brand: 'macOS', + }, + browsers: [ + { + brand: 'Not_A Brand', + version: ['8'], + }, + { + brand: 'Chromium', + version: ['120'], + }, + { + brand: 'Google Chrome', + version: ['120'], + }, + ], + mobile: 0, + }, + }, }, gdprConsent: { gdprApplies: 1, @@ -107,12 +141,10 @@ describe('Yandex adapter', function () { }); it('should send eids and ortb2 user data if defined', function() { - const bidRequestExtra = { - userIdAsEids: [{ - source: 'sharedid.org', - uids: [{ id: '01', atype: 1 }], - }], + const bidderRequestWithUserData = { + ...bidderRequest, ortb2: { + ...bidderRequest.ortb2, user: { data: [ { @@ -127,17 +159,24 @@ describe('Yandex adapter', function () { }, ], }, - }, + } + }; + const bidRequestExtra = { + userIdAsEids: [{ + source: 'sharedid.org', + uids: [{ id: '01', atype: 1 }], + }], }; + const expected = { ext: { eids: bidRequestExtra.userIdAsEids, }, - data: bidRequestExtra.ortb2.user.data, + data: bidderRequestWithUserData.ortb2.user.data, }; const bannerRequest = getBidRequest(bidRequestExtra); - const requests = spec.buildRequests([bannerRequest], bidderRequest); + const requests = spec.buildRequests([bannerRequest], bidderRequestWithUserData); expect(requests).to.have.lengthOf(1); const request = requests[0]; @@ -149,6 +188,16 @@ describe('Yandex adapter', function () { expect(data.user).to.deep.equal(expected); }); + it('should send site', function() { + const expected = { + site: bidderRequest.ortb2.site + }; + + const requests = spec.buildRequests([getBidRequest()], bidderRequest); + + expect(requests[0].data.site).to.deep.equal(expected.site); + }); + describe('banner', () => { it('should create valid banner object', () => { const bannerRequest = getBidRequest({ @@ -496,41 +545,46 @@ describe('Yandex adapter', function () { }); it('Should not trigger pixel if bid does not contain nurl', function() { - const result = spec.onBidWon({}); + spec.onBidWon({}); + expect(utils.triggerPixel.callCount).to.equal(0) }) it('Should trigger pixel if bid has nurl', function() { - const result = spec.onBidWon({ + spec.onBidWon({ nurl: 'https://example.com/some-tracker', timeToRespond: 378, }); + expect(utils.triggerPixel.callCount).to.equal(1) expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker?rtt=378') }) it('Should trigger pixel if bid has nurl with path & params', function() { - const result = spec.onBidWon({ + spec.onBidWon({ nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2', timeToRespond: 378, }); + expect(utils.triggerPixel.callCount).to.equal(1) expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&rtt=378') }) it('Should trigger pixel if bid has nurl with path & params and rtt macros', function() { - const result = spec.onBidWon({ + spec.onBidWon({ nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=${RTT}', timeToRespond: 378, }); + expect(utils.triggerPixel.callCount).to.equal(1) expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=378') }) it('Should trigger pixel if bid has nurl and there is no timeToRespond param, but has rtt macros in nurl', function() { - const result = spec.onBidWon({ + spec.onBidWon({ nurl: 'https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=${RTT}', }); + expect(utils.triggerPixel.callCount).to.equal(1) expect(utils.triggerPixel.getCall(0).args[0]).to.equal('https://example.com/some-tracker/abcdxyz?param1=1¶m2=2&custom-rtt=-1') }) From 98162dc9d9b5318ea7042e4a23c5ef1d7b8b4c27 Mon Sep 17 00:00:00 2001 From: naru-tsujine <159880063+naru-tsujine@users.noreply.github.com> Date: Sat, 24 Feb 2024 13:55:55 +0900 Subject: [PATCH 497/760] Logicad Bid Adapter: Add paapi support (#11123) * Logicad Bid Adapter: Add paapi support * Logicad Bid Adapter: fix * Logicad Bid Adapter: fix test --- modules/logicadBidAdapter.js | 50 +++++++++++----- test/spec/modules/logicadBidAdapter_spec.js | 63 ++++++++++++++++++++- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 07f9b893887..fe4dd83c9e2 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -31,13 +31,25 @@ export const spec = { }, interpretResponse: function (serverResponse, bidderRequest) { serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { return bids; } + serverResponse.seatbid.forEach(function (seatbid) { bids.push(seatbid.bid); }) + + const fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.fledgeAuctionConfigs') || []; + if (fledgeAuctionConfigs.length) { + return { + bids, + fledgeAuctionConfigs, + }; + } + return bids; }, getUserSyncs: function (syncOptions, serverResponses) { @@ -52,32 +64,42 @@ export const spec = { }, }; -function newBidRequest(bid, bidderRequest) { +function newBidRequest(bidRequest, bidderRequest) { + const bid = { + adUnitCode: bidRequest.adUnitCode, + bidId: bidRequest.bidId, + transactionId: bidRequest.ortb2Imp?.ext?.tid, + sizes: bidRequest.sizes, + params: bidRequest.params, + mediaTypes: bidRequest.mediaTypes, + } + + const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + if (fledgeEnabled) { + const ae = deepAccess(bidRequest, 'ortb2Imp.ext.ae'); + if (ae) { + bid.ae = ae; + } + } + const data = { // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - bidderRequestId: bid.bidderRequestId, - bids: [{ - adUnitCode: bid.adUnitCode, - bidId: bid.bidId, - transactionId: bid.ortb2Imp?.ext?.tid, - sizes: bid.sizes, - params: bid.params, - mediaTypes: bid.mediaTypes - }], + auctionId: bidRequest.auctionId, + bidderRequestId: bidRequest.bidderRequestId, + bids: [bid], prebidJsVersion: '$prebid.version$', // TODO: is 'page' the right value here? referrer: bidderRequest.refererInfo.page, auctionStartTime: bidderRequest.auctionStart, - eids: bid.userIdAsEids, + eids: bidRequest.userIdAsEids, }; - const sua = deepAccess(bid, 'ortb2.device.sua'); + const sua = deepAccess(bidRequest, 'ortb2.device.sua'); if (sua) { data.sua = sua; } - const userData = deepAccess(bid, 'ortb2.user.data'); + const userData = deepAccess(bidRequest, 'ortb2.user.data'); if (userData) { data.userData = userData; } diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 3c1383781b9..12e8ca31cbb 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -36,6 +36,11 @@ describe('LogicadAdapter', function () { } }] }], + ortb2Imp: { + ext: { + ae: 1 + } + }, ortb2: { device: { sua: { @@ -176,7 +181,8 @@ describe('LogicadAdapter', function () { numIframes: 1, stack: [] }, - auctionStart: 1563337198010 + auctionStart: 1563337198010, + fledgeEnabled: true }; const serverResponse = { body: { @@ -203,6 +209,49 @@ describe('LogicadAdapter', function () { } } }; + + const paapiServerResponse = { + body: { + seatbid: + [{ + bid: { + requestId: '51ef8751f9aead', + cpm: 101.0234, + width: 300, + height: 250, + creativeId: '2019', + currency: 'JPY', + netRevenue: true, + ttl: 60, + ad: '
TEST
', + meta: { + advertiserDomains: ['logicad.com'] + } + } + }], + ext: { + fledgeAuctionConfigs: [{ + bidId: '51ef8751f9aead', + config: { + seller: 'https://fledge.ladsp.com', + decisionLogicUrl: 'https://fledge.ladsp.com/decision_logic.js', + interestGroupBuyers: ['https://fledge.ladsp.com'], + requestedSize: {width: '300', height: '250'}, + allSlotsRequestedSizes: [{width: '300', height: '250'}], + sellerSignals: {signal: 'signal'}, + sellerTimeout: '500', + perBuyerSignals: {'https://fledge.ladsp.com': {signal: 'signal'}}, + perBuyerCurrencies: {'https://fledge.ladsp.com': 'USD'} + } + }] + }, + userSync: { + type: 'image', + url: 'https://cr-p31.ladsp.jp/cookiesender/31' + } + } + }; + const nativeServerResponse = { body: { seatbid: @@ -272,6 +321,11 @@ describe('LogicadAdapter', function () { const data = JSON.parse(request.data); expect(data.auctionId).to.equal('18fd8b8b0bd757'); + + // Protected Audience API flag + expect(data.bids[0]).to.have.property('ae'); + expect(data.bids[0].ae).to.equal(1); + expect(data.eids[0].source).to.equal('sharedid.org'); expect(data.eids[0].uids[0].id).to.equal('fakesharedid'); @@ -330,6 +384,13 @@ describe('LogicadAdapter', function () { expect(interpretedResponse[0].ttl).to.equal(serverResponse.body.seatbid[0].bid.ttl); expect(interpretedResponse[0].meta.advertiserDomains).to.equal(serverResponse.body.seatbid[0].bid.meta.advertiserDomains); + // Protected Audience API + const paapiRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const paapiInterpretedResponse = spec.interpretResponse(paapiServerResponse, paapiRequest); + expect(paapiInterpretedResponse).to.have.property('bids'); + expect(paapiInterpretedResponse).to.have.property('fledgeAuctionConfigs'); + expect(paapiInterpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); + // native const nativeRequest = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; const interpretedResponseForNative = spec.interpretResponse(nativeServerResponse, nativeRequest); From 2a3b64f0dd077efcae536abdeac3c918ca6c9b47 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Mon, 26 Feb 2024 09:04:30 -0800 Subject: [PATCH 498/760] Magnite Analytics: Check if prebid cache was called for video tracking (#10928) * add signal for client side cache * use Demetrio suggestion in magnite adapter instead * fix lint * test update * use weakset and remove once found * Demetrio knowledge transfer --- modules/magniteAnalyticsAdapter.js | 19 ++++++++-- .../modules/magniteAnalyticsAdapter_spec.js | 36 ++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index b9665b93494..3b70a51cd68 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -25,6 +25,7 @@ import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import { getHook } from '../src/hook.js'; const RUBICON_GVL_ID = 52; export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'magnite' }); @@ -75,7 +76,8 @@ const resetConfs = () => { pendingEvents: {}, eventPending: false, elementIdMap: {}, - sessionData: {} + sessionData: {}, + bidsCachedClientSide: new WeakSet() } rubiConf = { pvid: generateUUID().slice(0, 8), @@ -671,8 +673,20 @@ function enableMgniAnalytics(config = {}) { window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(() => subscribeToGamSlots()); } + + // Edge case handler for client side video caching + getHook('callPrebidCache').before(callPrebidCacheHook); }; +/* + We want to know if a bid was cached client side + And if it was we will use the actual bidId instead of the pbsBidId override in our BID_RESPONSE handler +*/ +export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAdded, videoMediaType) { + cache.bidsCachedClientSide.add(bidResponse); + fn.call(this, auctionInstance, bidResponse, afterBidAdded, videoMediaType); +} + const handleBidWon = args => { const bidWon = formatBidWon(args); addEventToQueue({ bidsWon: [bidWon] }, bidWon.renderAuctionId, 'bidWon'); @@ -687,6 +701,7 @@ magniteAdapter.disableAnalytics = function () { endpoint = undefined; accountId = undefined; resetConfs(); + getHook('callPrebidCache').getHooks({ hook: callPrebidCacheHook }).remove(); magniteAdapter.originDisableAnalytics(); }; @@ -749,7 +764,7 @@ const handleBidResponse = (args, bidStatus) => { // if pbs gave us back a bidId, we need to use it and update our bidId to PBA const pbsBidId = (args.pbsBidId == 0 ? generateUUID() : args.pbsBidId) || (args.seatBidId == 0 ? generateUUID() : args.seatBidId); - if (pbsBidId) { + if (pbsBidId && !cache.bidsCachedClientSide.has(args)) { bid.pbsBidId = pbsBidId; } } diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 0864a976d7d..0dfd6c15ba8 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -3,7 +3,8 @@ import magniteAdapter, { getHostNameFromReferer, storage, rubiConf, - detectBrowserFromUa + detectBrowserFromUa, + callPrebidCacheHook } from '../../../modules/magniteAnalyticsAdapter.js'; import CONSTANTS from 'src/constants.json'; import { config } from 'src/config.js'; @@ -1137,6 +1138,39 @@ describe('magnite analytics adapter', function () { }); }); + it('should not use pbsBidId if the bid was client side cached', function () { + // bid response + let seatBidResponse = utils.deepClone(MOCK.BID_RESPONSE); + seatBidResponse.pbsBidId = 'do-not-use-me'; + + // Run auction + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + + // mock client side cache call + callPrebidCacheHook(() => {}, {}, seatBidResponse); + + events.emit(BID_RESPONSE, seatBidResponse); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + + // emmit gpt events and bidWon + mockGpt.emitEvent(gptSlotRenderEnded0.eventName, gptSlotRenderEnded0.params); + + events.emit(BID_WON, MOCK.BID_WON); + + // tick the event delay time plus processing delay + clock.tick(rubiConf.analyticsEventDelay + rubiConf.analyticsProcessDelay); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + // Expect the ids sent to server to use the original bidId not the pbsBidId thing + expect(message.auctions[0].adUnits[0].bids[0].bidId).to.equal(MOCK.BID_RESPONSE.requestId); + expect(message.bidsWon[0].bidId).to.equal(MOCK.BID_RESPONSE.requestId); + }); + [0, '0'].forEach(pbsParam => { it(`should generate new bidId if incoming pbsBidId is ${pbsParam}`, function () { // bid response From 1ecfe8e9e9a97eaccfea5f33669516ca776575fb Mon Sep 17 00:00:00 2001 From: aleskanderl <109285067+aleskanderl@users.noreply.github.com> Date: Mon, 26 Feb 2024 20:08:52 +0200 Subject: [PATCH 499/760] Taboola Bid Adapter - support topics handling (#11139) * cookie-look-up-logic-fix-gpp-fix * Append support for topics in taboolaPrebidAdapter * test fix --------- Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Co-authored-by: Ahmad Lobany --- modules/taboolaBidAdapter.js | 5 ++++- modules/topicsFpdModule.js | 3 +++ modules/topicsFpdModule.md | 6 +++++- test/spec/modules/taboolaBidAdapter_spec.js | 24 +++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index f0f11ea113e..b0418ab9865 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -225,10 +225,13 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data) { const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); const device = {ua: navigator.userAgent}; - const user = { + let user = { buyeruid: userData.getUserId(gdprConsent, uspConsent), ext: {} }; + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.user) { + user.data = bidderRequest.ortb2.user.data; + } const regs = { coppa: 0, ext: {} diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index b5a90dacfb4..748242142c4 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -38,6 +38,9 @@ const bidderIframeList = { }, { bidder: 'onetag', iframeURL: 'https://onetag-sys.com/static/topicsapi.html' + }, { + bidder: 'taboola', + iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html' }] } diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index 89eb03fb6df..e8daded4439 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -56,6 +56,10 @@ pbjs.setConfig({ bidder: 'onetag', iframeURL: 'https://onetag-sys.com/static/topicsapi.html', expiry: 7 // Configurable expiry days + }, { + bidder: 'taboola', + iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html', + expiry: 7 // Configurable expiry days }] } .... @@ -71,4 +75,4 @@ pbjs.setConfig({ | topics.bidders | no | Array of objects | Array of topics callers with the iframe locations and other necessary informations like bidder(Bidder code) and expiry. Default Array of topics in the module itself.| | topics.bidders[].bidder | yes | string | Bidder Code of the bidder(SSP). | | topics.bidders[].iframeURL | yes | string | URL which is hosted on bidder/SSP/third-party domains which will call Topics API. | -| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | \ No newline at end of file +| topics.bidders[].expiry | no | integer | Max number of days where Topics data will be persist. If Data is stored for more than mentioned expiry day, it will be deleted from storage. Default is 21 days which is hardcoded in Module. | diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 8a121865cf2..39df2eb4a99 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -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 () { From 5b6dd8cfdd3fb2327d9123d1b2bff70810f842a1 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 27 Feb 2024 00:12:18 +0100 Subject: [PATCH 500/760] Adagio Bid Adapter: fix ortb delivery video param validation (#11144) --- modules/adagioBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) }; From 9fdc06a4840f6d1019c2e3087ecdd1e627975d86 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Mon, 26 Feb 2024 16:42:37 -0700 Subject: [PATCH 501/760] JsDoc Lint Fix : multiple adapters and modules (#11103) * update jsdoc * add typedef * update typedef * fix typo * update jsdoc objectguard * fix colon issue * add typdef * fix rtdmodule doc * fix a few adapters * fix bid adapters * fix prisma * remove array syntax * fix adot * update jsdoc * update jsdoc colon * fix errors * fix params * fix jsdoc * add typedef * add typedef and fix * fix errors * import types * fix jsdoc warnings * fix warnings * add typedef * jsdoc fixes * jsdoc fixes * fix warnings * fix warnings --- libraries/appnexusUtils/anKeywords.js | 2 +- libraries/keywords/keywords.js | 2 +- libraries/objectGuard/objectGuard.js | 2 ++ libraries/objectGuard/ortbGuard.js | 4 ++++ libraries/video/shared/parentModule.js | 1 + modules/adotBidAdapter.js | 17 +++++++++++++++++ modules/adriverIdSystem.js | 1 - modules/adtelligentBidAdapter.js | 3 ++- modules/adyoulikeBidAdapter.js | 4 +++- modules/airgridRtdProvider.js | 2 +- modules/akamaiDapRtdProvider.js | 9 ++++++--- modules/apacdexBidAdapter.js | 2 +- modules/appierBidAdapter.js | 2 +- modules/astraoneBidAdapter.js | 3 ++- modules/beopBidAdapter.js | 12 +++++++++--- modules/betweenBidAdapter.js | 3 ++- modules/bidglassBidAdapter.js | 5 ++++- modules/bliinkBidAdapter.js | 3 +-- modules/blueconicRtdProvider.js | 5 ++--- modules/brandmetricsRtdProvider.js | 2 ++ modules/britepoolIdSystem.js | 2 +- modules/browsiRtdProvider.js | 1 - modules/buzzoolaBidAdapter.js | 1 - modules/codefuelBidAdapter.js | 3 ++- modules/dsp_genieeBidAdapter.js | 16 +++++++++++++--- modules/dspxBidAdapter.js | 4 ++-- modules/dxkultureBidAdapter.js | 6 +++--- modules/dynamicAdBoostRtdProvider.js | 4 ++++ modules/feedadBidAdapter.js | 2 +- modules/flippBidAdapter.js | 4 +++- modules/fluctBidAdapter.js | 5 +++-- modules/freewheel-sspBidAdapter.js | 2 +- modules/geoedgeRtdProvider.js | 2 +- modules/getintentBidAdapter.js | 4 ++-- modules/gjirafaBidAdapter.js | 4 +++- modules/gmosspBidAdapter.js | 4 +++- modules/gnetBidAdapter.js | 4 +++- modules/growthCodeIdSystem.js | 6 ++++++ modules/pilotxBidAdapter.js | 4 ++-- modules/prismaBidAdapter.js | 4 ++-- modules/pubmaticBidAdapter.js | 2 +- modules/pubwiseBidAdapter.js | 2 +- modules/rtdModule/index.js | 15 ++++++++------- modules/smilewantedBidAdapter.js | 4 ++++ modules/yieldmoBidAdapter.js | 17 +++++++++-------- modules/zetaBidAdapter.js | 2 ++ modules/zeta_global_sspBidAdapter.js | 2 ++ modules/zmaticooBidAdapter.js | 8 ++++++++ test/fake-server/fake-responder.js | 2 +- test/spec/auctionmanager_spec.js | 5 +++++ test/spec/modules/cointrafficBidAdapter_spec.js | 5 +++++ test/spec/modules/rubiconBidAdapter_spec.js | 1 + test/spec/modules/sovrnAnalyticsAdapter_spec.js | 4 ++-- 53 files changed, 166 insertions(+), 69 deletions(-) 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/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/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/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/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/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/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/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/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/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/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index d25627a7b90..68431bcc383 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1070,7 +1070,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) => { 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/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/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/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 4082ed996fe..5fda0b751e7 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -21,6 +21,7 @@ import {find, includes} from '../src/polyfill.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').ServerRequest} ServerRequest */ @@ -52,8 +53,8 @@ export const spec = { gvlid: GVLID, /** * Determines whether or not the given bid request is valid. - * @param {object} bid, bid to validate - * @return boolean, true if valid, otherwise false + * @param {object} bid bid to validate + * @return {boolean} true if valid, otherwise false */ isBidRequestValid: function (bid) { return !!(bid && bid.adUnitCode && bid.bidId && (hasBannerMediaType(bid) || hasVideoMediaType(bid)) && @@ -583,8 +584,8 @@ function populateOpenRtbGdpr(openRtbRequest, bidderRequest) { /** * Determines whether or not the given video bid request is valid. If it's not a video bid, returns true. - * @param {object} bid, bid to validate - * @return boolean, true if valid, otherwise false + * @param {object} bid bid to validate + * @return {boolean} true if valid, otherwise false */ function validateVideoParams(bid) { if (!hasVideoMediaType(bid)) { @@ -689,9 +690,9 @@ function validateVideoParams(bid) { /** * Shortcut object property and check if required characters count was deleted * - * @param {number} extraCharacters, count of characters to remove - * @param {object} target, object on which string property length should be reduced - * @param {string} propertyName, name of property to reduce + * @param {number} extraCharacters count of characters to remove + * @param {object} target object on which string property length should be reduced + * @param {string} propertyName name of property to reduce * @return {number} 0 if required characters count was removed otherwise count of how many left */ function shortcutProperty(extraCharacters, target, propertyName) { @@ -710,7 +711,7 @@ function shortcutProperty(extraCharacters, target, propertyName) { /** * Creates and returnes eids arr using createEidsArray from './userId/eids.js' module; - * @param {Object} openRtbRequest OpenRTB's request as a cource of userId. + * @param {Object} bidRequest OpenRTB's request as a cource of userId. * @return array of eids objects */ function getEids(bidRequest) { diff --git a/modules/zetaBidAdapter.js b/modules/zetaBidAdapter.js index cc77d53d30d..658d3198df0 100644 --- a/modules/zetaBidAdapter.js +++ b/modules/zetaBidAdapter.js @@ -5,6 +5,8 @@ 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').Bids} Bids + * @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 diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index af73865c484..490d49ec63e 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -9,6 +9,8 @@ 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').ServerResponse} ServerResponse + * @typedef {import('../src/adapters/bidderFactory.js').Bids} Bids + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ const BIDDER_CODE = 'zeta_global_ssp'; diff --git a/modules/zmaticooBidAdapter.js b/modules/zmaticooBidAdapter.js index f392b74f672..905da191ab7 100644 --- a/modules/zmaticooBidAdapter.js +++ b/modules/zmaticooBidAdapter.js @@ -2,6 +2,14 @@ import {deepAccess, isArray, isBoolean, isNumber, isStr, logWarn, triggerPixel} import {registerBidder} from '../src/adapters/bidderFactory.js'; 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').Bids} Bids + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + const BIDDER_CODE = 'zmaticoo'; const ENDPOINT_URL = 'https://bid.zmaticoo.com/prebid/bid'; const DEFAULT_CUR = 'USD'; diff --git a/test/fake-server/fake-responder.js b/test/fake-server/fake-responder.js index a44d02260e7..13bf3bc816f 100644 --- a/test/fake-server/fake-responder.js +++ b/test/fake-server/fake-responder.js @@ -9,7 +9,7 @@ const fixturesPath = path.join(__dirname, 'fixtures'); /** * Matches 'req.body' with the responseBody pair * @param {object} requestBody - `req.body` of incoming request hitting middleware 'fakeResponder'. - * @returns {objct} responseBody + * @returns {object} responseBody */ const matchResponse = function (requestBody) { let actualUuids = []; diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index c78a5e51f17..06d3d538596 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -25,6 +25,11 @@ import {deepClone} from '../../src/utils.js'; import { IMAGE as ortbNativeRequest } from 'src/native.js'; import {PrebidServer} from '../../modules/prebidServerBidAdapter/index.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + var assert = require('assert'); /* use this method to test individual files instead of the whole prebid.js project */ 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/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 184bea07d04..09418caef53 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 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) { From c794435db56bead2fb5b60f656f6b6793e76b6b2 Mon Sep 17 00:00:00 2001 From: nalexand <35492736+nalexand@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:07:04 +0100 Subject: [PATCH 502/760] Mediaimpact Bid Adapter: initial release (#11099) * Add mediaimpact bid adapter * Add mediaimpact bid adapter tests --------- Co-authored-by: koshe --- modules/mediaimpactBidAdapter.js | 211 +++++++++++ modules/mediaimpactBidAdapter.md | 44 +++ .../modules/mediaimpactBidAdapter_spec.js | 336 ++++++++++++++++++ 3 files changed, 591 insertions(+) create mode 100644 modules/mediaimpactBidAdapter.js create mode 100644 modules/mediaimpactBidAdapter.md create mode 100644 test/spec/modules/mediaimpactBidAdapter_spec.js 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/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'); + }); + }); +}); From 37d368ef312707257627ca24fe2edc76ff20050f Mon Sep 17 00:00:00 2001 From: Aymeric Le Corre Date: Wed, 28 Feb 2024 15:47:00 +0100 Subject: [PATCH 503/760] Lucead Adapter: update (#11143) * Lucead Adapter: update * update endpoint url * update tests --- modules/luceadBidAdapter.js | 45 ++++++++++++++-------- modules/luceadBidAdapter.md | 2 + test/spec/modules/luceadBidAdapter_spec.js | 8 +++- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index 8958e8f3786..299bd47a8e4 100644 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -1,17 +1,20 @@ 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'; +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'); + +function isDevEnv() { + return location.hostname.endsWith('.ngrok-free.app') || location.href.startsWith('https://ayads.io/test'); +} function isBidRequestValid(bidRequest) { return !!bidRequest?.params?.placementId; @@ -21,16 +24,16 @@ export function log(msg, obj) { logInfo('Lucead - ' + 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 +42,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 +85,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 +124,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) { @@ -141,6 +155,7 @@ export const spec = { interpretResponse, onBidWon, onTimeout, + isDevEnv, }; // noinspection JSCheckFunctionSignatures diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md index 45fd3ec5301..d12d081f0b7 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 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(); From 666960925fbed3b5aeef035a63e8b5062897aa13 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:56:24 +0100 Subject: [PATCH 504/760] ZetaGlobalSsp Bid Adapter: provide dspId into bid (#11150) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 4 ++++ test/spec/modules/zeta_global_sspBidAdapter_spec.js | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 490d49ec63e..aa35066e26b 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -202,6 +202,7 @@ export const spec = { const response = (serverResponse || {}).body; if (response && response.seatbid && response.seatbid[0].bid && response.seatbid[0].bid.length) { response.seatbid.forEach(zetaSeatbid => { + const seat = zetaSeatbid.seat; zetaSeatbid.bid.forEach(zetaBid => { let bid = { requestId: zetaBid.impid, @@ -223,6 +224,9 @@ export const spec = { if (bid.mediaType === VIDEO) { bid.vastXml = bid.ad; } + if (seat) { + bid.dspId = seat; + } bidResponses.push(bid); }) }) diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 81617b93d3c..7beac2f820c 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -209,6 +209,7 @@ describe('Zeta Ssp Bid Adapter', function () { id: '12345', seatbid: [ { + seat: '1', bid: [ { id: 'auctionId', @@ -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 () { From 72e2ee6e595d3063877c2beb57f9052496e7e54f Mon Sep 17 00:00:00 2001 From: carsten1980 <45483737+carsten1980@users.noreply.github.com> Date: Wed, 28 Feb 2024 23:52:44 +0100 Subject: [PATCH 505/760] Adspirit Bid Adapter: initial release (#10939) * Add files via upload * Add files via upload * Update adspiritBidAdapter.js updated version with testcases * Update adspiritBidAdapter.md updated version add gdpr und privacy polices * testcases for adspirit adapter 1/2024 Added all the necessary test cases * Update adspiritBidAdapter_spec.js kicking off unit tests * kick of circleci * Update adspiritBidAdapter.js Bid Response is updated to outside of the condition with the shared values and here conditions only set the data that's specific to that use case * Update adspiritBidAdapter.md kicking off circleci ? --------- Co-authored-by: Patrick McCann Co-authored-by: Chris Huie --- modules/adspiritBidAdapter.js | 124 ++++++++ modules/adspiritBidAdapter.md | 66 +++++ test/spec/modules/adspiritBidAdapter_spec.js | 294 +++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 modules/adspiritBidAdapter.js create mode 100644 modules/adspiritBidAdapter.md create mode 100644 test/spec/modules/adspiritBidAdapter_spec.js 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/test/spec/modules/adspiritBidAdapter_spec.js b/test/spec/modules/adspiritBidAdapter_spec.js new file mode 100644 index 00000000000..0454cd6a39f --- /dev/null +++ b/test/spec/modules/adspiritBidAdapter_spec.js @@ -0,0 +1,294 @@ +import { expect } from 'chai'; +import { spec } from 'modules/adspiritBidAdapter.js'; +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'; + +describe('Adspirit Bidder Spec', function () { + + // isBidRequestValid ---case + describe('isBidRequestValid', function () { + it('should return true if the bid request is valid', function () { + const validBid = { bidder: 'adspirit', params: { placementId: '57', host: 'test.adspirit.de' } }; + const result = spec.isBidRequestValid(validBid); + expect(result).to.be.true; + }); + + it('should return false if the bid request is invalid', function () { + const invalidBid = { bidder: 'adspirit', params: {} }; + const result = spec.isBidRequestValid(invalidBid); + expect(result).to.be.false; + }); + }); + + // getBidderHost Case + describe('getBidderHost', function () { + it('should return host for adspirit bidder', function () { + const bid = { bidder: 'adspirit', params: { host: 'test.adspirit.de' } }; + const result = spec.getBidderHost(bid); + expect(result).to.equal('test.adspirit.de'); + }); + + it('should return host for twiago bidder', function () { + const bid = { bidder: 'twiago' }; + const result = spec.getBidderHost(bid); + expect(result).to.equal('a.twiago.com'); + }); + it('should return null for unsupported bidder', function () { + const bid = { bidder: 'unsupportedBidder', params: {} }; + const result = spec.getBidderHost(bid); + expect(result).to.be.null; + }); + }); + + // Test cases for buildRequests + describe('buildRequests', function () { + const bidRequestWithGDPRAndSchain = [ + { + id: '26c1ee0038ac11', + bidder: 'adspirit', + params: { + placementId: '57' + }, + schain: { + ver: '1.0', + nodes: [ + { + asi: 'exchange1.com', + sid: '1234', + hp: 1, + rid: 'bidRequest123', + name: 'Publisher', + domain: 'publisher.com' + }, + { + asi: 'network1.com', + sid: '5678', + hp: 1, + rid: 'bidderRequest123', + name: 'Network', + domain: 'network1.com' + } + ] + } + } + ]; + + const mockBidderRequestWithGDPR = { + refererInfo: { + topmostLocation: 'test.adspirit.de' + }, + gdprConsent: { + gdprApplies: true, + consentString: 'consentString' + }, + schain: { + ver: '1.0', + nodes: [ + { + asi: 'network1.com', + sid: '5678', + hp: 1, + rid: 'bidderRequest123', + name: 'Network', + domain: 'network1.com' + } + ] + } + }; + + it('should construct valid bid requests with GDPR consent and schain', function () { + const requests = spec.buildRequests(bidRequestWithGDPRAndSchain, mockBidderRequestWithGDPR); + expect(requests).to.be.an('array').that.is.not.empty; + const request = requests[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.include('test.adspirit.de'); + expect(request.url).to.include('pid=57'); + expect(request.data).to.have.property('schain'); + expect(request.data.schain).to.be.an('object'); + if (request.data.schain && Array.isArray(request.data.schain.nodes)) { + const nodeWithGdpr = request.data.schain.nodes.find(node => node.gdpr); + if (nodeWithGdpr) { + expect(nodeWithGdpr).to.have.property('gdpr'); + expect(nodeWithGdpr.gdpr).to.be.an('object'); + expect(nodeWithGdpr.gdpr).to.have.property('applies', true); + expect(nodeWithGdpr.gdpr).to.have.property('consent', 'consentString'); + } + } + }); + + it('should construct valid bid requests without GDPR consent and schain', function () { + const bidRequestWithoutGDPR = [ + { + id: '26c1ee0038ac11', + bidder: 'adspirit', + params: { + placementId: '57' + } + } + ]; + + const mockBidderRequestWithoutGDPR = { + refererInfo: { + topmostLocation: 'test.adspirit.de' + } + }; + + const requests = spec.buildRequests(bidRequestWithoutGDPR, mockBidderRequestWithoutGDPR); + expect(requests).to.be.an('array').that.is.not.empty; + const request = requests[0]; + expect(request.method).to.equal('GET'); + expect(request.url).to.include('test.adspirit.de'); + expect(request.url).to.include('pid=57'); + expect(request.data).to.deep.equal({}); + }); + }); + + // interpretResponse For Native + describe('interpretResponse', function () { + const nativeBidRequestMock = { + bidRequest: { + bidId: '123456', + params: { + placementId: '57', + adomain: ['test.adspirit.de'] + }, + mediaTypes: { + native: true + } + } + }; + + it('should handle native media type bids and missing cpm in the server response body', function () { + const serverResponse = { + body: { + w: 320, + h: 50, + title: 'Ad Title', + body: 'Ad Body', + cta: 'Click Here', + image: 'img_url', + click: 'click_url', + view: 'view_tracker_url' + } + }; + + const result = spec.interpretResponse(serverResponse, nativeBidRequestMock); + expect(result.length).to.equal(0); + }); + + it('should handle native media type bids', function () { + const serverResponse = { + body: { + cpm: 1.0, + w: 320, + h: 50, + title: 'Ad Title', + body: 'Ad Body', + cta: 'Click Here', + image: 'img_url', + click: 'click_url', + view: 'view_tracker_url' + } + }; + + const result = spec.interpretResponse(serverResponse, nativeBidRequestMock); + expect(result.length).to.equal(1); + const bid = result[0]; + expect(bid).to.include({ + requestId: '123456', + cpm: 1.0, + width: 320, + height: 50, + creativeId: '57', + currency: 'EUR', + netRevenue: true, + ttl: 300, + mediaType: 'native' + }); + expect(bid.native).to.deep.include({ + title: 'Ad Title', + body: 'Ad Body', + cta: 'Click Here', + image: { url: 'img_url' }, + clickUrl: 'click_url', + impressionTrackers: ['view_tracker_url'] + }); + }); + + + const bannerBidRequestMock = { + bidRequest: { + bidId: '123456', + params: { + placementId: '57', + adomain: ['siva.adspirit.de'] + }, + mediaTypes: { + banner: true + } + } + }; + + // Test cases for various scenarios + it('should return empty array when serverResponse is missing', function () { + const result = spec.interpretResponse(null, { bidRequest: {} }); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return empty array when serverResponse.body is missing', function () { + const result = spec.interpretResponse({}, { bidRequest: {} }); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return empty array when bidObj is missing', function () { + const result = spec.interpretResponse({ body: { cpm: 1.0 } }, { bidRequest: null }); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should return empty array when all required parameters are missing', function () { + const result = spec.interpretResponse(null, { bidRequest: null }); + expect(result).to.be.an('array').that.is.empty; + }); + + it('should handle banner media type bids and missing cpm in the server response body', function () { + const serverResponseBanner = { + body: { + w: 728, + h: 90, + adm: '
Ad Content
' + } + }; + const result = spec.interpretResponse(serverResponseBanner, bannerBidRequestMock); + expect(result.length).to.equal(0); + }); + + it('should handle banner media type bids', function () { + const serverResponse = { + body: { + cpm: 2.0, + w: 728, + h: 90, + adm: '
Ad Content
' + } + }; + const result = spec.interpretResponse(serverResponse, bannerBidRequestMock); + expect(result.length).to.equal(1); + const bid = result[0]; + expect(bid).to.include({ + requestId: '123456', + cpm: 2.0, + width: 728, + height: 90, + creativeId: '57', + currency: 'EUR', + netRevenue: true, + ttl: 300, + mediaType: 'banner' + }); + expect(bid.ad).to.equal('
Ad Content
'); + }); + }); +}); From b4d9197a73f9c1cfde4cdc4126b79def9cc4b2d6 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 29 Feb 2024 05:58:23 -0800 Subject: [PATCH 506/760] fix hadron ID module name (#11151) --- modules/hadronIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 6184940b954378d4fcbab99584604f9181e7ddc3 Mon Sep 17 00:00:00 2001 From: dzhang-criteo <87757739+dzhang-criteo@users.noreply.github.com> Date: Thu, 29 Feb 2024 15:00:53 +0100 Subject: [PATCH 507/760] Criteo bid adapter: raise Fledge timeout (#11152) Raise from 50ms to 500ms --- modules/criteoBidAdapter.js | 2 +- test/spec/modules/criteoBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 0618a076193..33eb903ab55 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -298,7 +298,7 @@ export const spec = { if (!sellerSignals.floor && bidRequest.params.bidFloor) { sellerSignals.floor = bidRequest.params.bidFloor; } - let perBuyerTimeout = { '*': 50 }; + let perBuyerTimeout = { '*': 500 }; if (sellerSignals.perBuyerTimeout) { for (const buyer in sellerSignals.perBuyerTimeout) { perBuyerTimeout[buyer] = sellerSignals.perBuyerTimeout[buyer]; diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 1139dbf5210..726754f39aa 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2644,7 +2644,7 @@ describe('The Criteo bidding adapter', function () { }, }, perBuyerTimeout: { - '*': 50, + '*': 500, 'buyer1': 100, 'buyer2': 200 }, @@ -2687,7 +2687,7 @@ describe('The Criteo bidding adapter', function () { }, }, perBuyerTimeout: { - '*': 50, + '*': 500, 'buyer1': 100, 'buyer2': 200 }, From e97e700692ec66f643bdbc6455107a606c90a899 Mon Sep 17 00:00:00 2001 From: lasloche <62240785+lasloche@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:09:24 +0100 Subject: [PATCH 508/760] Stn Bid Adapter: initial release (#11085) * stnBidAdapter: initial release * update endpoints * update stnBidAdapter * update the test mode params --- modules/stnBidAdapter.js | 488 ++++++++++++++++++ modules/stnBidAdapter.md | 77 +++ test/spec/modules/stnBidAdapter_spec.js | 625 ++++++++++++++++++++++++ 3 files changed, 1190 insertions(+) create mode 100644 modules/stnBidAdapter.js create mode 100644 modules/stnBidAdapter.md create mode 100644 test/spec/modules/stnBidAdapter_spec.js diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js new file mode 100644 index 00000000000..633e941b3b7 --- /dev/null +++ b/modules/stnBidAdapter.js @@ -0,0 +1,488 @@ +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + timestamp, + triggerPixel, + isInteger, + 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'; + +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BIDDER_CODE = 'stn'; +const ADAPTER_VERSION = '6.0.0'; +const TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.stngo.com/'; +const MODES = { + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} + +export const spec = { + code: BIDDER_CODE, + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to STN adapter'); + return false; + } + + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for STN adapter'); + return false; + } + + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; + + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; + + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject + } + }, + interpretResponse: function ({body}) { + const bidResponses = []; + + if (body.bids) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || DEFAULT_CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + 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; + } + + bidResponses.push(bidResponse); + }); + } + + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { + syncs.push({ + type: 'iframe', + url: deepAccess(response, 'body.params.userSyncURL') + }); + } + if (syncOptions.pixelEnabled && isArray(deepAccess(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); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } + } +}; + +registerBidder(spec); + +/** + * Get floor price + * @param bid {bid} + * @param mediaType {String} + * @param currency {String} + * @returns {Number} + */ +function getFloor(bid, mediaType, currency) { + 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 ad sizes array from the bid + * @param bid {bid} + * @param mediaType {String} + * @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 += `${getEncodedValIfNotEmpty(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 (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; +} + +/** + * Get preferred user-sync method based on publisher configuration + * @param filterSettings {Object} + * @param bidderCode {string} + * @returns {string} + */ +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 the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * 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 generateBidsParams(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); + const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + currency: currency, + floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', + coppa: 0, + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`) + if (coppa) { + bidObject.coppa = 1; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } + + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; + } + } + + return bidObject; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +/** + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object + */ +function generateGeneralParams(generalObject, bidderRequest) { + const domain = window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode} = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = bidderRequest.timeout; + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + auction_start: timestamp(), + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), + tmax: timeout + } + + 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 (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + return generalParams +} diff --git a/modules/stnBidAdapter.md b/modules/stnBidAdapter.md new file mode 100644 index 00000000000..46374c5a53d --- /dev/null +++ b/modules/stnBidAdapter.md @@ -0,0 +1,77 @@ +#Overview + +Module Name: STN Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: hb@stnvideo.com + + +# Description + +Module that connects to STN's demand sources. + +The STN adapter requires setup and approval from the STN. Please reach out to hb@stnvideo.com to create an STN account. + +The adapter supports Video(instream) & Banner. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- |-------------------------------------------------------------------| ------- +| `org` | required | String | STN publisher Id provided by your STN representative | "STN_0000013" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `placementId` | optional | String | A unique placement identifier | "12345678" +| `testMode` | optional | Boolean | This activates the test mode | true +| `currency` | optional | String | 3 letters currency | "EUR" + +# Test Parameters +```javascript +var adUnits = [{ + code: 'dfp-video-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } + }, + bids: [{ + bidder: 'stn', + params: { + org: 'STN_0000013', // Required + floorPrice: 2.00, // Optional + placementId: 'video-test', // Optional + testMode: true // Optional + } + }] + }, + { + code: 'dfp-banner-div', + sizes: [ + [640, 480] + ], + mediaTypes: { + banner: { + sizes: [ + [640, 480] + ] + } + }, + bids: [{ + bidder: 'stn', + params: { + org: 'STN_0000013', // Required + floorPrice: 2.00, // Optional + placementId: 'banner-test', // Optional + testMode: true // Optional + } + }] + } +]; +``` 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) + }) + }) +}); From 9427cd3b52f6fee7aeff1b108dd80b5c795cd945 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:08:27 +0530 Subject: [PATCH 509/760] Fixed use of adUnitId for analytics purpose (#11160) Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index ad2a06ea86d..66593a9d72b 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -391,7 +391,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,6 +450,7 @@ 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; @@ -466,7 +467,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); From 0a0343bb15f7f77c5cc912e73b1c0b3b9aa1590d Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:20:14 -0500 Subject: [PATCH 510/760] adspiritBidAdapter - fix lint errors in unit test file (#11163) --- test/spec/modules/adspiritBidAdapter_spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/spec/modules/adspiritBidAdapter_spec.js b/test/spec/modules/adspiritBidAdapter_spec.js index 0454cd6a39f..022a26da60e 100644 --- a/test/spec/modules/adspiritBidAdapter_spec.js +++ b/test/spec/modules/adspiritBidAdapter_spec.js @@ -6,8 +6,7 @@ import { BANNER, NATIVE } from 'src/mediaTypes.js'; const RTB_URL = '/rtb/getbid.php?rtbprovider=prebid'; const SCRIPT_URL = '/adasync.min.js'; -describe('Adspirit Bidder Spec', function () { - +describe('Adspirit Bidder Spec', function () { // isBidRequestValid ---case describe('isBidRequestValid', function () { it('should return true if the bid request is valid', function () { @@ -218,7 +217,6 @@ describe('Adspirit Bidder Spec', function () { }); }); - const bannerBidRequestMock = { bidRequest: { bidId: '123456', From 6347324f829970ac76fd33cad7932af378f928ec Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 1 Mar 2024 16:21:33 +0000 Subject: [PATCH 511/760] Prebid 8.39.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2aa39fa8b3f..b40bb29aaa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.39.0-pre", + "version": "8.39.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index e576149def1..a8ae5b9ca46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.39.0-pre", + "version": "8.39.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 9da6ef037a40df284c5623dd91b526518e9b7d46 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 1 Mar 2024 16:21:34 +0000 Subject: [PATCH 512/760] Increment version to 8.40.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b40bb29aaa8..1581edd19ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.39.0", + "version": "8.40.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index a8ae5b9ca46..c865d4520a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.39.0", + "version": "8.40.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 99247d07736e249edd5e6a5d752f9873c6b4c85b Mon Sep 17 00:00:00 2001 From: ops-co <159886704+ops-co@users.noreply.github.com> Date: Sun, 3 Mar 2024 18:15:56 +0100 Subject: [PATCH 513/760] Opsco Bid Adapter : initial release (#11112) * Opsco bid adapter init commit * Opsco bid adapter banner implementation * Changing test parameter * Changing endpoint --------- Co-authored-by: adtech-sky --- modules/opscoBidAdapter.js | 129 +++++++++++ modules/opscoBidAdapter.md | 36 +++ test/spec/modules/opscoBidAdapter_spec.js | 260 ++++++++++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 modules/opscoBidAdapter.js create mode 100644 modules/opscoBidAdapter.md create mode 100644 test/spec/modules/opscoBidAdapter_spec.js 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/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); + }); + }); +}); From eb08d22a18ad2911c03a3336700b2c3cb984185e Mon Sep 17 00:00:00 2001 From: abazylewicz-id5 <106807984+abazylewicz-id5@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:31:46 +0100 Subject: [PATCH 514/760] ID5 User Id module - expose euid as a separate eid object (#11158) --- modules/id5IdSystem.js | 32 ++++++++++++-- test/spec/modules/id5IdSystem_spec.js | 62 ++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 6 deletions(-) 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/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 () { From 94bb619b961cfec0b19bc11fb70b89f96e7b2d0c Mon Sep 17 00:00:00 2001 From: Patrick Loughrey Date: Mon, 4 Mar 2024 13:13:42 -0500 Subject: [PATCH 515/760] Triplelift Bid Adapter: Optimize EID Signals (#11168) * MPY-77: Updated EID logic to ingest as is * MPY-77: Updated EID logic to ingest as is --- modules/tripleliftBidAdapter.js | 88 +----- .../spec/modules/tripleliftBidAdapter_spec.js | 287 ++++-------------- 2 files changed, 63 insertions(+), 312 deletions(-) diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index bfbf1409c1b..fc0d73dc44b 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -1,4 +1,5 @@ -import { logMessage, logError, isEmpty, isStr, isPlainObject, isArray, logWarn } from '../src/utils.js'; +import * as utils from '../src/utils.js'; +import { logMessage, logError, isEmpty, logWarn } from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; @@ -184,17 +185,12 @@ function _buildPostBody(bidRequests, bidderRequest) { return imp; }); - let eids = [ - ...getUnifiedIdEids([bidRequests[0]]), - ...getIdentityLinkEids([bidRequests[0]]), - ...getCriteoEids([bidRequests[0]]), - ...getPubCommonEids([bidRequests[0]]), - ...getUniversalEids(bidRequests[0]) - ]; + let eids = []; - if (eids.length > 0) { + if (bidRequests[0].userIdAsEids) { + eids = utils.deepAccess(bidRequests[0], 'userIdAsEids'); data.user = { - ext: {eids} + ext: { eids } }; } @@ -363,78 +359,6 @@ function _getExt(schain, fpd) { return ext; } -function getUnifiedIdEids(bidRequest) { - return getEids(bidRequest, 'tdid', 'adserver.org', 'TDID'); -} - -function getIdentityLinkEids(bidRequest) { - return getEids(bidRequest, 'idl_env', 'liveramp.com', 'idl'); -} - -function getCriteoEids(bidRequest) { - return getEids(bidRequest, 'criteoId', 'criteo.com', 'criteoId'); -} - -function getPubCommonEids(bidRequest) { - return getEids(bidRequest, 'pubcid', 'pubcid.org', 'pubcid'); -} - -function getUniversalEids(bidRequest) { - let common = ['adserver.org', 'liveramp.com', 'criteo.com', 'pubcid.org']; - let eids = []; - if (bidRequest.userIdAsEids) { - bidRequest.userIdAsEids.forEach(id => { - try { - if (common.indexOf(id.source) === -1) { - let uids = id.uids.map(uid => ({ id: uid.id, ext: { rtiPartner: id.source } })); - eids.push({ source: id.source, uids }); - } - } catch (err) { - logWarn(`Triplelift: Error attempting to add ${id} to bid request`, err); - } - }); - } - return eids; -} - -function getEids(bidRequest, type, source, rtiPartner) { - return bidRequest - .map(getUserId(type)) // bids -> userIds of a certain type - .filter(filterEids(type)) // filter out unqualified userIds - .map(formatEid(source, rtiPartner)); // userIds -> eid objects -} - -const filterEids = type => (userId, i, arr) => { - let isValidUserId = - !!userId && // is not null nor empty - (isStr(userId) - ? !!userId - : isPlainObject(userId) && // or, is object - !isArray(userId) && // not an array - !isEmpty(userId) && // is not empty - userId.id && // contains nested id field - isStr(userId.id) && // nested id field is a string - !!userId.id); // that is not empty - if (!isValidUserId && arr[0] !== undefined) { - logWarn(`Triplelift: invalid ${type} userId format`); - } - return isValidUserId; -}; - -function getUserId(type) { - return bid => bid && bid.userId && bid.userId[type]; -} - -function formatEid(source, rtiPartner) { - return (userId) => ({ - source, - uids: [{ - id: userId.id ? userId.id : userId, - ext: { rtiPartner } - }] - }); -} - function _sizes(sizeArray) { let sizes = sizeArray.filter(_isValidSize); return sizes.map(function(size) { 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' } } ] From 37e91e75d1ebcbc36bfb700280e2500a11b2c30c Mon Sep 17 00:00:00 2001 From: Jordi Garcia Date: Tue, 5 Mar 2024 11:08:04 +0100 Subject: [PATCH 516/760] Azerion Edge RTD Module: Initial release (#11134) * Azerion Edge RTD Module: Initial release ### Type of change [x] Feature: New RTD Submodule ### Description of change Adds new Azerion Edge RTD module. Maintainer: azerion.com Contact: @garciapuig @mserrate @gguridi * Azerion Edge RTD Module: Initial release. Typo --- .../gpt/azerionedgeRtdProvider_example.html | 91 +++++++++ modules/.submodules.json | 1 + modules/azerionedgeRtdProvider.js | 143 ++++++++++++++ modules/azerionedgeRtdProvider.md | 112 +++++++++++ src/adloader.js | 3 +- .../modules/azerionedgeRtdProvider_spec.js | 183 ++++++++++++++++++ 6 files changed, 532 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/gpt/azerionedgeRtdProvider_example.html create mode 100644 modules/azerionedgeRtdProvider.js create mode 100644 modules/azerionedgeRtdProvider.md create mode 100644 test/spec/modules/azerionedgeRtdProvider_spec.js 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/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/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/src/adloader.js b/src/adloader.js index 5309f3a3d42..c2da2646320 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -20,6 +20,7 @@ const _approvedLoadExternalJSList = [ 'hadron', 'medianet', 'improvedigital', + 'azerionedge', 'aaxBlockmeter', 'confiant', 'arcspan', @@ -33,7 +34,7 @@ const _approvedLoadExternalJSList = [ 'contxtful', 'id5', 'lucead', -] +]; /** * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy 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 + ); + }); + }); + }); +}); From b97b55a75b7d76292f08fc15c0d75d15ae284d91 Mon Sep 17 00:00:00 2001 From: pangle-fe <149553960+pangle-fe@users.noreply.github.com> Date: Tue, 5 Mar 2024 23:42:30 +0800 Subject: [PATCH 517/760] feat: pangle multi format (#11175) --- modules/pangleBidAdapter.js | 12 ++++++------ test/spec/modules/pangleBidAdapter_spec.js | 14 +++++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) 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/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); }); }); From e873c71ca681f68bca74efa30b25115b967181a8 Mon Sep 17 00:00:00 2001 From: redaguermas Date: Tue, 5 Mar 2024 11:16:23 -0800 Subject: [PATCH 518/760] NoBid Analytics Adapter: support for multiple currencies (#11171) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Delete nobidBidAdapter.js.orig * Delete a * Delete .jsdtscope * Delete org.eclipse.wst.jsdt.ui.superType.container * Delete org.eclipse.wst.jsdt.ui.superType.name * Delete .project * Added support for multiple currencies to the NoBid Analytics adapter. --------- Co-authored-by: Reda Guermas --- modules/nobidAnalyticsAdapter.js | 7 +- .../modules/nobidAnalyticsAdapter_spec.js | 67 ++++++++++++------- 2 files changed, 45 insertions(+), 29 deletions(-) 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/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(); From 2553471dada292bf7b8dbb325874f221b22bb744 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Wed, 6 Mar 2024 06:35:31 +1100 Subject: [PATCH 519/760] Fix for bids without userId specified. (#11170) --- modules/adnuntiusBidAdapter.js | 15 +++++++------- test/spec/modules/adnuntiusBidAdapter_spec.js | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) 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/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 20795b59e8c..71f0a6a3a6c 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -681,6 +681,26 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(`${ENDPOINT_URL_BASE}&userId=different_user_id`); }); + + it('should handle no user specified', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + }); + const req = [ + { + bidId: 'adn-000000000008b6bc', + bidder: 'adnuntius', + params: { + auId: '000000000008b6bc', + network: 'adnuntius' + } + } + ] + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + expect(request[0].url).to.equal(ENDPOINT_URL); + }); }); describe('user privacy', function() { From 9cb0df52408fb74b948124183bc7547889045dbb Mon Sep 17 00:00:00 2001 From: ryohamadaumt <105703275+ryohamadaumt@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:03:25 +0900 Subject: [PATCH 520/760] adstirBidAdapter support topic api (#11177) --- modules/adstirBidAdapter.js | 1 + test/spec/modules/adstirBidAdapter_spec.js | 1 + 2 files changed, 2 insertions(+) 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/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); From 1a3a6ed0b1aece1e43731133342b123a635565fc Mon Sep 17 00:00:00 2001 From: Wiem Zine Elabidine Date: Wed, 6 Mar 2024 17:00:37 +0100 Subject: [PATCH 521/760] inline ttd and refactor test (#11174) --- libraries/uid1Eids/uid1Eids.js | 16 ++++ modules/liveIntentIdSystem.js | 18 ++--- modules/unifiedIdSystem.js | 13 +--- modules/userId/eids.md | 14 +--- test/spec/modules/eids_spec.js | 45 +++--------- .../modules/liveIntentIdMinimalSystem_spec.js | 2 +- test/spec/modules/liveIntentIdSystem_spec.js | 2 +- test/spec/modules/userId_spec.js | 73 +++++++++++++++++-- 8 files changed, 104 insertions(+), 79 deletions(-) create mode 100644 libraries/uid1Eids/uid1Eids.js 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/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/unifiedIdSystem.js b/modules/unifiedIdSystem.js index e88aec3a90f..1ede5f76850 100644 --- a/modules/unifiedIdSystem.js +++ b/modules/unifiedIdSystem.js @@ -8,6 +8,7 @@ import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {submodule} from '../src/hook.js' +import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -74,17 +75,7 @@ export const unifiedIdSubmodule = { }; return {callback: resp}; }, - eids: { - 'tdid': { - source: 'adserver.org', - atype: 1, - getUidExt: function() { - return { - rtiPartner: 'TDID' - }; - } - }, - } + eids: {...UID1_EIDS} }; submodule('userId', unifiedIdSubmodule); diff --git a/modules/userId/eids.md b/modules/userId/eids.md index 11400f4007f..c10ecde9c30 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -31,7 +31,8 @@ userIdAsEids = [ id: 'some-random-id-value', atype: 1, ext: { - rtiPartner: 'TDID' + rtiPartner: 'TDID', + provider: 'liveintent.com' } }] }, @@ -173,17 +174,6 @@ userIdAsEids = [ }] }, - { - source: 'adserver.org', - uids: [{ - id: 'some-random-id-value', - atype: 3, - ext: { - provider: 'liveintent.com' - } - }] - }, - { source: 'rubiconproject.com', uids: [{ 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/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/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: { From 06bf1e4f2a91b1bcae100805bf6259b25a78b848 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Wed, 6 Mar 2024 20:45:24 +0300 Subject: [PATCH 522/760] NextMillennium Bid Adapter: removed the use of the events module (#11141) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * deleted deprecated code wurl * removed the use of the events module * added parameters w and h for imp[].banner objecct --- modules/nextMillenniumBidAdapter.js | 166 +++--------------- .../modules/nextMillenniumBidAdapter_spec.js | 4 +- 2 files changed, 28 insertions(+), 142 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index d151523b265..c64fb7b7ea4 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'; @@ -55,15 +53,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 +68,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 +124,8 @@ export const spec = { params, auctionId, }); + + this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_REQUESTED, bid); }); return requests; @@ -148,11 +139,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 +168,8 @@ export const spec = { }; bidResponses.push(bidResponse); + + this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_RESPONSE, bid); }); }); @@ -215,6 +203,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 +246,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) { @@ -267,8 +271,12 @@ export function getImp(bid, id, mediaTypes) { 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 = { - format: (banner.data?.sizes || []).map(s => { return {w: s[0], h: s[1]} }), + w, + h, + format, }; }; @@ -487,126 +495,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/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index b9871bbbe71..6b7c559229b 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}]}, }, }, @@ -902,7 +902,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); }; }) From 0210d2eeb5639753e1e39ad0069ea69325d60e2c Mon Sep 17 00:00:00 2001 From: bhasker-ddh <159261864+bhasker-ddh@users.noreply.github.com> Date: Thu, 7 Mar 2024 02:35:23 +0530 Subject: [PATCH 523/760] Colossus Bid Adapter: Add GPP Support and Accept eids from publisher request (#11155) * add video&native traffic colossus ssp * Native obj validation * Native obj validation #2 * Added size field in requests * fixed test * fix merge conflicts * move to 3.0 * move to 3.0 * fix IE11 new URL issue * fix IE11 new URL issue * fix IE11 new URL issue * https for 3.0 * add https test * add ccp and schain features * fix test * sync with upstream, fix conflicts * Update colossussspBidAdapter.js remove commented code * Update colossussspBidAdapter.js lint fix * identity extensions * identity extensions * fix * fix * fix * fix * fix * add tests for user ids * fix * fix * fix * fix * fix * fix * fix * add gdpr support * add gdpr support * id5id support * Update colossussspBidAdapter.js add bidfloor parameter * Update colossussspBidAdapter.js check bidfloor * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter.js * Update colossussspBidAdapter_spec.js * use floor module * Revert "use floor module" This reverts commit f0c5c248627567e669d8eed4f2bb9a26a857e2ad. * use floor module * update to 5v * fix * add uid2 and bidFloor support * fix * add pbadslot support * fix conflicts * add onBidWon * refactor * add test for onBidWon() * fix * add group_id * Trigger circleci * fix * update user sync * fix window.location * fix test * updates * fix conflict * fix * updates * remove traffic param * add transactionId to request data for colossusssp adapter * Send tid in placements array * update user sync * updated tests * remove changes package-lock file * fix * add First Party Data * gpp support * accepting eids from request * fixing lint errors * resolving a conflict * fixing a failed test case related to tid * fixing karma version for conflict resolution * reverting package json files to original version --------- Co-authored-by: Vladislav Isaiko Co-authored-by: Aiholkin Co-authored-by: Bill Newman Co-authored-by: Mykhailo Yaremchuk Co-authored-by: kottapally --- modules/colossussspBidAdapter.js | 2 +- .../modules/colossussspBidAdapter_spec.js | 51 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) 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/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') From 0bb0df1df1bedecd304bbe36861d53b7434890a5 Mon Sep 17 00:00:00 2001 From: Espen <2290914+espen-j@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:11:10 +0100 Subject: [PATCH 524/760] =?UTF-8?q?Cwire=20adapter:=20Add=20gvl=5Fid=20for?= =?UTF-8?q?=20tcfeu=20compliance=C2=A0=20(c-wire/support#117)=20(#11181)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/cwireBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) 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], /** From 48e88f388fa7f27000ba548eb456b33a3a882f3e Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Thu, 7 Mar 2024 17:58:28 +0200 Subject: [PATCH 525/760] Vidazoo Bid Adapter : more ortb2 data and fledge support (#11182) * Pass ortb2 content data and user data to server. * Pass ortb2 content data and user data to server. * added fledge flag to to request --- modules/vidazooBidAdapter.js | 11 +++ test/spec/modules/vidazooBidAdapter_spec.js | 87 ++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index 59f3fe97969..ea1555e5327 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -86,6 +86,8 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); + const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ @@ -121,6 +123,8 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout isStorageAllowed: isStorageAllowed, gpid: gpid, cat: cat, + contentData, + userData: userData, pagecat: pagecat, transactionId: ortb2Imp?.ext?.tid, bidderRequestId: bidderRequestId, @@ -159,6 +163,13 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } + if (bidderRequest.fledgeEnabled) { + const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); + if (fledge) { + data.fledge = fledge; + } + } + _each(ext, (value, key) => { data['ext.' + key] = value; }); 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(); From 2b3a62b084b3077fde798946ada5824b14b8bb6d Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 7 Mar 2024 16:30:14 -0300 Subject: [PATCH 526/760] [JW Player RTD Module] Deprecate playerID (#11179) * renames player ID * updates tests --- modules/jwplayerRtdProvider.js | 22 +++++---- modules/jwplayerRtdProvider.md | 4 +- test/spec/modules/jwplayerRtdProvider_spec.js | 45 ++++++++++++++++--- 3 files changed, 55 insertions(+), 16 deletions(-) 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/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 () { From e7eb2be844de56fcf1b0802beb37ed2213c0662f Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 8 Mar 2024 08:59:56 -0500 Subject: [PATCH 527/760] Delete test/spec/modules/enrichmentFpdModule_spec.js (#11188) --- test/spec/modules/enrichmentFpdModule_spec.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/spec/modules/enrichmentFpdModule_spec.js diff --git a/test/spec/modules/enrichmentFpdModule_spec.js b/test/spec/modules/enrichmentFpdModule_spec.js deleted file mode 100644 index e69de29bb2d..00000000000 From 8a92067f4861cd4df2bd72449ce2f683ed434401 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 8 Mar 2024 14:47:43 +0000 Subject: [PATCH 528/760] Prebid 8.40.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1581edd19ae..3009823ec22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.40.0-pre", + "version": "8.40.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index c865d4520a1..bd504637c80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.40.0-pre", + "version": "8.40.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 920a36df6a9e6db7912949385eda0fe3ace529c5 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 8 Mar 2024 14:47:44 +0000 Subject: [PATCH 529/760] Increment version to 8.41.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3009823ec22..bd57dba81e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.40.0", + "version": "8.41.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index bd504637c80..2f363611489 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.40.0", + "version": "8.41.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From edf27c8dc57b1da0c3406de6284b07c8c91f599b Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:34:33 +0800 Subject: [PATCH 530/760] Discovery Bid Adapter : Extend the expiration time of pmguid (#11154) * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test resolve conflict * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test * Discovery Bid Adapter : synchronize mguid from third party cookie to first party cookie * test * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 9 +++++---- test/spec/modules/discoveryBidAdapter_spec.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index ad8f5616d44..aa497b99d00 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; }; diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 961ccb33c4f..4fb4c29b99b 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -219,7 +219,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', () => { From 2da60558feb4bf0d188a6bc329e09869a30dd6f8 Mon Sep 17 00:00:00 2001 From: Gabriel Chicoye Date: Mon, 11 Mar 2024 15:09:54 +0100 Subject: [PATCH 531/760] ext.vastxml to adm (#11164) Co-authored-by: Gabriel Chicoye --- modules/nexx360BidAdapter.js | 5 ++--- test/spec/modules/nexx360BidAdapter_spec.js | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) 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/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'); From 59466ad9db6a299055a56f5fc73644a66fb1d9d9 Mon Sep 17 00:00:00 2001 From: Elad Yosifon Date: Mon, 11 Mar 2024 16:26:43 +0200 Subject: [PATCH 532/760] fix kueezRtbBidAdapter timeout logic #9787 (#11193) --- modules/kueezRtbBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From a0683559f87f96584c2f9aaeb556caa9ac269066 Mon Sep 17 00:00:00 2001 From: Minebomber Date: Mon, 11 Mar 2024 07:57:21 -0700 Subject: [PATCH 533/760] HypeLab Bid Adapter: support floors and bugfixes (#11165) --- modules/hypelabBidAdapter.js | 36 ++++++++++++++++++--- test/spec/modules/hypelabBidAdapter_spec.js | 6 +++- 2 files changed, 36 insertions(+), 6 deletions(-) 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/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', () => { From 075637937150ca9f5876e12da380ef5aae3d32dc Mon Sep 17 00:00:00 2001 From: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:54:39 +0100 Subject: [PATCH 534/760] Sspbc Bid Adapter : add support for dsa and pass non standard ids (#11173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * [sspbc-adapter] update v5.93 - add suport for DSA and non-standard user id's * [sspbc-adapter] update v5.93 - remove unnecessary gdpr data * [sspbc-adapter] remove debug * [sspbc-adapter] fix tests --------- Co-authored-by: Wojciech Biały --- modules/sspBCBidAdapter.js | 39 +++++++++++++---------- test/spec/modules/sspBCBidAdapter_spec.js | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) 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) => { } + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file 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/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) + }) + }) +}); From 101ec9403d8cc8b502076c790ac35c725e9c0642 Mon Sep 17 00:00:00 2001 From: cckowalewska <117265111+cckowalewska@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:43:12 +0100 Subject: [PATCH 536/760] Pstudio Bid Adapter: initial release (#10980) * Pstudio initial bidder * remove unnecessary return * Use Storage and Floor Module * mend * use proper url for adapter * Update modules/pstudioBidAdapter.js Co-authored-by: Olivier --------- Co-authored-by: Noemi Kowalewska Co-authored-by: Olivier --- modules/pstudioBidAdapter.js | 435 +++++++++++++++++ modules/pstudioBidAdapter.md | 148 ++++++ test/spec/modules/pstudioBidAdapter_spec.js | 514 ++++++++++++++++++++ 3 files changed, 1097 insertions(+) create mode 100644 modules/pstudioBidAdapter.js create mode 100644 modules/pstudioBidAdapter.md create mode 100644 test/spec/modules/pstudioBidAdapter_spec.js diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js new file mode 100644 index 00000000000..9eb2d33aed5 --- /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://nft-exchange.pre-prod.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/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; + } + }); + }); +}); From 691f15965386a4cd4e897b0bd461c06b6da496b0 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Tue, 12 Mar 2024 19:52:32 +0200 Subject: [PATCH 537/760] Adkernel Bid Adapter: add digiad alias (#11199) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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], From 643cbd0fa07f941040d124d63df4b7b63bc87bad Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Tue, 12 Mar 2024 15:53:23 -0400 Subject: [PATCH 538/760] Update rtbhouseBidAdapter.js (#11185) --- modules/rtbhouseBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 6bd8ba61235a7368edf284df5e9dfe866dc9a9d4 Mon Sep 17 00:00:00 2001 From: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:22:23 +0100 Subject: [PATCH 539/760] Onetag Bid Adapter: add reading of addtlConsent GDPR field (#11202) Co-authored-by: federico --- modules/onetagBidAdapter.js | 3 ++- test/spec/modules/onetagBidAdapter_spec.js | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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/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 () { From 22d10ddf9c7e975f9dc15d7a5f33ff0c11245e38 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Wed, 13 Mar 2024 19:27:43 +0530 Subject: [PATCH 540/760] PubMatic Analytics Adapter : Logging price bucket value of the bid (#11197) * Logging price bucket value for the bid * Converting data type for logging * Dummy Commit --------- Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 9 +++++++-- test/spec/modules/pubmaticAnalyticsAdapter_spec.js | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 66593a9d72b..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; @@ -457,8 +460,9 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { 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); @@ -475,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/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'); From c9faa28c4a7af5716aec24abd29e181282fa336a Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:58:02 +0200 Subject: [PATCH 541/760] SmartyadsBidAdapter/add_SGP_region (#11205) Co-authored-by: vrishko --- modules/smartyadsBidAdapter.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 2409bebbc59..b6d5a1711b0 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']; } From b68759d641a7e617b4cc04b38d0dd79c5c66ceca Mon Sep 17 00:00:00 2001 From: Shashank Pradeep <101392500+shashankatd@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:17:49 +0530 Subject: [PATCH 542/760] Automatad Analytics Adapter : expose queue as a global object (#11203) * fix: added fix for automatadAnalytics * fix: add s3 upload for atmtd * fix: add upload to s3 * fix: removed changes to gulp file and package.json * fix: added build command to gulp * fix: removed extra line from gulpfile * restored original gulp file * fix: minor fix to condition * fix: removed unnecessary condition * fix: removed linting error * Get build check to re run --------- Co-authored-by: Shashank <=> --- modules/automatadAnalyticsAdapter.js | 53 ++++---- .../modules/automatadAnalyticsAdapter_spec.js | 117 +++++++++++++++--- 2 files changed, 133 insertions(+), 37 deletions(-) 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/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(); }) From 1c57789be9a6c8e451eb27886999d85822b84b29 Mon Sep 17 00:00:00 2001 From: barRubi <151519394+barRubi@users.noreply.github.com> Date: Wed, 13 Mar 2024 23:48:11 +0200 Subject: [PATCH 543/760] Taboola Bid Adapter : providing visibility in case of bid error (#11195) * cookie-look-up-logic-fix-gpp-fix * refactor using ORTB conversion library --------- Co-authored-by: ahmadlob <109217988+ahmadlob@users.noreply.github.com> Co-authored-by: Ahmad Lobany Co-authored-by: aleskanderl <109285067+aleskanderl@users.noreply.github.com> --- modules/taboolaBidAdapter.js | 2 +- test/spec/modules/taboolaBidAdapter_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index b0418ab9865..3b26a00c6fb 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -200,7 +200,7 @@ export const spec = { }, onBidderError: ({ error, bidderRequest }) => { - ajax(EVENT_ENDPOINT + '/bidError', null, JSON.stringify(error, bidderRequest), {method: 'POST'}); + ajax(EVENT_ENDPOINT + '/bidError', null, JSON.stringify({error, bidderRequest}), {method: 'POST'}); }, }; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 39df2eb4a99..958cd668f63 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}); }); }); From 77254c2954d61c9156735a2fb30611fd7bbde6e4 Mon Sep 17 00:00:00 2001 From: Saurabh Joshi <43156540+sj1815@users.noreply.github.com> Date: Thu, 14 Mar 2024 04:49:56 -0400 Subject: [PATCH 544/760] Kargo Bid Adapter: floors and CreativeID update (#11153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * KargoBidAdapter: GPP Support * kargo adapter to forward schain object (#21) * wrap in if statement (#22) * KRKPD-572: Add spec for schain (#23) * wrap in if statement * update test for schain, file formatting * Adding site to Kargo adapter. * KRKPD-619 Updating Site object. * KRKPD-619 Adding null check for Site object. * Update modules/kargoBidAdapter.js Co-authored-by: Julian Gan * Reducing the size of Site object. * remove white space that is causing linting error * Kargo Bid Adapter: Updates to gpid retrieval * Support for sending ortb2.user.data * update bid Response to use actual creativeID * update spec * fix nomencalature based on Kargo's service * Prebid.js - Update bid Response to use actual creativeID (#25) * update bid Response to use actual creativeID * update spec * fix nomencalature based on Kargo's service * utilize floors mod * fixes tests * mediatype specific floors * simpler implementation leveraging Prebid's smart rule selection * revert nomenclature change * fix * removes comment --------- Co-authored-by: Jeremy Sadwith Co-authored-by: Julian Gan Co-authored-by: Neil Flynn Co-authored-by: “Nick <“nick.llerandi”@kargo.com> Co-authored-by: Nick Llerandi --- modules/kargoBidAdapter.js | 38 +++++++++++++-------- test/spec/modules/kargoBidAdapter_spec.js | 40 ++++++++++++++++------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index 9d8c7bc06a1..b72601e5ebb 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'; @@ -221,7 +221,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 +459,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 +478,33 @@ function getImpression(bid) { } } - if (bid.mediaTypes != null) { - if (bid.mediaTypes.banner != null) { - imp.banner = bid.mediaTypes.banner; + if (bid.mediaTypes) { + const { banner, video, native } = bid.mediaTypes; + + if (banner) { + imp.banner = banner; } - if (bid.mediaTypes.video != null) { - imp.video = bid.mediaTypes.video; + if (video) { + imp.video = video; } - if (bid.mediaTypes.native != null) { - imp.native = bid.mediaTypes.native; + if (native) { + imp.native = 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/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index f43c3b11aac..40f8833c6ec 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -491,7 +491,8 @@ describe('kargo adapter tests', function () { }, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } + }, + floor: 2 }, { code: '303', @@ -503,7 +504,8 @@ describe('kargo adapter tests', function () { }, fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } + }, + floor: 3 } ], socan: { @@ -605,6 +607,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 +737,8 @@ describe('kargo adapter tests', function () { adm: '
', width: 320, height: 50, - metadata: {} + metadata: {}, + creativeID: 'bar' }, 2: { id: 'bar', @@ -736,14 +749,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 +768,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'banner', metadata: {}, + creativeID: 'foo', currency: 'EUR' }, 5: { @@ -763,6 +779,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'video', metadata: {}, + creativeID: 'foo', currency: 'EUR' }, 6: { @@ -774,6 +791,7 @@ describe('kargo adapter tests', function () { height: 250, mediaType: 'video', metadata: {}, + creativeID: 'foo', currency: 'EUR' } } @@ -818,7 +836,7 @@ describe('kargo adapter tests', function () { width: 320, height: 50, ttl: 300, - creativeId: 'foo', + creativeId: 'bar', dealId: undefined, netRevenue: true, currency: 'USD', @@ -833,7 +851,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: 'dmpmptest1234', netRevenue: true, currency: 'USD', @@ -850,7 +868,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'USD', @@ -865,7 +883,7 @@ describe('kargo adapter tests', function () { width: 300, height: 250, ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'EUR', @@ -880,7 +898,7 @@ describe('kargo adapter tests', function () { height: 250, vastXml: '', ttl: 300, - creativeId: 'bar', + creativeId: 'foo', dealId: undefined, netRevenue: true, currency: 'EUR', @@ -895,7 +913,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', From b8a80313d3d34feb2abf9fe9db2dbc165693e945 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Thu, 14 Mar 2024 15:33:44 +0400 Subject: [PATCH 545/760] fix mismatch (#11208) --- modules/sovrnBidAdapter.js | 23 ++++++++----- test/spec/modules/sovrnBidAdapter_spec.js | 39 +++++++++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) 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/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 = { From e939aee5521736f957412e90c574f1382a065ce8 Mon Sep 17 00:00:00 2001 From: kzwolinskirasp <162448351+kzwolinskirasp@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:05:54 +0100 Subject: [PATCH 546/760] Ras Bid Adapter : add support for native mediaType (#11180) * add support for native in rasbidadapter * lint fixs * final logic fix * final logic fix * corner case fix * update rasBidAdapter.md --- modules/rasBidAdapter.js | 200 +++++++++++-- modules/rasBidAdapter.md | 4 +- test/spec/modules/rasBidAdapter_spec.js | 366 +++++++++++++++++++++++- 3 files changed, 544 insertions(+), 26 deletions(-) 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/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)); + }); + }); }); From e45e5cad54a175aa6e6901fc8dd50906a06210c6 Mon Sep 17 00:00:00 2001 From: Tiago Peczenyj Date: Thu, 14 Mar 2024 13:42:01 +0100 Subject: [PATCH 547/760] Weborama RTD Module : update gdpr purpose ids verification for TCF v2.2 (#11089) * add gdpr verification for user-centric modules * fix log messages * refactor logger * small refactors in code and import order * add new purpose ids and special feature 1 * remove special feature one, keep purposes 1, 3, 4, 5 and 7 * remove special feature one, keep purposes 1, 3, 4, 5 and 7 * remove special feature one, keep purposes 1, 3, 4, 5 and 6 * fix lint warning jsdoc/no-undefined-types * fix typos * update doc * fix link in doc --- modules/weboramaRtdProvider.js | 135 +++++++++++++----- modules/weboramaRtdProvider.md | 17 ++- test/spec/modules/weboramaRtdProvider_spec.js | 114 +++++++++++++++ 3 files changed, 226 insertions(+), 40 deletions(-) diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index a0963b844e1..82feec252eb 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -104,18 +104,18 @@ import { getGlobal } from '../src/prebidGlobal.js'; import { + deepAccess, deepClone, deepSetValue, - isEmpty, - isFn, - logError, - logMessage, isArray, - isStr, isBoolean, + isEmpty, + isFn, isPlainObject, + isStr, logWarn, - mergeDeep + mergeDeep, + prefixLog, } from '../src/utils.js'; import { submodule @@ -126,9 +126,13 @@ import { import { getStorageManager } from '../src/storageManager.js'; +import { + MODULE_TYPE_RTD +} from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; -import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; -import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; +import { + tryAppendQueryString +} from '../libraries/urlUtils/urlUtils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -159,6 +163,8 @@ const SFBX_LITE_DATA_SOURCE_LABEL = 'lite'; /** @type {number} */ const GVLID = 284; +const logger = prefixLog('[WeboramaRTD]'); + export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME @@ -199,9 +205,11 @@ class WeboramaRtdProvider { * @method * @param {Object} moduleConfig * @param {?ModuleParams} moduleConfig.params + * @param {Object} userConsent + * @param {?Object} userConsent.gdpr * @return {boolean} true if module was initialized with success */ - init(moduleConfig) { + init(moduleConfig, userConsent) { /** @type {Object} */ const globalDefaults = { setPrebidTargeting: true, @@ -219,8 +227,14 @@ class WeboramaRtdProvider { this.#components.WeboUserData.data = null; this.#components.SfbxLiteData.data = null; - this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token'); - this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION); + const weboCtxRequiredFields = ['token']; + + this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, { + requiredFields: weboCtxRequiredFields, + }); + this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION, { + userConsent: userConsent || {}, + }); this.#components.SfbxLiteData.initialized = this.#initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); return Object.values(this.#components).some((c) => c.initialized); @@ -251,7 +265,7 @@ class WeboramaRtdProvider { const weboCtxConf = moduleParams.weboCtxConf || {}; this.#fetchContextualProfile(weboCtxConf, (data) => { - logMessage('fetchContextualProfile on getBidRequestData is done'); + logger.logMessage('fetchContextualProfile on getBidRequestData is done'); this.#setWeboContextualProfile(data); }, () => { @@ -276,17 +290,17 @@ class WeboramaRtdProvider { const profileHandlers = this.#buildProfileHandlers(moduleParams); if (isEmpty(profileHandlers)) { - logMessage('no data to set targeting'); + logger.logMessage('no data to set targeting'); return {}; } try { return adUnitsCodes.reduce((data, adUnitCode) => { data[adUnitCode] = profileHandlers.reduce((targeting, ph) => { - // logMessage(`check if should set targeting for adunit '${adUnitCode}'`); + // logger.logMessage(`check if should set targeting for adunit '${adUnitCode}'`); const [data, metadata] = this.#copyDataAndMetadata(ph); if (ph.setTargeting(adUnitCode, data, metadata)) { - // logMessage(`set targeting for adunit '${adUnitCode}', source '${metadata.source}'`); + // logger.logMessage(`set targeting for adunit '${adUnitCode}', source '${metadata.source}'`); mergeDeep(targeting, data); } @@ -297,7 +311,7 @@ class WeboramaRtdProvider { return data; }, {}); } catch (e) { - logError(`unable to format weborama rtd targeting data:`, e); + logger.logError(`unable to format weborama rtd targeting data:`, e); return {}; } @@ -309,10 +323,12 @@ class WeboramaRtdProvider { * @private * @param {ModuleParams} moduleParams * @param {string} subSection subsection name to initialize - * @param {string[]} requiredFields + * @param {?Object} extra + * @param {string[]} extra.requiredFields + * @param {?Object} extra.userConsent * @return {boolean} true if module subsection was initialized with success */ - #initSubSection(moduleParams, subSection, ...requiredFields) { + #initSubSection(moduleParams, subSection, extra) { /** @type {CommonConf} */ const weboSectionConf = moduleParams[subSection] || { enabled: false }; @@ -325,21 +341,59 @@ class WeboramaRtdProvider { try { this.#normalizeConf(moduleParams, weboSectionConf); + extra = extra || {}; + const requiredFields = extra?.requiredFields || []; + requiredFields.forEach(field => { if (!(field in weboSectionConf)) { - throw `missing required field '${field}''`; + throw `missing required field '${field}'`; } }); + + if (isPlainObject(extra?.userConsent?.gdpr) && !this.#checkTCFv2(extra.userConsent.gdpr)) { + throw 'gdpr consent not ok'; + } } catch (e) { - logError(`unable to initialize: error on ${subSection} configuration:`, e); + logger.logError(`unable to initialize: error on '${subSection}' configuration:`, e); return false; } - logMessage(`weborama ${subSection} initialized with success`); + logger.logMessage(`weborama '${subSection}' initialized with success`); return true; } + /** + * check gdpr consent data + * @method + * @private + * @param {Object} gdpr + * @param {?boolean} gdpr.gdprApplies + * @param {?Object} gdpr.vendorData + * @param {?Object} gdpr.vendorData.purpose + * @param {?Object.} gdpr.vendorData.purpose.consents + * @param {?Object} gdpr.vendorData.vendor + * @param {?Object.} gdpr.vendorData.vendor.consents + * @return {boolean} + */ + // eslint-disable-next-line no-dupe-class-members + #checkTCFv2(gdpr) { + if (gdpr?.gdprApplies !== true) { + return true; + } + + if (deepAccess(gdpr, 'vendorData.vendor.consents') && + deepAccess(gdpr, 'vendorData.purpose.consents')) { + return gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id + gdpr.vendorData.purpose.consents[1] === true && // info storage access + gdpr.vendorData.purpose.consents[3] === true && // create personalized ads + gdpr.vendorData.purpose.consents[4] === true && // select personalized ads + gdpr.vendorData.purpose.consents[5] === true && // create personalized content + gdpr.vendorData.purpose.consents[6] === true; // select personalized content + } + + return true; + } /** * normalize submodule configuration * @method @@ -455,7 +509,7 @@ class WeboramaRtdProvider { const profileHandlers = this.#buildProfileHandlers(moduleParams); if (isEmpty(profileHandlers)) { - logMessage('no data to send to bidders'); + logger.logMessage('no data to send to bidders'); return; } @@ -465,11 +519,11 @@ class WeboramaRtdProvider { adUnits.forEach( adUnit => adUnit.bids?.forEach( bid => profileHandlers.forEach(ph => { - // logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); + // logger.logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); const [data, metadata] = this.#copyDataAndMetadata(ph); if (ph.sendToBidders(bid, adUnit.code, data, metadata)) { - // logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); + // logger.logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`); this.#handleBid(reqBidsConfigObj, bid, data, ph.metadata); } @@ -477,7 +531,7 @@ class WeboramaRtdProvider { ) ); } catch (e) { - logError('unable to send data to bidders:', e); + logger.logError('unable to send data to bidders:', e); } profileHandlers.forEach(ph => { @@ -485,7 +539,7 @@ class WeboramaRtdProvider { const [data, metadata] = this.#copyDataAndMetadata(ph); ph.onData(data, metadata); } catch (e) { - logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e); + logger.logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e); } }); } @@ -529,7 +583,7 @@ class WeboramaRtdProvider { try { assetID = weboCtxConf.assetID(); } catch (e) { - logError('unexpected error while fetching asset id from callback', e); + logger.logError('unexpected error while fetching asset id from callback', e); onDone(); @@ -538,7 +592,7 @@ class WeboramaRtdProvider { } if (!assetID) { - logError('missing asset id'); + logger.logError('missing asset id'); onDone(); @@ -565,7 +619,7 @@ class WeboramaRtdProvider { }; const error = (e, req) => { - logError(`unable to get weborama data`, e, req); + logger.logError(`unable to get weborama data`, e, req); onDone(); }; @@ -625,7 +679,7 @@ class WeboramaRtdProvider { if (profileHandler) { ph.push(profileHandler); } else { - logMessage(`skip ${source} profile: no data`); + logger.logMessage(`skip ${source} profile: no data`); } return ph; @@ -703,17 +757,26 @@ class WeboramaRtdProvider { #handleBid(reqBidsConfigObj, bid, profile, metadata) { this.#handleBidViaORTB2(reqBidsConfigObj, bid.bidder, profile, metadata); - /** @type {Object.} */ - const bidderAliasRegistry = adapterManager.aliasRegistry || {}; - /** @type {string} */ - const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + const bidder = this.#getAdapterNameForAlias(bid.bidder); if (bidder == 'appnexus') { this.#handleAppnexusBid(reqBidsConfigObj, bid, profile); } } + /** + * return adapter name based on alias, if any + * @method + * @private + * @param {string} aliasName + * @returns {string} + */ + // eslint-disable-next-line no-dupe-class-members + #getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; + } + /** * function that handles bid request data * @method @@ -760,13 +823,13 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #handleBidViaORTB2(reqBidsConfigObj, bidder, profile, metadata) { if (isBoolean(metadata.user)) { - logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); + logger.logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); const section = metadata.user ? 'user' : 'site'; const path = `${section}.ext.data`; this.#setBidderOrtb2(reqBidsConfigObj.ortb2Fragments?.bidder, bidder, path, profile) } else { - logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + logger.logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`); } } /** diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index 0c6e3339787..a8fc692ba74 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -16,7 +16,7 @@ Weborama provides a Real-Time Data Submodule for `Prebid.js`, allowing to easy i * LiTE by SFBX® (Local inApp Trust Engine) provides “Zero Party Data” given by users, stored and calculated only on the user’s device. Through a unique cohorting system, it enables better monetization in a consent/consentless and identity-less mode. -Contact prebid-support@weborama.com for more information. +Contact [prebid-support@weborama.com] for more information. ### Publisher Usage @@ -79,7 +79,7 @@ pbjs.setConfig({ Each module can perform two actions: -* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html]) via `prebid.js` +* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html) via `prebid.js` * send data to other `prebid.js` bidder modules (check the complete list at the end of this page) @@ -117,9 +117,9 @@ On this section we will explain the `params.weboCtxConf` subconfiguration: | enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| | baseURLProfileAPI | String| if present, update the domain of the contextual api| Optional. Default is `ctx.weborama.com` | -#### WAM User-Centric Configuration +#### User-Centric Configuration -To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you lust include the `wamfactory` script in your pages with `wam2gam` feature activated. +To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you must include the `wamfactory` script in your pages with `wam2gam` feature activated. Please contact weborama if you don't have it. On this section we will explain the `params.weboUserDataConf` subconfiguration: @@ -134,6 +134,15 @@ On this section we will explain the `params.weboUserDataConf` subconfiguration: | localStorageProfileKey| String | can be used to customize the local storage key | Optional | | enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present| +##### User Consent + +The WAM User-Centric configuration will check for user consent if gdpr applies. It will check for consent: + +* Vendor ID 284 (Weborama) +* Purpose IDs: 1, 3, 4, 5 and 6 + +If the user consent does not match such conditions, this module will not load, means we will not check for any data in local storage and the default profile will be ignored. + #### Sfbx LiTE Site-Centric Configuration To be possible use the integration between Weborama and Sfbx LiTE you should also contact SFBX® to setup this product. 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() { From fa8c030e24c0a5e193f060106de3ab6d15796c12 Mon Sep 17 00:00:00 2001 From: abermanov-zeta <95416296+abermanov-zeta@users.noreply.github.com> Date: Thu, 14 Mar 2024 13:49:39 +0100 Subject: [PATCH 548/760] Zeta Global SSP Bid Adapter: add gvlid. (#11211) --- modules/zeta_global_sspBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index aa35066e26b..5d16a469153 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -50,6 +50,7 @@ const VIDEO_CUSTOM_PARAMS = { export const spec = { code: BIDDER_CODE, + gvlid: 469, supportedMediaTypes: [BANNER, VIDEO], /** From e5d87df819681b03aa39150cdb9c7660c5bb6152 Mon Sep 17 00:00:00 2001 From: Teddy Pierre <33702256+pierreted90@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:07:03 -0400 Subject: [PATCH 549/760] Sharethrough Bid Adapter : Support for dsa transparency (#11191) * Added dsa transparency * fix linting * fixed linting * Removed validation * removed unused var --------- Co-authored-by: Max Dupuis <118775839+maxime-dupuis@users.noreply.github.com> --- modules/sharethroughBidAdapter.js | 4 +++ .../modules/sharethroughBidAdapter_spec.js | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) 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/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); From 5018b6ba3c944d2c2c5509b098dcd776dfd15a87 Mon Sep 17 00:00:00 2001 From: cckowalewska <117265111+cckowalewska@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:30:48 +0100 Subject: [PATCH 550/760] Pstudio Bid Adapter: update endpoint to production (#11210) * Pstudio initial bidder * remove unnecessary return * Use Storage and Floor Module * mend * use proper url for adapter * Update modules/pstudioBidAdapter.js Co-authored-by: Olivier * Use production pstudio url --------- Co-authored-by: Noemi Kowalewska Co-authored-by: Olivier --- modules/pstudioBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js index 9eb2d33aed5..77a11ac58c6 100644 --- a/modules/pstudioBidAdapter.js +++ b/modules/pstudioBidAdapter.js @@ -12,7 +12,7 @@ import { import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'pstudio'; -const ENDPOINT = 'https://nft-exchange.pre-prod.pstudio.tadex.id/prebid-bid' +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 From 4b4e83ebec9520d0747f686b757dd04c33c78e9d Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:35:40 +0100 Subject: [PATCH 551/760] ZetaGlobalSsp Adapter: crop page (#11198) Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 26 ++++++++++++++++++- .../modules/zeta_global_sspBidAdapter_spec.js | 8 +++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 5d16a469153..722c51dc058 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -141,7 +141,7 @@ export const spec = { }; const rInfo = bidderRequest.refererInfo; // TODO: do the fallbacks make sense here? - payload.site.page = rInfo.page || rInfo.topmostLocation; + payload.site.page = cropPage(rInfo.page || rInfo.topmostLocation); payload.site.domain = parseDomain(payload.site.page, {noLeadingWww: true}); payload.device.ua = navigator.userAgent; @@ -386,6 +386,30 @@ function provideMediaType(zetaBid, bid, bidRequest) { } } +function cropPage(page) { + if (page) { + if (page.length > 100) { + page = page.substring(0, 100); + } + if (page.startsWith('https://')) { + page = page.substring(8); + } else if (page.startsWith('http://')) { + page = page.substring(7); + } + if (page.startsWith('www.')) { + page = page.substring(4); + } + for (let i = 3; i < page.length; i++) { + const c = page[i]; + if (c === '#' || c === '?') { + return page.substring(0, i); + } + } + return page; + } + return ''; +} + function clearEmpties(o) { for (let k in o) { if (o[k] === null) { diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index 7beac2f820c..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' @@ -235,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: { @@ -263,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: { @@ -312,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'); }); From 9a978bd3edf56f9bd0d1a404965ba971d28ed56e Mon Sep 17 00:00:00 2001 From: olafbuitelaar Date: Fri, 15 Mar 2024 07:10:28 +0100 Subject: [PATCH 552/760] Userid module: allow postInstall (#11215) * allow postInstall for the userId * Update index.js * Update index.js --------- Co-authored-by: Patrick McCann --- modules/userId/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/userId/index.js b/modules/userId/index.js index 5a088b27319..0e1df12e1db 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -1096,7 +1096,7 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { // init config update listener to start the application init(config); -module('userId', attachIdSystem); +module('userId', attachIdSystem, { postInstallAllowed: true }); export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); From 44717bc646dd37369d6babb0aaa6c4d10785ecb3 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Fri, 15 Mar 2024 16:51:08 +0300 Subject: [PATCH 553/760] NextMillennium Bid Adapter : fix imp.video.mimes (#11216) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * fixed bug imp.video.mimes * fixed bug imp.video.mimes - 2 --- modules/nextMillenniumBidAdapter.js | 85 ++++++++++++------- .../modules/nextMillenniumBidAdapter_spec.js | 37 +++++++- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index c64fb7b7ea4..18f2b461142 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -31,18 +31,24 @@ 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, + linearity: undefined, + maxduration: undefined, + mimes: [ + 'video/mp4', + 'video/x-ms-wmv', + 'application/javascript', + ], + + minduration: undefined, + placement: undefined, + playbackmethod: undefined, + protocols: undefined, + startdelay: undefined, +}; +const VIDEO_PARAMS = Object.keys(VIDEO_PARAMS_DEFAULT); const ALLOWED_ORTB2_PARAMETERS = [ 'site.pagecat', 'site.content.cat', @@ -267,33 +273,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); - 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; +}; - 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) { diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 6b7c559229b..5c0f5fc3f4d 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -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,38 @@ 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, + 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']}, }, }, ]; From 28cceb9a17cfa3048d9211ef9f0c27fc012828cd Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 15 Mar 2024 14:30:43 +0000 Subject: [PATCH 554/760] Prebid 8.41.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bd57dba81e4..4ff24f5559f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.41.0-pre", + "version": "8.41.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 2f363611489..87faf214c85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.41.0-pre", + "version": "8.41.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 2ecfa99917b5f30b8ac049932807593b27e790e9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 15 Mar 2024 14:30:45 +0000 Subject: [PATCH 555/760] Increment version to 8.42.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4ff24f5559f..9689743fd1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.41.0", + "version": "8.42.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 87faf214c85..9859d39606a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.41.0", + "version": "8.42.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 90bc7f40c8ba1943230991814e983fbe03cb6e6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Mar 2024 10:42:51 -0400 Subject: [PATCH 556/760] Bump follow-redirects from 1.15.4 to 1.15.6 (#11220) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9689743fd1e..19d7e70ff37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.38.0-pre", + "version": "8.42.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -13809,9 +13809,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -39839,9 +39839,9 @@ } }, "follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "for-each": { From a7a8f74b62da8c559481b01ab7b32fc434640553 Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Fri, 15 Mar 2024 17:46:31 +0300 Subject: [PATCH 557/760] Removing protocol check (#11219) Removing validation for the value of video.protocol --- modules/yieldmoBidAdapter.js | 8 -------- test/spec/modules/yieldmoBidAdapter_spec.js | 14 -------------- 2 files changed, 22 deletions(-) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 5fda0b751e7..1bb0488bf5d 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -654,14 +654,6 @@ function validateVideoParams(bid) { } validate('video.protocols', val => isDefined(val), paramRequired); - validate( - 'video.protocols', - (val) => - isArrayOfNums(val) && - val.every((v) => v >= 1 && v <= 12 && v != 9 && v != 10), // 9 and 10 are for DAST which are not supported. - paramInvalid, - 'array of numbers between 1 and 12 except for 9 or 10 , ex: [2,3, 7, 11]' - ); validate('video.api', val => isDefined(val), paramRequired); validate('video.api', val => isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)), 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; From d17c44bc98b88423882b4befa8baf414780cecd3 Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:47:25 +0800 Subject: [PATCH 558/760] jixie Bid Adapter : adding handling of userSync urls sent from server side (#11217) * Adapter does not seem capable of supporting advertiserDomains #6650 added response comment and some trivial code. * removed a blank line at the end of file added a space behind the // in comments * in response to comment from reviewer. add the aspect of advertiserdomain in unit tests * added the code to get the keywords from the meta tags if available. * WIP * cleaned up * correcting formatting errors from circleci * sending floor to our backend for each bid, when available, changed one of the 1st party cookies that we want to send to backend * fixed spacing issues in code * 1/ provide the possibility of using the jixie section of the config object to determine what ids to read from cookie and to send 2/ removed ontimeout handling 3/ bidwon just ping the trackingUrl, if any 4/ misc: sending aid (from jixie config if any), prebid version etc * corrected formatting mistakes * adding support of handling usersync array from the server side * add back registerBidder line dropped accidentally * fixed some formatting flaws * correcting more formatting issues * more formatting corrections --- modules/jixieBidAdapter.js | 15 ++++++ test/spec/modules/jixieBidAdapter_spec.js | 63 +++++++++++++++++++++++ 2 files changed, 78 insertions(+) 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/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) + }) + }) }); From ecdeb25a4796b5fee82159b9388010b556d45dd0 Mon Sep 17 00:00:00 2001 From: mrsmoking Date: Mon, 18 Mar 2024 18:31:22 +0200 Subject: [PATCH 559/760] Update ssp endpoint (#11226) --- modules/adnowBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 40dd3b6fd7738f6d444b5846678b872ebab26102 Mon Sep 17 00:00:00 2001 From: Sabau-Adrian-Cnx <163830758+Sabau-Adrian-Cnx@users.noreply.github.com> Date: Mon, 18 Mar 2024 19:15:49 +0200 Subject: [PATCH 560/760] Connatix Bid Adapter: GPP Integration (#11229) * connatix adapter gpp integration * Connatix adapter gpp integration fix and add test --- modules/connatixBidAdapter.js | 1 + test/spec/modules/connatixBidAdapter_spec.js | 5 +++++ 2 files changed, 6 insertions(+) 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/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); }); }); From 4b4648e83486001a7c30ba4fc85de159115f260b Mon Sep 17 00:00:00 2001 From: aleksanderl <109285067+aleskanderl@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:18:20 +0200 Subject: [PATCH 561/760] Taboola Bid Adapter : add for support fledge (#11192) * Support fledge in taboola adapter * Append corner cases handling for the empty or partial response * Swap json parse to safeJsonParse * Rename test param --- modules/taboolaBidAdapter.js | 55 ++- test/spec/modules/taboolaBidAdapter_spec.js | 350 ++++++++++++++++++++ 2 files changed, 401 insertions(+), 4 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 3b26a00c6fb..6e6d89dc921 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice} from '../src/utils.js'; +import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; @@ -151,12 +151,59 @@ export const spec = { if (!serverResponse || !serverResponse.body) { return []; } - + const bids = []; + const fledgeAuctionConfigs = []; if (!serverResponse.body.seatbid || !serverResponse.body.seatbid.length || !serverResponse.body.seatbid[0].bid || !serverResponse.body.seatbid[0].bid.length) { - return []; + if (!serverResponse.body.ext || !serverResponse.body.ext.igbid || !serverResponse.body.ext.igbid.length) { + return []; + } + } else { + bids.push(...converter.fromORTB({response: serverResponse.body, request: request.data}).bids); + } + if (isArray(serverResponse.body.ext?.igbid)) { + serverResponse.body.ext.igbid.forEach((igbid) => { + if (!igbid || !igbid.igbuyer || !igbid.igbuyer.length || !igbid.igbuyer[0].buyerdata) { + return; + } + let buyerdata = safeJSONParse(igbid.igbuyer[0]?.buyerdata) + if (!buyerdata) { + return; + } + const perBuyerSignals = {}; + igbid.igbuyer.forEach(buyerItem => { + if (!buyerItem || !buyerItem.buyerdata || !buyerItem.origin) { + return; + } + let parsedData = safeJSONParse(buyerItem.buyerdata) + if (!parsedData || !parsedData.perBuyerSignals || !(buyerItem.origin in parsedData.perBuyerSignals)) { + return; + } + perBuyerSignals[buyerItem.origin] = parsedData.perBuyerSignals[buyerItem.origin]; + }); + const impId = igbid?.impid; + fledgeAuctionConfigs.push({ + impId, + config: { + seller: buyerdata?.seller, + resolveToConfig: buyerdata?.resolveToConfig, + sellerSignals: {}, + sellerTimeout: buyerdata?.sellerTimeout, + perBuyerSignals, + auctionSignals: {}, + decisionLogicUrl: buyerdata?.decisionLogicUrl, + interestGroupBuyers: buyerdata?.interestGroupBuyers, + perBuyerTimeouts: buyerdata?.perBuyerTimeouts, + }, + }); + }); } - const bids = converter.fromORTB({response: serverResponse.body, request: request.data}).bids; + if (fledgeAuctionConfigs.length) { + return { + bids, + fledgeAuctionConfigs, + }; + } return bids; }, onBidWon: (bid) => { diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 958cd668f63..ca09fbbbcc9 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -692,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 @@ -848,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; From 90822c67e36b8c2e3512fbe0e645bc6494531b96 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 19 Mar 2024 17:00:15 +0100 Subject: [PATCH 562/760] Criteo Bid Adapter: use igi.igs to register fledge auction configs (#11218) Co-authored-by: v.raybaud --- modules/criteoBidAdapter.js | 55 +----- test/spec/modules/criteoBidAdapter_spec.js | 205 +++++++++------------ 2 files changed, 98 insertions(+), 162 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 33eb903ab55..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 = { '*': 500 }; - 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/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 726754f39aa..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: { - '*': 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', - }, + 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: { - '*': 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: '???' - }, + impid: 'test-bidId-2', + config: auctionConfig2, }); }); From 48113b10e02870d84269e54d4e0c81ec690e3c8a Mon Sep 17 00:00:00 2001 From: pashaGhub <48026915+pashaGhub@users.noreply.github.com> Date: Tue, 19 Mar 2024 19:04:05 +0200 Subject: [PATCH 563/760] setupad Bid Adapter: initial commit (#11008) * create setupadBidAdapter * add setupadBidAdapter * update setupadBidAdapter * update metrics collection * update analytics collection * update getUserSyncs * add setupadAnalyticsAdapter.js * test setupadAnalyticsAdapter * remove test: 1 * add GVLID && bug fixes && test updates * remove setupadAnalyticsAdapter * add userID module handling * add GVLID && bug fixes && test updates * remove setupadAnalyticsAdapter * add userID module handling * clean up && seat bugfix * clean up logs * add userID module handling * update md && clean up * Send setupad only on bidRequested * Fix bidResponse and bidWon responses * Improve bidResponse and bidWon logic * Revert changes to specific files * Remove test parameter * Fix multiple bidResponse and bidTimeout calls to getPixelUrl * eslint errors fixes(brackets added) * Add extra checks for events * Fix BIDDER_CODE const * update reporting endpoint * update setupadBidAdapter_spec.js REPORT_ENDPOINT * update readme * Revert "Merge branch 'prebid:master' into setupad-adapter" This reverts commit 1c14dbe88883e4c25bd303e2094c72f64e361877, reversing changes made to 7fe9ea569e144c37beeab83bda98564b543c7b09. * Revert "Revert "Merge branch 'prebid:master' into setupad-adapter"" This reverts commit a34e3e4983418d74e57055a12dc61e554c995089. * # This is a combination of 20 commits. # This is the 1st commit message: add setupadBidAdapter # This is the commit message #2: update setupadBidAdapter # This is the commit message #3: update metrics collection # This is the commit message #4: update analytics collection # This is the commit message #5: update getUserSyncs # This is the commit message #6: add setupadAnalyticsAdapter.js # This is the commit message #7: test setupadAnalyticsAdapter # This is the commit message #8: remove test: 1 # This is the commit message #9: add GVLID && bug fixes && test updates # This is the commit message #10: remove setupadAnalyticsAdapter # This is the commit message #11: add userID module handling # This is the commit message #12: clean up && seat bugfix # This is the commit message #13: add userID module handling # This is the commit message #14: add GVLID && bug fixes && test updates # This is the commit message #15: remove setupadAnalyticsAdapter # This is the commit message #16: add userID module handling # This is the commit message #17: clean up logs # This is the commit message #18: update md && clean up # This is the commit message #19: Send setupad only on bidRequested # This is the commit message #20: Fix bidResponse and bidWon responses * # This is a combination of 22 commits.tree 8abae7e6dffc9a21ad11770713ba485fc610028a parent cecfce3db84fdeca9bb9b66005f3c7aa6106d951 author pavel 1706627437 +0200 committer pavel 1706627437 +0200 gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEqGYI3KX/FkbObQG8FABtd4pCs/AFAmW5EW0ACgkQFABtd4pC s/CK3w//WWJSFUlycnnNKTV2XfdcBjooOeZZvjpXVthwr09CCC4uO//kw4bPluhn f5fcVFdXzrY1AZ6ch8Wo3msX/Pkso014jIGd5aIWcHpNYFtffACwH/40Y8AcJNZd bsOZxVK0awPTz/RihC5eY+0J3cP+iFWP/FlYJoHEQIBXq/Eg6mWoAhxwpL/JvxbY QbLFWsRn2ckQ6ftOZgm3/jh8VLaG1zWbWImlWEs5Zel+CorJBTniTj58VbApelYD TFMgbSR2I4NGVaqNIrHePnSMsDATxalQ2nZPwY6raKCHWIbvoUPIn/OpDMMbKgC7 nCwounNmObxFVoj3xusAZppzHpKPasY8xKWb2Kr7zfhZArsOMC6B7fYqQNK0cWG3 8RR/10oheJD9M2kRlfLiqnRv7ExY08SQ/ZMo9LA8BeRUGBXhh6++8FKhKIHvX1gL k1R5W6c+NNWP+PDFsmrFpMn+LpYdl84I7yfYK5dHuw80od7f1wuAVYpswi6Cziy9 /KY6/rfENvUrGTmWSh5GdDBel89ACCfFkasIKB92xhzKTfjzF/DXkc8XQZOMbt1j CsILgWMNfLPMo4Dlgdx/tYCSLLBNEtZ1/hhUcFQ3+0TzLf0GtMkvMnlBnDinqe1n 1P30fQ2I5W5NJKDPrCOnRymI6QOAPFXtMF11R81mbB9H8asft/E= =oJtZ -----END PGP SIGNATURE----- bugfixes # This is the commit message #22: Remove test parameter * # This is a combination of 26 commits. parent cecfce3db84fdeca9bb9b66005f3c7aa6106d951 author pavel 1706627437 +0200 committer pavel 1706627437 +0200 gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEqGYI3KX/FkbObQG8FABtd4pCs/AFAmW5EW0ACgkQFABtd4pC s/CK3w//WWJSFUlycnnNKTV2XfdcBjooOeZZvjpXVthwr09CCC4uO//kw4bPluhn f5fcVFdXzrY1AZ6ch8Wo3msX/Pkso014jIGd5aIWcHpNYFtffACwH/40Y8AcJNZd bsOZxVK0awPTz/RihC5eY+0J3cP+iFWP/FlYJoHEQIBXq/Eg6mWoAhxwpL/JvxbY QbLFWsRn2ckQ6ftOZgm3/jh8VLaG1zWbWImlWEs5Zel+CorJBTniTj58VbApelYD TFMgbSR2I4NGVaqNIrHePnSMsDATxalQ2nZPwY6raKCHWIbvoUPIn/OpDMMbKgC7 nCwounNmObxFVoj3xusAZppzHpKPasY8xKWb2Kr7zfhZArsOMC6B7fYqQNK0cWG3 8RR/10oheJD9M2kRlfLiqnRv7ExY08SQ/ZMo9LA8BeRUGBXhh6++8FKhKIHvX1gL k1R5W6c+NNWP+PDFsmrFpMn+LpYdl84I7yfYK5dHuw80od7f1wuAVYpswi6Cziy9 /KY6/rfENvUrGTmWSh5GdDBel89ACCfFkasIKB92xhzKTfjzF/DXkc8XQZOMbt1j CsILgWMNfLPMo4Dlgdx/tYCSLLBNEtZ1/hhUcFQ3+0TzLf0GtMkvMnlBnDinqe1n 1P30fQ2I5W5NJKDPrCOnRymI6QOAPFXtMF11R81mbB9H8asft/E= =oJtZ -----END PGP SIGNATURE----- bugfixes # This is the commit message #22: Remove test parameter # This is the commit message #23: Fix multiple bidResponse and bidTimeout calls to getPixelUrl # This is the commit message #25: eslint errors fixes(brackets added) # This is the commit message #26: Add extra checks for events * parent 75178b929c01b37d8bb2e83572ed83abf5849922 author pavel 1706627694 +0200 committer pavel 1706627694 +0200 gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEqGYI3KX/FkbObQG8FABtd4pCs/AFAmW5Em4ACgkQFABtd4pC s/BBUQ/+NXyHoxPM185YJLG9M1ySC/5vTT9W5mfwQ93cVDLCeuGnpsnmi4S21NuQ b7gSeokFjwztvVOUmh/xqMp4lTsvL53TUd00b1k4KGVSqgcF00Foit5g8fOGLYsI DAoqphYV6MWjpAun+II+ELY8QUkHR1cjTc7PEGtmf+8RnptGVdyJ6C9Ab8u9TQTY Apj5Srhfo3Tl8S+WScOxwwB/uqEJR4fhIrJyzFzdLDEb2olSPyrQUs87vQXlhEnK buPEg2F5JsRH6sw11Xp3TFNSZGxNnBSlTh9dixou5md4yRCv5a2TMef667N0BVDp lGgc7mCrRKXyqzphmmeHudiscEGFjtUPObXoHutSVw22wdARFCTpNFKBLLFn4v8o Zv1OvFdNprvHsoeW0HVlZdU7OKnDTRrko6DHk2AahxojjvAFEWuDsGYZNjhdQwRR lK1zm+SFQnKI0Eojd+f84fvKod9geGs640jyH/x5R4eYm4yjZb8SkRtd3cca88wS OuGq9LIkbU428b46l7VnDwudldTXPUU8eKfUtFRjdGtIWH9I3tK6TsRoCfTcXkv0 smxYiiU1XHjAkkPFWQWEeFdfZ071snFKVWouU0AoKiq+PdRoS8+3AJqIQUjlA2sH AybnSkv9KxY/Rs1bnvMubsQm1GF66qVrbxBU6FILBv1JZYwj4yA= =Gbog -----END PGP SIGNATURE----- bugfixes update setupadBidAdapter_spec.js REPORT_ENDPOINT update readme Revert "Merge branch 'prebid:master' into setupad-adapter" This reverts commit 1c14dbe88883e4c25bd303e2094c72f64e361877, reversing changes made to 7fe9ea569e144c37beeab83bda98564b543c7b09. Revert "Revert "Merge branch 'prebid:master' into setupad-adapter"" This reverts commit a34e3e4983418d74e57055a12dc61e554c995089. * change double quote to single quote --------- Co-authored-by: pavel Co-authored-by: Elgars Grodnis * bugfix setupadBidAdapter remove getAdEl, spelling correction * add onBidWon event onBidWon event handling moved from custom to native onBidWon method * minor bugfixes && remove funk getSiteObj && getDeviceObj --------- Co-authored-by: pavel Co-authored-by: Elgars Grodnis --- modules/setupadBidAdapter.js | 271 +++++++++++++++ modules/setupadBidAdapter.md | 35 ++ test/spec/modules/setupadBidAdapter_spec.js | 348 ++++++++++++++++++++ 3 files changed, 654 insertions(+) create mode 100644 modules/setupadBidAdapter.js create mode 100644 modules/setupadBidAdapter.md create mode 100644 test/spec/modules/setupadBidAdapter_spec.js 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/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; + }); + }); +}); From dfdf9123778371a11281d5e04de7046b502adc91 Mon Sep 17 00:00:00 2001 From: Aymeric Le Corre Date: Tue, 19 Mar 2024 18:45:25 +0100 Subject: [PATCH 564/760] New Bidder Adapter: Adlive Plus (#11176) * New Bidder adpater : Adlive Plus * remove adlive plus md file (alias) --- modules/luceadBidAdapter.js | 8 +++++--- modules/luceadBidAdapter.md | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index 299bd47a8e4..ab7f96c4e60 100644 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -5,15 +5,17 @@ import {getUniqueIdentifierStr, logInfo, deepSetValue} from '../src/utils.js'; import {fetch} from '../src/ajax.js'; const bidderCode = 'lucead'; +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.lucead.com/go'; const defaultCurrency = 'EUR'; const defaultTtl = 500; +const aliases = ['adliveplus']; function isDevEnv() { - return location.hostname.endsWith('.ngrok-free.app') || location.href.startsWith('https://ayads.io/test'); + return location.hash.includes('prebid-dev') || location.href.startsWith('https://ayads.io/test'); } function isBidRequestValid(bidRequest) { @@ -21,7 +23,7 @@ function isBidRequestValid(bidRequest) { } export function log(msg, obj) { - logInfo('Lucead - ' + msg, obj); + logInfo(`${bidderName} - ${msg}`, obj); } function buildRequests(bidRequests, bidderRequest) { @@ -149,7 +151,7 @@ function onTimeout(timeoutData) { export const spec = { code: bidderCode, // gvlid: BIDDER_GVLID, - aliases: [], + aliases, isBidRequestValid, buildRequests, interpretResponse, diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md index d12d081f0b7..953c911cd2b 100644 --- a/modules/luceadBidAdapter.md +++ b/modules/luceadBidAdapter.md @@ -18,9 +18,9 @@ const adUnits = [ sizes: [[300, 250]], bids: [ { - bidder: "lucead", + bidder: 'lucead', params: { - placementId: '1', + placementId: '2', } } ] From 1d66b92d8bfa155f3663e7ed06da1e54c4b01dc6 Mon Sep 17 00:00:00 2001 From: tudou <42998776+lhxx121@users.noreply.github.com> Date: Wed, 20 Mar 2024 05:15:08 +0800 Subject: [PATCH 565/760] Discovery Bid Adapter : support topics (#11209) * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test resolve conflict * Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test * Discovery Bid Adapter : synchronize mguid from third party cookie to first party cookie * test * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : Extend the expiration time of pmguid * Discovery Bid Adapter : support topics * Discovery Bid Adapter : support topics * Discovery Bid Adapter : support topics * Discovery Bid Adapter : support topics --------- Co-authored-by: lvhuixin --- modules/discoveryBidAdapter.js | 2 ++ modules/topicsFpdModule.js | 3 +++ modules/topicsFpdModule.md | 4 ++++ test/spec/modules/discoveryBidAdapter_spec.js | 23 +++++++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index aa497b99d00..de2fd3c3a94 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -437,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(); @@ -463,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/topicsFpdModule.js b/modules/topicsFpdModule.js index 748242142c4..715f1ca735a 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -41,6 +41,9 @@ const bidderIframeList = { }, { bidder: 'taboola', iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html' + }, { + bidder: 'discovery', + iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html' }] } diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index e8daded4439..8ebddacf613 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -60,6 +60,10 @@ pbjs.setConfig({ bidder: 'taboola', iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html', expiry: 7 // Configurable expiry days + }, { + bidder: 'discovery', + iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html', + expiry: 7 // Configurable expiry days }] } .... diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 4fb4c29b99b..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() { From 075abb7f97b3d3abcb95f91951fc8744578e1583 Mon Sep 17 00:00:00 2001 From: Matthieu Wipliez <89922776+github-matthieu-wipliez@users.noreply.github.com> Date: Wed, 20 Mar 2024 01:19:13 +0100 Subject: [PATCH 566/760] Autoplay detection library: initial release && Teads Bid Adapter: detect autoplay (#11222) * Add autoplay library * Filter out bids with needAutoplay if autoplay is disabled * Refactoring + add test * Simplify logic for filtering bids * Start detection in autoplay.js directly --- libraries/autoplayDetection/autoplay.js | 42 ++++++++++++++ modules/teadsBidAdapter.js | 18 ++++-- test/spec/modules/teadsBidAdapter_spec.js | 70 ++++++++++++++++++++++- 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 libraries/autoplayDetection/autoplay.js 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/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index d03782611e4..1108c12c822 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -1,6 +1,7 @@ import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParameter} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; +import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -120,11 +121,18 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidderRequest) { - const bidResponses = []; serverResponse = serverResponse.body; - if (serverResponse.responses) { - serverResponse.responses.forEach(function (bid) { + if (!serverResponse.responses) { + return []; + } + + const autoplayEnabled = isAutoplayEnabled(); + return serverResponse.responses + .filter((bid) => + // ignore this bid if it requires autoplay but it is not enabled on this browser + !bid.needAutoplay || autoplayEnabled + ).map((bid) => { const bidResponse = { cpm: bid.cpm, width: bid.width, @@ -146,10 +154,8 @@ export const spec = { if (bid?.ext?.dsa) { bidResponse.meta.dsa = bid.ext.dsa; } - bidResponses.push(bidResponse); + return bidResponse; }); - } - return bidResponses; } }; 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': { From 91512b988821097a087fd44d3afaa91f3107c3bd Mon Sep 17 00:00:00 2001 From: awiackiewicz <40166510+awiackiewicz@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:08:53 +0100 Subject: [PATCH 567/760] Adquery Bid Adapter: bidWon bugfix (#11227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * removed ad data from bidWon event handler * removed ad data from bidWon event handler --------- Co-authored-by: Adam Wiąckiewicz --- modules/adqueryBidAdapter.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 = { From b1d4679377f49b481d4ff00f48a1404bde2fb92a Mon Sep 17 00:00:00 2001 From: Justas Pupelis Date: Wed, 20 Mar 2024 18:59:48 +0200 Subject: [PATCH 568/760] Adf Bid Adapter: set vastUrl (#11243) * Set vastUrl on adfBidAdapter if bidResponse has nurl * mistype --- modules/adfBidAdapter.js | 9 ++++++++- test/spec/modules/adfBidAdapter_spec.js | 26 +++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) 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/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index 5612af8094c..d4c5f5c3c38 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -1321,6 +1321,32 @@ describe('Adf adapter', function () { assert.equal(bids[0].meta.mediaType, 'video'); }); + it('should set vastUrl if nurl is present in response', function () { + let vastUrl = 'http://url.to/vast' + let serverResponse = { + body: { + seatbid: [{ + bid: [{ impid: '1', adm: '', nurl: vastUrl, ext: { prebid: { type: 'video' } } }] + }] + } + }; + let bidRequest = { + data: {}, + bids: [ + { + bidId: 'bidId1', + params: { mid: 1000 } + } + ] + }; + + bids = spec.interpretResponse(serverResponse, bidRequest); + assert.equal(bids.length, 1); + assert.equal(bids[0].vastUrl, vastUrl); + assert.equal(bids[0].mediaType, 'video'); + assert.equal(bids[0].meta.mediaType, 'video'); + }); + it('should add renderer for outstream bids', function () { let serverResponse = { body: { From 31f7581b630ef120b86fcf3718632fd4d291d22c Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:56:55 +0200 Subject: [PATCH 569/760] Magnite Analytics Adapter: add indication of cookieless traffic (#11241) --- modules/magniteAnalyticsAdapter.js | 16 +++- .../modules/magniteAnalyticsAdapter_spec.js | 73 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) 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/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', () => { From 9648399acfec524a8508550b13ca06f88e25868c Mon Sep 17 00:00:00 2001 From: Jason Quaccia Date: Thu, 21 Mar 2024 07:31:14 -0700 Subject: [PATCH 570/760] PubMatic Bid Adapter: Support for DSA (#11245) --- modules/pubmaticBidAdapter.js | 10 +++++ test/spec/modules/pubmaticBidAdapter_spec.js | 43 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 68431bcc383..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 = { @@ -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/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 () { From 2fbdf0695fca93697da18ee7206dd5f541b5690f Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Thu, 21 Mar 2024 18:42:52 -0400 Subject: [PATCH 571/760] Kargo Bid Adapter: Adds ortb2 and ortb2Imp to request (#11248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * adds full ortb2 and ortb2Imp to request * fixes tests --------- Co-authored-by: “Nick <“nick.llerandi”@kargo.com> --- modules/kargoBidAdapter.js | 16 +++- test/spec/modules/kargoBidAdapter_spec.js | 97 ++++++++++++++++++++++- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index b72601e5ebb..e86d7022987 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.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 @@ -478,6 +481,11 @@ function getImpression(bid) { } } + // Add full ortb2Imp object as backup + if (bid.ortb2Imp) { + imp.ext = { ortb2Imp: bid.ortb2Imp }; + } + if (bid.mediaTypes) { const { banner, video, native } = bid.mediaTypes; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 40f8833c6ec..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' + } + } } }, { @@ -492,7 +507,21 @@ describe('kargo adapter tests', function () { fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' }, - floor: 2 + floor: 2, + ext: { + ortb2Imp: { + ext: { + tid: '20202', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + }, + pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } + } }, { code: '303', @@ -505,7 +534,21 @@ describe('kargo adapter tests', function () { fpd: { gpid: '/22558409563,18834096/dfy_mobile_adhesion' }, - floor: 3 + floor: 3, + ext: { + ortb2Imp: { + ext: { + tid: '30303', + data: { + adServer: { + name: 'gam', + adslot: '/22558409563,18834096/dfy_mobile_adhesion' + } + }, + gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } + } + } } ], socan: { @@ -555,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' + }, + ] + }, + ] + } + } } }; From 708a696e1415bf0d915644f819a325020fdc6b74 Mon Sep 17 00:00:00 2001 From: Mikhail Malkov Date: Fri, 22 Mar 2024 01:46:38 +0300 Subject: [PATCH 572/760] NextMillennium: Added some parameters for video (#11235) * added support for gpp consent string * changed test for nextMillenniumBidAdapter * added some tests * added site.pagecat, site.content.cat and site.content.language to request * lint fix * formated code * formated code * formated code * pachage-lock with prebid * pachage-lock with prebid * formatted code * added device.sua, user.eids * formatted * fixed tests * fixed bug functio getSua * added support some parameters for video --- modules/nextMillenniumBidAdapter.js | 8 ++++++++ test/spec/modules/nextMillenniumBidAdapter_spec.js | 1 + 2 files changed, 9 insertions(+) diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 18f2b461142..de91b508125 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -33,6 +33,8 @@ const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS_DEFAULT = { api: undefined, + context: undefined, + delivery: undefined, linearity: undefined, maxduration: undefined, mimes: [ @@ -43,8 +45,14 @@ const VIDEO_PARAMS_DEFAULT = { minduration: undefined, placement: undefined, + plcmt: undefined, + playbackend: undefined, playbackmethod: undefined, + pos: undefined, protocols: undefined, + skip: undefined, + skipafter: undefined, + skipmin: undefined, startdelay: undefined, }; diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index 5c0f5fc3f4d..ff58671b17b 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -63,6 +63,7 @@ describe('nextMillenniumBidAdapterTests', () => { mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript'], api: [2], placement: 1, + plcmt: 1, w: 400, h: 300, }, From 9088112e3dd4dd7fd60001e679ae2f65b1956b29 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 22 Mar 2024 06:50:08 -0700 Subject: [PATCH 573/760] Debugging module: add PAAPI support (#11240) * Debugging module: add PAAPI support --- modules/debugging/bidInterceptor.js | 27 +++++++++++-- modules/debugging/debugging.js | 8 +++- modules/debugging/pbsInterceptor.js | 17 ++++++++- src/adapters/bidderFactory.js | 20 +++++----- test/spec/modules/debugging_mod_spec.js | 50 +++++++++++++++++++++++-- 5 files changed, 100 insertions(+), 22 deletions(-) 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/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 3d55f2c06af..4f9237fc8d3 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -293,15 +293,13 @@ export function newBidder(spec) { onTimelyResponse(spec.code); responses.push(resp) }, - onFledgeAuctionConfigs: (fledgeAuctionConfigs) => { - fledgeAuctionConfigs.forEach((fledgeAuctionConfig) => { - const bidRequest = bidRequestMap[fledgeAuctionConfig.bidId]; - if (bidRequest) { - addComponentAuction(bidRequest, fledgeAuctionConfig.config); - } else { - logWarn('Received fledge auction configuration for an unknown bidId', fledgeAuctionConfig); - } - }); + onPaapi: (paapiConfig) => { + const bidRequest = bidRequestMap[paapiConfig.bidId]; + if (bidRequest) { + addComponentAuction(bidRequest, paapiConfig.config); + } else { + logWarn('Received fledge auction configuration for an unknown bidId', paapiConfig); + } }, // If the server responds with an error, there's not much we can do beside logging. onError: (errorMessage, error) => { @@ -378,7 +376,7 @@ export function newBidder(spec) { * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse * @param onCompletion {function()} invoked once when all bid requests have been processed */ -export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onFledgeAuctionConfigs, onError, onBid, onCompletion}) { +export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); @@ -427,7 +425,7 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe let bids; // Extract additional data from a structured {BidderAuctionResponse} response if (response && isArray(response.fledgeAuctionConfigs)) { - onFledgeAuctionConfigs(response.fledgeAuctionConfigs); + response.fledgeAuctionConfigs.forEach(onPaapi); bids = response.bids; } else { bids = response; 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; From d9245b02ca19ebe3f9c4f386ff3f41b09333a99b Mon Sep 17 00:00:00 2001 From: Fatih Kaya Date: Fri, 22 Mar 2024 18:12:09 +0300 Subject: [PATCH 574/760] AdMatic Bid Adapter : gvlid and video renderer feature added (#11212) * Admatic Bidder Adaptor * Update admaticBidAdapter.md * Update admaticBidAdapter.md * remove floor parameter * Update admaticBidAdapter.js * Admatic Bid Adapter: alias and bid floor features activated * Admatic adapter: host param control changed * Alias name changed. * Revert "Admatic adapter: host param control changed" This reverts commit de7ac85981b1ba3ad8c5d1dc95c5dadbdf5b9895. * added alias feature and host param * Revert "added alias feature and host param" This reverts commit 6ec8f4539ea6be403a0d7e08dad5c7a5228f28a1. * Revert "Alias name changed." This reverts commit 661c54f9b2397e8f25c257144d73161e13466281. * Revert "Admatic Bid Adapter: alias and bid floor features activated" This reverts commit 7a2e0e29c49e2f876b68aafe886b336fe2fe6fcb. * Revert "Update admaticBidAdapter.js" This reverts commit 7a845b7151bbb08addfb58ea9bd5b44167cc8a4e. * Revert "remove floor parameter" This reverts commit 7a23b055ccd4ea23d23e73248e82b21bc6f69d90. * Admatic adapter: host param control && Add new Bidder * Revert "Admatic adapter: host param control && Add new Bidder" This reverts commit 3c797b120c8e0fe2b851381300ac5c4b1f92c6e2. * commit new features * Update admaticBidAdapter.js * updated for coverage * sync updated * Update adloader.js * AdMatic Bidder: development of user sync url * Update admaticBidAdapter.js * Set currency for AdserverCurrency: bug fix * Update admaticBidAdapter.js * update * admatic adapter video params update * Update admaticBidAdapter.js * update * Update admaticBidAdapter.js * update * update * Update admaticBidAdapter_spec.js * Update admaticBidAdapter.js * Update admaticBidAdapter.js * Revert "Update admaticBidAdapter.js" This reverts commit 1216892fe55e5ab24dda8e045ea007ee6bb40ff8. * Revert "Update admaticBidAdapter.js" This reverts commit b1929ece33bb4040a3bcd6b9332b50335356829c. * Revert "Update admaticBidAdapter_spec.js" This reverts commit 1ca659798b0c9b912634b1673e15e54e547b81e7. * Revert "update" This reverts commit 689ce9d21e08c27be49adb35c5fd5205aef5c35c. * Revert "update" This reverts commit f381a453f9389bebd58dcfa719e9ec17f939f338. * Revert "Update admaticBidAdapter.js" This reverts commit 38fd7abec701d8a4750f9e95eaeb40fb67e9f0e6. * Revert "update" This reverts commit a5316e74b612a5b2cd16cf42586334321fc87770. * Revert "Update admaticBidAdapter.js" This reverts commit 60a28cae302b711366dab0bff9f49b11862fb8ee. * Revert "admatic adapter video params update" This reverts commit 31e69e88fd9355e143f736754ac2e47fe49b65b6. * update * Update admaticBidAdapter.js * Update admaticBidAdapter_spec.js * mime_type add * add native adapter * AdMatic Adapter: Consent Management * added gvlid --- modules/admaticBidAdapter.js | 104 +++++--- test/spec/modules/admaticBidAdapter_spec.js | 266 +++++++++++++++++--- 2 files changed, 299 insertions(+), 71 deletions(-) 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/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index b4d84634962..8730f2c1a0d 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -130,7 +130,7 @@ describe('admaticBidAdapter', () => { ], 'mediatype': {}, 'type': 'banner', - 'id': '2205da7a81846b', + 'id': 1, 'floors': { 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, @@ -434,7 +434,7 @@ describe('admaticBidAdapter', () => { 'h': 90 } ], - 'id': '2205da7a81846b', + 'id': 1, 'mediatype': {}, 'type': 'banner', 'floors': { @@ -802,7 +802,10 @@ describe('admaticBidAdapter', () => { 'price': 0.01, 'type': 'banner', 'bidder': 'admatic', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'adomain': ['admatic.com.tr'], 'party_tag': '
', 'iurl': 'https://www.admatic.com.tr' @@ -814,7 +817,10 @@ describe('admaticBidAdapter', () => { 'height': 250, 'price': 0.01, 'type': 'video', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': '', @@ -822,25 +828,15 @@ describe('admaticBidAdapter', () => { }, { 'id': 3, - 'creative_id': '3741', - 'width': 300, - 'height': 250, - 'price': 0.01, - 'type': 'video', - 'mime_type': 'iframe', - 'bidder': 'admatic', - 'adomain': ['admatic.com.tr'], - 'party_tag': 'https://www.admatic.com.tr', - 'iurl': 'https://www.admatic.com.tr' - }, - { - 'id': 4, 'creative_id': '3742', 'width': 1, 'height': 1, 'price': 0.01, 'type': 'native', - 'mime_type': 'iframe', + 'mime_type': { + 'name': 'backfill', + 'force': false + }, 'bidder': 'admatic', 'adomain': ['admatic.com.tr'], 'party_tag': '{"native":{"ver":"1.1","assets":[{"id":1,"title":{"text":"title"}},{"id":4,"data":{"value":"body"}},{"id":5,"data":{"value":"sponsored"}},{"id":6,"data":{"value":"cta"}},{"id":2,"img":{"url":"https://www.admatic.com.tr","w":1200,"h":628}},{"id":3,"img":{"url":"https://www.admatic.com.tr","w":640,"h":480}}],"link":{"url":"https://www.admatic.com.tr"},"imptrackers":["https://www.admatic.com.tr"]}}', @@ -863,7 +859,10 @@ describe('admaticBidAdapter', () => { ad: '
', creativeId: '374', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -877,11 +876,13 @@ describe('admaticBidAdapter', () => { currency: 'TRY', mediaType: 'video', netRevenue: true, - vastImpUrl: 'https://www.admatic.com.tr', vastXml: '', creativeId: '3741', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -890,24 +891,6 @@ describe('admaticBidAdapter', () => { { requestId: 3, cpm: 0.01, - width: 300, - height: 250, - currency: 'TRY', - mediaType: 'video', - netRevenue: true, - vastImpUrl: 'https://www.admatic.com.tr', - vastXml: 'https://www.admatic.com.tr', - creativeId: '3741', - meta: { - model: 'iframe', - advertiserDomains: ['admatic.com.tr'] - }, - ttl: 60, - bidder: 'admatic' - }, - { - requestId: 4, - cpm: 0.01, width: 1, height: 1, currency: 'TRY', @@ -933,7 +916,10 @@ describe('admaticBidAdapter', () => { }, creativeId: '3742', meta: { - model: 'iframe', + model: { + 'name': 'backfill', + 'force': false + }, advertiserDomains: ['admatic.com.tr'] }, ttl: 60, @@ -944,8 +930,206 @@ describe('admaticBidAdapter', () => { ext: { 'cur': 'TRY', 'type': 'admatic' - } + }, + imp: [ + { + 'size': [ + { + 'w': 320, + 'h': 100 + } + ], + 'type': 'banner', + 'mediatype': {}, + 'ext': { + 'instl': 0, + 'gpid': 'desktop-standard', + 'pxid': [ + '1111111111' + ], + 'pxtype': 'pixad', + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'desktop-standard' + }, + 'ae': 1 + }, + 'id': 1, + 'floors': { + 'banner': { + '320x100': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + }, + { + 'size': [ + { + 'w': 320, + 'h': 100 + } + ], + 'type': 'video', + 'mediatype': { + 'context': 'instream', + 'mimes': [ + 'video/mp4' + ], + 'maxduration': 240, + 'api': [ + 1, + 2 + ], + 'playerSize': [ + [ + 320, + 100 + ] + ], + 'protocols': [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + 'skip': 0, + 'playbackmethod': [ + 2 + ], + 'linearity': 1, + 'placement': 2, + 'plcmt': 4 + }, + 'ext': { + 'gpid': 'outstream-desktop-standard', + 'pxtype': 'pixad', + 'pxid': [ + '1111111111' + ], + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'outstream-desktop-standard' + }, + 'ae': 1 + }, + 'id': 2, + 'floors': { + 'video': { + '320x100': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + }, + { + 'size': [ + { + 'w': 1, + 'h': 1 + } + ], + 'type': 'native', + 'mediatype': { + 'sendTargetingKeys': false, + 'ortb': { + 'ver': '1.1', + 'context': 2, + 'plcmttype': 1, + 'privacy': 1, + 'assets': [ + { + 'id': 1, + 'required': 1, + 'title': { + 'len': 120 + } + }, + { + 'id': 2, + 'required': 1, + 'img': { + 'type': 3, + 'w': 640, + 'h': 480 + } + }, + { + 'id': 3, + 'required': 0, + 'img': { + 'type': 1, + 'w': 640, + 'h': 480 + } + }, + { + 'id': 4, + 'required': 0, + 'data': { + 'type': 2 + } + }, + { + 'id': 5, + 'required': 0, + 'data': { + 'type': 1 + } + }, + { + 'id': 6, + 'required': 0, + 'data': { + 'type': 11 + } + } + ], + 'eventtrackers': [ + { + 'event': 1, + 'methods': [ + 1, + 2 + ] + } + ] + } + }, + 'ext': { + 'gpid': 'native-desktop-standard', + 'pxtype': 'pixad', + 'pxid': [ + '1111111111' + ], + 'ortbstatus': true, + 'viewability': 100, + 'data': { + 'pbadslot': 'native-desktop-standard' + }, + 'ae': 1 + }, + 'id': 3, + 'floors': { + 'native': { + '*': { + 'floor': 0.1, + 'currency': 'TRY' + } + } + } + } + ] }; + let result = spec.interpretResponse(bids, {data: request}); expect(result).to.eql(expectedResponse); }); From 4bad7e4c4c0f0fe36321e9ab2d1f1211e79f4fec Mon Sep 17 00:00:00 2001 From: Scott Sundahl <37344964+ssundahlTTD@users.noreply.github.com> Date: Mon, 25 Mar 2024 06:56:31 -0600 Subject: [PATCH 575/760] Uid2 Token Gen Library: Allow EUID key prefixes (#11250) * initial technical implementation * initial technical implementation * test and doc update * optout check in encrypted payload * fixed cstg example config * allow EUID key prefix * typo --- modules/uid2IdSystem_shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index 102d217a658..f3702069d10 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -196,7 +196,7 @@ if (FEATURES.UID2_CSTG) { _logWarn('CSTG opts.serverPublicKey must be a string'); return false; } - const serverPublicKeyPrefix = /^UID2-X-[A-Z]-.+/; + const serverPublicKeyPrefix = /^(UID2|EUID)-X-[A-Z]-.+/; if (!serverPublicKeyPrefix.test(opts.serverPublicKey)) { _logWarn( `CSTG opts.serverPublicKey must match the regular expression ${serverPublicKeyPrefix}` From e5da9e409ad2c165be93c32967f89950088de4e4 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:24:19 +0200 Subject: [PATCH 576/760] Update DM wrappers TTL default (#11255) --- modules/rubiconBidAdapter.js | 7 ++----- test/spec/modules/rubiconBidAdapter_spec.js | 19 ++++--------------- 2 files changed, 6 insertions(+), 20 deletions(-) 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/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 09418caef53..55e8909f6c8 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -2268,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); @@ -3087,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); @@ -3104,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); @@ -3916,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'); @@ -4008,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'); From af20eda57d22ee59fa04802c867b13cfd2986a98 Mon Sep 17 00:00:00 2001 From: "Adserver.Online" <61009237+adserver-online@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:36:29 +0200 Subject: [PATCH 577/760] Aso Bid Adapter: refactoring to use ortb converter (#11249) * Refactoring to use ortConverter * +default currency * Tests refactored * UsersSyncs: url formatting fixed * Native tests added --------- Co-authored-by: dev --- modules/asoBidAdapter.js | 332 +++++---------------- modules/asoBidAdapter.md | 5 +- test/spec/modules/asoBidAdapter_spec.js | 380 ++++++++++++++++-------- 3 files changed, 334 insertions(+), 383 deletions(-) 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/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); + }); }); }); }); From c5e8af7fb14a62ca04e4e60082cefb28030ea9ce Mon Sep 17 00:00:00 2001 From: Alexandru Date: Mon, 25 Mar 2024 22:08:09 +0200 Subject: [PATCH 578/760] OMS Adapter: add gpid support (#11238) * OMS Adapter: add gpid support * Oms Adapter: move gpid to imp.ext * Oms Adapter: cover with tests gpid integration --- modules/omsBidAdapter.js | 15 ++++++++++++++- test/spec/modules/omsBidAdapter_spec.js | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) 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/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}); From e3789a68c4d32b6f03df65590ac2a6483d5bc321 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 26 Mar 2024 14:04:40 +0000 Subject: [PATCH 579/760] Prebid 8.42.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19d7e70ff37..0ec87dc6c39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.42.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9859d39606a..129f6328a99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.42.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From b64724eafd52caf0a91fdbc8a94fe00200ff5ea2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Tue, 26 Mar 2024 14:04:41 +0000 Subject: [PATCH 580/760] Increment version to 8.43.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ec87dc6c39..3383b2f554e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.42.0", + "version": "8.43.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 129f6328a99..df0c12a3e16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.42.0", + "version": "8.43.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8e5b35c23d62bb8a891e28fc96087fe802ba8f98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 08:52:38 -0600 Subject: [PATCH 581/760] Bump express from 4.18.2 to 4.19.2 (#11258) Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 78 +++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3383b2f554e..4cb04ff9c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.42.0-pre", + "version": "8.43.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -8326,12 +8326,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -8339,7 +8339,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -9462,9 +9462,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -9481,9 +9481,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -13097,16 +13097,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -23942,9 +23942,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -35599,12 +35599,12 @@ } }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -35612,7 +35612,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -36454,9 +36454,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "continuable-cache": { "version": "0.3.1", @@ -36470,9 +36470,9 @@ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -39248,16 +39248,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -47594,9 +47594,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", From 673f44fded44970ee6392b9c1aa4e824204ff474 Mon Sep 17 00:00:00 2001 From: rishko00 <43280707+rishko00@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:52:59 +0200 Subject: [PATCH 582/760] Smartyads Bid Adapter : add user id fields (#11221) * Smartyads Bid Adapter/add user id fields * smallfix * smallfix * smallfix tests --------- Co-authored-by: vrishko --- modules/smartyadsBidAdapter.js | 9 +++++++-- test/spec/modules/smartyadsBidAdapter_spec.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index b6d5a1711b0..6920983e50d 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -82,6 +82,7 @@ export const spec = { location = winTop.location; logMessage(e); }; + let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -91,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) { @@ -111,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/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'); From 0d681ef78047ac9134c90bf495c9325e2b98f4e8 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Wed, 27 Mar 2024 14:48:31 +0200 Subject: [PATCH 583/760] Adkernel Bid Adapter: add monetix alias (#11264) --- modules/adkernelBidAdapter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 62e0ab29f8a..491808f2602 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -112,7 +112,8 @@ export const spec = { {code: 'qortex'}, {code: 'adpluto'}, {code: 'headbidder'}, - {code: 'digiad'} + {code: 'digiad'}, + {code: 'monetix'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], From ec34fa4bc20628fbe64513b2c11f049eebfffcd0 Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Wed, 27 Mar 2024 10:01:03 -0600 Subject: [PATCH 584/760] Documentation : change day on release notes (#11257) * Change day on release notes * change to by * Update language --- RELEASE_SCHEDULE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_SCHEDULE.md b/RELEASE_SCHEDULE.md index 45f4e6c7dc5..016e3e71cfc 100644 --- a/RELEASE_SCHEDULE.md +++ b/RELEASE_SCHEDULE.md @@ -9,7 +9,7 @@ ## Release Schedule -We aim to push a new release of Prebid.js every week on Tuesday. +We aim to push a new release of Prebid.js each week barring any unforseen circumstances or in observance of holidays. While the releases will be available immediately for those using direct Git access, it will be about a week before the Prebid Org [Download Page](https://docs.prebid.org/download.html) will be updated. From af3e7faf523b878551d1f5f39f5c47b4ea4ca157 Mon Sep 17 00:00:00 2001 From: Parth Shah Date: Thu, 28 Mar 2024 01:11:09 +0530 Subject: [PATCH 585/760] DeepIntent Bid Adapter : add gpp and coppa compliance support (#11239) * add gpp and coppa compliance checks and tests * update test case * resolve comments * read coppa as per updated docs * update test suite * update test suite --- modules/deepintentBidAdapter.js | 14 +++++++++ .../spec/modules/deepintentBidAdapter_spec.js | 31 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 7c24cd6a8f6..0a64ed88ca5 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -98,6 +98,20 @@ export const spec = { deepSetValue(openRtbBidRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } + // GPP Consent + if (bidderRequest?.gppConsent?.gppString) { + deepSetValue(openRtbBidRequest, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(openRtbBidRequest, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(openRtbBidRequest, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(openRtbBidRequest, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + + // coppa compliance + if (bidderRequest?.ortb2?.regs?.coppa) { + deepSetValue(openRtbBidRequest, 'regs.coppa', 1); + } + injectEids(openRtbBidRequest, validBidRequests); return { diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index d2a351b4089..644e9255789 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -357,5 +357,34 @@ describe('Deepintent adapter', function () { let response = spec.interpretResponse(invalidResponse, bRequest); expect(response[0].mediaType).to.equal(undefined); }); - }) + }); + describe('GPP and coppa', function() { + it('Request params check with GPP Consent', function () { + let bidderReq = {gppConsent: {gppString: 'gpp-string-test', applicableSections: [5]}}; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.gpp).to.equal('gpp-string-test'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.gpp).to.equal('gpp-test-string'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + it('should include coppa flag in bid request if coppa is set to true', () => { + let bidderReq = {ortb2: {regs: {coppa: 1}}}; + let bRequest = spec.buildRequests(request, bidderReq); + let data = JSON.parse(bRequest.data); + expect(data.regs.coppa).to.equal(1); + }); + }); }); From f072634e48604f2c3898ed290790ac6c5e78ebc3 Mon Sep 17 00:00:00 2001 From: Alexander Plotnikov <80626793+alexander-plotnikov-openweb@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:41:02 +0200 Subject: [PATCH 586/760] OpenWeb adapter refactoring (#11115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * openwebBidAdapter update * update openwebBidAdapter.md * update openwebBidAdapter.md * OpenWebBidAdapter CR fixes --------- Co-authored-by: Zdravko Kosanovic Co-authored-by: Zdravko Kosanović <41286499+zkosanovic@users.noreply.github.com> --- modules/openwebBidAdapter.js | 604 +++++++++---- modules/openwebBidAdapter.md | 54 +- test/spec/modules/openwebBidAdapter_spec.js | 898 +++++++++++++------- 3 files changed, 1029 insertions(+), 527 deletions(-) diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 547447039da..39bd50f61a9 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -1,249 +1,487 @@ -import {deepAccess, flatten, isArray, isNumber, parseSizesInput} from '../src/utils.js'; +import { + logWarn, + logInfo, + isArray, + isFn, + deepAccess, + isEmpty, + contains, + triggerPixel, + isInteger, + getBidIdParameter, + getDNT +} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {find} from '../src/polyfill.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; -const ENDPOINT = 'https://ghb.spotim.market/v2/auction'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; -const DISPLAY = 'display'; -const syncsCache = {}; +const ADAPTER_VERSION = '6.0.0'; +const TTL = 360; +const DEFAULT_CURRENCY = 'USD'; +const SELLER_ENDPOINT = 'https://hb.openwebmp.com/'; +const MODES = { + PRODUCTION: 'hb-multi', + TEST: 'hb-multi-test' +} +const SUPPORTED_SYNC_METHODS = { + IFRAME: 'iframe', + PIXEL: 'pixel' +} export const spec = { code: BIDDER_CODE, gvlid: 280, - supportedMediaTypes: [VIDEO, BANNER, ADPOD], - isBidRequestValid: function (bid) { - return isNumber(deepAccess(bid, 'params.aid')); - }, - getUserSyncs: function (syncOptions, serverResponses) { - const syncs = []; + version: ADAPTER_VERSION, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid: function (bidRequest) { + if (!bidRequest.params) { + logWarn('no params have been set to OpenWeb adapter'); + return false; + } - function addSyncs(bid) { - const uris = bid.cookieURLs; - const types = bid.cookieURLSTypes || []; + if (!bidRequest.params.org) { + logWarn('org is a mandatory param for OpenWeb adapter'); + return false; + } - if (Array.isArray(uris)) { - uris.forEach((uri, i) => { - const type = types[i] || 'image'; + return true; + }, + buildRequests: function (validBidRequests, bidderRequest) { + const combinedRequestsObject = {}; - if ((!syncOptions.pixelEnabled && type === 'image') || - (!syncOptions.iframeEnabled && type === 'iframe') || - syncsCache[uri]) { - return; - } + // use data from the first bid, to create the general params for all bids + const generalObject = validBidRequests[0]; + const testMode = generalObject.params.testMode; - syncsCache[uri] = true; - syncs.push({ - type: type, - url: uri - }) - }) - } + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + + return { + method: 'POST', + url: getEndpoint(testMode), + data: combinedRequestsObject } + }, + interpretResponse: function ({body}) { + const bidResponses = []; - if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { - isArray(serverResponses) && serverResponses.forEach((response) => { - if (response.body) { - if (isArray(response.body)) { - response.body.forEach(b => { - addSyncs(b); - }) - } else { - addSyncs(response.body) + if (body && body.bids && body.bids.length) { + body.bids.forEach(adUnit => { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || DEFAULT_CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.requestId, + 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; } - }) - } - return syncs; - }, - /** - * Make a server request from the list of BidRequests - * @param bidRequests - * @param adapterRequest - */ - buildRequests: function (bidRequests, adapterRequest) { - const { tag, bids } = bidToTag(bidRequests, adapterRequest); - return [{ - data: Object.assign({}, tag, { BidRequests: bids }), - adapterRequest, - method: 'POST', - url: ENDPOINT - }]; - }, - /** - * Unpack the response from the server into a list of bids - * @param serverResponse - * @param bidderRequest - * @return {Bid[]} An array of bids which were nested inside the server - */ - interpretResponse: function (serverResponse, { adapterRequest }) { - serverResponse = serverResponse.body; - let bids = []; + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } - if (!isArray(serverResponse)) { - return parseRTBResponse(serverResponse, adapterRequest); + bidResponses.push(bidResponse); + }); } - serverResponse.forEach(serverBidResponse => { - bids = flatten(bids, parseRTBResponse(serverBidResponse, adapterRequest)); - }); - - return bids; + return bidResponses; }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + for (const response of serverResponses) { + 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; + } - transformBidParams(params) { - return convertTypes({ - 'aid': 'number', - }, params); + logInfo('onBidWon:', bid); + if (bid.hasOwnProperty('nurl') && bid.nurl.length > 0) { + triggerPixel(bid.nurl); + } } }; -function parseRTBResponse(serverResponse, adapterRequest) { - const isEmptyResponse = !serverResponse || !isArray(serverResponse.bids); - const bids = []; +registerBidder(spec); - if (isEmptyResponse) { - return bids; +/** + * Get floor price + * @param bid {bid} + * @returns {Number} + */ +function getFloor(bid, mediaType, currency) { + if (!isFn(bid.getFloor)) { + return 0; } + let floorResult = bid.getFloor({ + currency: currency, + mediaType: mediaType, + size: '*' + }); + return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; +} - serverResponse.bids.forEach(serverBid => { - const request = find(adapterRequest.bids, (bidRequest) => { - return bidRequest.bidId === serverBid.requestId; - }); +/** + * Get the the ad sizes array from the bid + * @param bid {bid} + * @returns {Array} + */ +function getSizesArray(bid, mediaType) { + let sizesArray = [] - if (serverBid.cpm !== 0 && request !== undefined) { - const bid = createBid(serverBid, request); + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } - bids.push(bid); - } + 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; +} - return bids; +/** + * Get encoded node value + * @param val {string} + * @returns {string} + */ +function getEncodedValIfNotEmpty(val) { + return !isEmpty(val) ? encodeURIComponent(val) : ''; } -function bidToTag(bidRequests, adapterRequest) { - // start publisher env - const tag = { - // TODO: is 'page' the right value here? - Domain: deepAccess(adapterRequest, 'refererInfo.page') - }; - if (config.getConfig('coppa') === true) { - tag.Coppa = 1; +/** + * Get preferred user-sync method based on publisher configuration + * @param bidderCode {string} + * @returns {string} + */ +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 (deepAccess(adapterRequest, 'gdprConsent.gdprApplies')) { - tag.GDPR = 1; - tag.GDPRConsent = deepAccess(adapterRequest, 'gdprConsent.consentString'); + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return SUPPORTED_SYNC_METHODS.PIXEL; } - if (deepAccess(adapterRequest, 'uspConsent')) { - tag.USP = deepAccess(adapterRequest, 'uspConsent'); +} + +/** + * Check if sync rule is supported + * @param syncRule {Object} + * @param bidderCode {string} + * @returns {boolean} + */ +function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; } - if (deepAccess(bidRequests[0], 'schain')) { - tag.Schain = deepAccess(bidRequests[0], 'schain'); + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +/** + * Get the seller endpoint + * @param testMode {boolean} + * @returns {string} + */ +function getEndpoint(testMode) { + return testMode + ? SELLER_ENDPOINT + MODES.TEST + : SELLER_ENDPOINT + MODES.PRODUCTION; +} + +/** + * get device type + * @param uad {ua} + * @returns {string} + */ +function getDeviceType(ua) { + if (/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i + .test(ua.toLowerCase())) { + return '5'; } - if (deepAccess(bidRequests[0], 'userId')) { - tag.UserIds = deepAccess(bidRequests[0], 'userId'); + if (/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i + .test(ua.toLowerCase())) { + return '4'; } - if (deepAccess(bidRequests[0], 'userIdAsEids')) { - tag.UserEids = deepAccess(bidRequests[0], 'userIdAsEids'); + if (/smart[-_\s]?tv|hbbtv|appletv|googletv|hdmi|netcast|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b/i + .test(ua.toLowerCase())) { + return '3'; } - // end publisher env - const bids = []; + return '1'; +} + +function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; - for (let i = 0, length = bidRequests.length; i < length; i++) { - const bid = prepareBidRequests(bidRequests[i]); - bids.push(bid); + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); } - return { tag, bids }; + return bidsArray; } /** - * Parse mediaType - * @param bidReq {object} - * @returns {object} + * Generate bid specific parameters + * @param {bid} bid + * @param {bidderRequest} bidderRequest + * @returns {Object} bid specific params object */ -function prepareBidRequests(bidReq) { - const mediaType = deepAccess(bidReq, 'mediaTypes.video') ? VIDEO : DISPLAY; - const sizes = mediaType === VIDEO ? deepAccess(bidReq, 'mediaTypes.video.playerSize') : deepAccess(bidReq, 'mediaTypes.banner.sizes'); - const bidReqParams = { - 'CallbackId': bidReq.bidId, - 'Aid': bidReq.params.aid, - 'AdType': mediaType, - 'Sizes': parseSizesInput(sizes).join(',') +function generateBidParameters(bid, bidderRequest) { + const {params} = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; + + // fix floor price in case of NAN + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + currency: currency, + floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + loop: getBidIdParameter('bidderRequestsCount', bid), + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', + coppa: 0, }; - bidReqParams.PlacementId = bidReq.adUnitCode; - if (bidReq.params.iframe) { - bidReqParams.AdmType = 'iframe'; + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`); + if (coppa) { + bidObject.coppa = 1; + } + if (mediaType === VIDEO) { - const context = deepAccess(bidReq, 'mediaTypes.video.context'); - if (context === ADPOD) { - bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video'); + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + // verify playbackMethod is of type integer array, or integer only. + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } + + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; } } - return bidReqParams; + + return bidObject; } -/** - * Prepare all parameters for request - * @param bidderRequest {object} - * @returns {object} - */ -function getMediaType(bidderRequest) { - return deepAccess(bidderRequest, 'mediaTypes.video') ? VIDEO : BANNER; +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; } /** - * Configure new bid by response - * @param bidResponse {object} - * @param bidRequest {Object} - * @returns {object} + * Generate params that are common between all bids + * @param {single bid object} generalObject + * @param {bidderRequest} bidderRequest + * @returns {object} the common params object */ -function createBid(bidResponse, bidRequest) { - const mediaType = getMediaType(bidRequest) - const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - const bid = { - requestId: bidResponse.requestId, - creativeId: bidResponse.cmpId, - height: bidResponse.height, - currency: bidResponse.cur, - width: bidResponse.width, - cpm: bidResponse.cpm, - netRevenue: true, - mediaType, - ttl: 300, - meta: { - advertiserDomains: bidResponse.adomain || [] +function generateGeneralParams(generalObject, bidderRequest) { + const domain = deepAccess(bidderRequest, 'refererInfo.domain') || window.location.hostname; + const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; + const {bidderCode, timeout} = bidderRequest; + const generalBidParams = generalObject.params; + + // these params are snake_case instead of camelCase to allow backwards compatability on the server. + // in the future, these will be converted to camelCase to match our convention. + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: ADAPTER_VERSION, + publisher_id: generalBidParams.org, + 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 + } + + 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 (mediaType === BANNER) { - return Object.assign(bid, { - ad: bidResponse.ad, - adUrl: bidResponse.adUrl, - }); + if (bidderRequest.auctionStart) { + generalParams.auction_start = bidderRequest.auctionStart; } - if (context === ADPOD) { - Object.assign(bid, { - meta: { - primaryCatId: bidResponse.primaryCatId, - }, - video: { - context: ADPOD, - durationSeconds: bidResponse.durationSeconds - } - }); + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; } - Object.assign(bid, { - vastUrl: bidResponse.vastUrl - }); + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } - return bid; -} + if (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } -registerBidder(spec); + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + } + + return generalParams; +} diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index dc8bfa6c59e..36c1f0ca6c5 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -1,27 +1,53 @@ # Overview -**Module Name**: OpenWeb Bidder Adapter -**Module Type**: Bidder Adapter -**Maintainer**: monetization@openweb.com +Module Name: OpenWeb Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: monetization@openweb.com + # Description -OpenWeb.com official prebid adapter. Available in both client and server side versions. -OpenWeb header bidding adapter provides solution for accessing both Video and Display demand. +Module that connects to OpenWeb's demand sources. + +The OpenWeb adapter requires setup and approval from OpenWeb. Please reach out to monetization@openweb.com to create an OpenWeb account. + +The adapter supports Video and Display demand. + +# Bid Parameters +## Video + +| Name | Scope | Type | Description | Example +| ---- | ----- | ---- | ----------- | ------- +| `org` | required | String | OpenWeb publisher Id provided by your OpenWeb representative | "1234567890abcdef12345678" +| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 +| `placementId` | optional | String | A unique placement identifier | "12345678" +| `testMode` | optional | Boolean | This activates the test mode | false +| `currency` | optional | String | 3 letters currency | "EUR" + # Test Parameters -``` - var adUnits = [ - // Banner adUnit - { - code: 'div-test-div', - sizes: [[300, 250]], +```javascript +var adUnits = [ + { + code: 'dfp-video-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + } + }, bids: [{ bidder: 'openweb', params: { - aid: 529814 + org: '1234567890abcdef12345678', // Required + floorPrice: 2.00, // Optional + placementId: '12345678', // Optional + testMode: false, // Optional, } }] } - ]; -``` + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index c515c21690a..5a0264f00b8 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -2,386 +2,624 @@ import { expect } from 'chai'; import { spec } from 'modules/openwebBidAdapter.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 DEFAULT_ADATPER_REQ = { bidderCode: 'openweb' }; -const DISPLAY_REQUEST = { - 'bidder': 'openweb', - 'params': { - 'aid': 12345 - }, - 'schain': { ver: 1 }, - 'userId': { criteo: 2 }, - 'mediaTypes': { 'banner': { 'sizes': [300, 250] } }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '84ab500420319d', -}; - -const VIDEO_REQUEST = { - 'bidder': 'openweb', - 'mediaTypes': { - 'video': { - 'playerSize': [[480, 360], [640, 480]] - } - }, - 'params': { - 'aid': 12345 - }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '84ab500420319d' -}; - -const ADPOD_REQUEST = { - 'bidder': 'openweb', - 'mediaTypes': { - 'video': { - 'context': 'adpod', - 'playerSize': [[640, 480]], - 'anyField': 10 - } - }, - 'params': { - 'aid': 12345 - }, - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '2e41f65424c87c', - 'adUnitCode': 'adunit-code', - 'bidId': '2e41f65424c87c' -}; - -const SERVER_VIDEO_RESPONSE = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'vastUrl': 'vastUrl', - 'requestId': '2e41f65424c87c', - 'url': '44F2AEB9BFC881B3', - 'creative_id': 342516, - 'durationSeconds': 30, - 'cmpId': 342516, - 'height': 480, - 'cur': 'USD', - 'width': 640, - 'cpm': 0.9, - 'adomain': ['a.com'] - }] -}; -const SERVER_OUSTREAM_VIDEO_RESPONSE = SERVER_VIDEO_RESPONSE; -const SERVER_DISPLAY_RESPONSE = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'ad': '', - 'adUrl': 'adUrl', - 'requestId': '2e41f65424c87c', - 'creative_id': 342516, - 'cmpId': 342516, - 'height': 250, - 'cur': 'USD', - 'width': 300, - 'cpm': 0.9 - }], - 'cookieURLs': ['link1', 'link2'] -}; -const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { - 'source': { 'aid': 12345, 'pubId': 54321 }, - 'bids': [{ - 'ad': '', - 'requestId': '2e41f65424c87c', - 'creative_id': 342516, - 'cmpId': 342516, - 'height': 250, - 'cur': 'USD', - 'width': 300, - 'cpm': 0.9 - }], - 'cookieURLs': ['link3', 'link4'], - 'cookieURLSTypes': ['image', 'iframe'] -}; - -const videoBidderRequest = { - bidderCode: 'bidderCode', - bids: [{ mediaTypes: { video: {} }, bidId: '2e41f65424c87c' }] -}; - -const displayBidderRequest = { - bidderCode: 'bidderCode', - bids: [{ bidId: '2e41f65424c87c' }] -}; - -const displayBidderRequestWithConsents = { - bidderCode: 'bidderCode', - bids: [{ bidId: '2e41f65424c87c' }], - gdprConsent: { - gdprApplies: true, - consentString: 'test' - }, - uspConsent: 'iHaveIt' -}; - -const videoEqResponse = [{ - vastUrl: 'vastUrl', - requestId: '2e41f65424c87c', - creativeId: 342516, - mediaType: 'video', - netRevenue: true, - currency: 'USD', - height: 480, - width: 640, - ttl: 300, - cpm: 0.9, - meta: { - advertiserDomains: ['a.com'] - } -}]; - -const displayEqResponse = [{ - requestId: '2e41f65424c87c', - creativeId: 342516, - mediaType: 'banner', - netRevenue: true, - currency: 'USD', - ad: '', - adUrl: 'adUrl', - height: 250, - width: 300, - ttl: 300, - cpm: 0.9, - meta: { - advertiserDomains: [] - } - -}]; - -describe('openwebBidAdapter', () => { +const ENDPOINT = 'https://hb.openwebmp.com/hb-multi'; +const TEST_ENDPOINT = 'https://hb.openwebmp.com/hb-multi-test'; +const TTL = 360; +/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ + +describe('openwebAdapter', function () { const adapter = newBidder(spec); - describe('inherited functions', () => { - it('exists and is a function', () => { + + describe('inherited functions', function () { + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('user syncs', () => { - describe('as image', () => { - it('should be returned if pixel enabled', () => { - const syncs = spec.getUserSyncs({ pixelEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); - - expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); - expect(syncs.map(s => s.type)).to.deep.equal(['image']); - }) - }) + 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); + }); - describe('as iframe', () => { - it('should be returned if iframe enabled', () => { - const syncs = spec.getUserSyncs({ iframeEnabled: true }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + 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); + }); + }); - expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); - expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); - }) - }) + 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: 'openweb', + } + 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); + }); - describe('user sync', () => { - it('should not be returned if passed syncs where already used', () => { - const syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + it('sends the plcmt to ENDPOINT via POST', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].plcmt).to.equal(1); + }); - expect(syncs).to.deep.equal([]); - }) + 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('should not be returned if pixel not set', () => { - const syncs = spec.getUserSyncs({}, [{ body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS }]); + 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'); + }); - expect(syncs).to.be.empty; - }); + 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'); }); - describe('user syncs with both types', () => { - it('should be returned if pixel and iframe enabled', () => { - const mockedServerResponse = Object.assign({}, SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS, { 'cookieURLs': ['link5', 'link6'] }); - const syncs = spec.getUserSyncs({ - iframeEnabled: true, - pixelEnabled: true - }, [{ body: mockedServerResponse }]); - expect(syncs.map(s => s.url)).to.deep.equal(mockedServerResponse.cookieURLs); - expect(syncs.map(s => s.type)).to.deep.equal(mockedServerResponse.cookieURLSTypes); - }); + it('should send the correct bid Id', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.bids[0].bidId).to.equal('299ffc8cca0b87'); }); - }); - describe('isBidRequestValid', () => { - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + 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 return false when required params are not passed', () => { - let bid = Object.assign({}, VIDEO_REQUEST); - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.equal(false); + 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']); }); - }); - describe('buildRequests', () => { - let videoBidRequests = [VIDEO_REQUEST]; - let displayBidRequests = [DISPLAY_REQUEST]; - let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; - const displayRequest = spec.buildRequests(displayBidRequests, DEFAULT_ADATPER_REQ); - const videoRequest = spec.buildRequests(videoBidRequests, DEFAULT_ADATPER_REQ); - const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, DEFAULT_ADATPER_REQ); - - it('building requests as arrays', () => { - expect(videoRequest).to.be.a('array'); - expect(displayRequest).to.be.a('array'); - expect(videoAndDisplayRequests).to.be.a('array'); - }) + 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('sending as POST', () => { - const postActionMethod = 'POST' - const comparator = br => br.method === postActionMethod; - expect(videoRequest.every(comparator)).to.be.true; - expect(displayRequest.every(comparator)).to.be.true; - expect(videoAndDisplayRequests.every(comparator)).to.be.true; - }); - it('forms correct ADPOD request', () => { - const pbBidReqData = spec.buildRequests([ADPOD_REQUEST], DEFAULT_ADATPER_REQ)[0].data; - const impRequest = pbBidReqData.BidRequests[0] - expect(impRequest.AdType).to.be.equal('video'); - expect(impRequest.Adpod).to.be.a('object'); - expect(impRequest.Adpod.anyField).to.be.equal(10); - }) - it('sends correct video bid parameters', () => { - const data = videoRequest[0].data; - - const eq = { - CallbackId: '84ab500420319d', - AdType: 'video', - Aid: 12345, - Sizes: '480x360,640x480', - PlacementId: 'adunit-code' - }; - expect(data.BidRequests[0]).to.deep.equal(eq); + 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('sends correct display bid parameters', () => { - const data = displayRequest[0].data; + 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) + }); - const eq = { - CallbackId: '84ab500420319d', - AdType: 'display', - Aid: 12345, - Sizes: '300x250', - PlacementId: 'adunit-code' + 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); + }); - expect(data.BidRequests[0]).to.deep.equal(eq); - }); - - it('sends correct video and display bid parameters', () => { - const bidRequests = videoAndDisplayRequests[0].data; - const expectedBidReqs = [{ - CallbackId: '84ab500420319d', - AdType: 'display', - Aid: 12345, - Sizes: '300x250', - PlacementId: 'adunit-code' - }, { - CallbackId: '84ab500420319d', - AdType: 'video', - Aid: 12345, - Sizes: '480x360,640x480', - PlacementId: 'adunit-code' - }] + 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'); + }); - expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); + 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'); }); - describe('publisher environment', () => { - const sandbox = sinon.sandbox.create(); - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'coppa': true - }; - return config[key]; + it('should respect "all" filter settings', function () { + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: [spec.code], + filter: 'include' + } + } + } }); - const bidRequestWithPubSettingsData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithConsents)[0].data; - sandbox.restore(); - it('sets GDPR', () => { - expect(bidRequestWithPubSettingsData.GDPR).to.be.equal(1); - expect(bidRequestWithPubSettingsData.GDPRConsent).to.be.equal(displayBidderRequestWithConsents.gdprConsent.consentString); + 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, + } }); - it('sets USP', () => { - expect(bidRequestWithPubSettingsData.USP).to.be.equal(displayBidderRequestWithConsents.uspConsent); - }) - it('sets Coppa', () => { - expect(bidRequestWithPubSettingsData.Coppa).to.be.equal(1); - }) - it('sets Schain', () => { - expect(bidRequestWithPubSettingsData.Schain).to.be.deep.equal(DISPLAY_REQUEST.schain); - }) - it('sets UserId\'s', () => { - expect(bidRequestWithPubSettingsData.UserIds).to.be.deep.equal(DISPLAY_REQUEST.userId); - }) - }) - }); + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.params).to.be.an('object'); + expect(request.data.params).to.have.property('cs_method', 'pixel'); + }); - describe('interpretResponse', () => { - let serverResponse; - let adapterRequest; - let eqResponse; + 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'); + }); - afterEach(() => { - serverResponse = null; - adapterRequest = null; - eqResponse = null; + 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 get correct video bid response', () => { - serverResponse = SERVER_VIDEO_RESPONSE; - adapterRequest = videoBidderRequest; - eqResponse = videoEqResponse; + 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'); + }); - bidServerResponseCheck(); + 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 get correct display bid response', () => { - serverResponse = SERVER_DISPLAY_RESPONSE; - adapterRequest = displayBidderRequest; - eqResponse = displayEqResponse; + 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'); + }); - bidServerResponseCheck(); + 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'); }); - function bidServerResponseCheck() { - const result = spec.interpretResponse({ body: serverResponse }, { adapterRequest }); + 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); + }); - expect(result).to.deep.equal(eqResponse); - } + 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); + }); - function nobidServerResponseCheck() { - const noBidServerResponse = { bids: [] }; - const noBidResult = spec.interpretResponse({ body: noBidServerResponse }, { adapterRequest }); + 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); + }); - expect(noBidResult.length).to.equal(0); - } + 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('handles video nobid responses', () => { - adapterRequest = videoBidderRequest; + 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' + } + ]); + }); - nobidServerResponseCheck(); + 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('handles display nobid responses', () => { - adapterRequest = displayBidderRequest; + it('should handle an empty response', function() { + const syncs = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); - nobidServerResponseCheck(); + it('should handle when user syncs are disabled', function() { + const syncs = spec.getUserSyncs({ pixelEnabled: false }, [imageSyncResponse]); + expect(syncs).to.deep.equal([]); }); + }) - it('forms correct ADPOD response', () => { - const videoBids = spec.interpretResponse({ body: SERVER_VIDEO_RESPONSE }, { adapterRequest: { bids: [ADPOD_REQUEST] } }); - expect(videoBids[0].video.durationSeconds).to.be.equal(30); - expect(videoBids[0].video.context).to.be.equal('adpod'); + 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) }) - }); + }) }); From 74d63a4a61b9290befdb2531f4c8dff3390716fd Mon Sep 17 00:00:00 2001 From: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Date: Mon, 1 Apr 2024 18:44:50 +0300 Subject: [PATCH 587/760] Evolution Bid Adapter: add gvlid (#11272) * updates for Prebid v5 * add id5id * update tests * add gvlid --- modules/e_volutionBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 5f1b46ff9eb..2ce6cda16d1 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -4,6 +4,7 @@ import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'e_volution'; +const GVLID = 957; const AD_URL = 'https://service.e-volution.ai/?c=o&m=multi'; const URL_SYNC = 'https://service.e-volution.ai/?c=o&m=sync'; const NO_SYNC = true; @@ -57,6 +58,7 @@ function getUserId(eids, id, source, uidExt) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], noSync: NO_SYNC, From 3a808fe7a7a719d24acd55633cd5c2985e17116f Mon Sep 17 00:00:00 2001 From: GammaSSP <35954362+gammassp@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:27:43 +0700 Subject: [PATCH 588/760] Gamma Bid Adapter : Support multi endpoint by region (#11263) * add multi endpoints * Update parameters * Update parammeters * Update multi endpoint * Update gammaBidAdapter.js * Update gammaBidAdapter.md * Update gammaBidAdapter_spec.js --- modules/gammaBidAdapter.js | 53 +++++++++++++++++++---- modules/gammaBidAdapter.md | 23 +++++++--- test/spec/modules/gammaBidAdapter_spec.js | 7 +-- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/modules/gammaBidAdapter.js b/modules/gammaBidAdapter.js index 40abdd81930..dadfe2ab14b 100644 --- a/modules/gammaBidAdapter.js +++ b/modules/gammaBidAdapter.js @@ -5,9 +5,13 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ -const ENDPOINT = 'https://hb.gammaplatform.com'; -const ENDPOINT_USERSYNC = 'https://cm-supply-web.gammaplatform.com'; const BIDDER_CODE = 'gamma'; +const ENDPOINTS = { + SGP: 'https://hb.gammaplatform.com', + JPN: 'https://hb-jp.gammaplatform.com', + US_WEST: 'https://hb-us.gammaplatform.com', + EU: 'https://hb-eu.gammaplatform.com' +} export const spec = { code: BIDDER_CODE, @@ -33,8 +37,10 @@ export const spec = { buildRequests: function(bidRequests, bidderRequest) { const serverRequests = []; const bidderRequestReferer = bidderRequest?.refererInfo?.page || ''; + let ENDPOINT; for (var i = 0, len = bidRequests.length; i < len; i++) { const gaxObjParams = bidRequests[i]; + ENDPOINT = getAdUrlByRegion(gaxObjParams); serverRequests.push({ method: 'GET', url: ENDPOINT + '/adx/request?wid=' + gaxObjParams.params.siteId + '&zid=' + gaxObjParams.params.zoneId + '&hb=pbjs&bidid=' + gaxObjParams.bidId + '&urf=' + encodeURIComponent(bidderRequestReferer) @@ -60,16 +66,45 @@ export const spec = { } return bids; - }, + } +} - getUserSyncs: function(syncOptions) { - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: ENDPOINT_USERSYNC + '/adx/usersync' - }]; +/** + * Get endpoint url by region + * @param bid + * @return aUrl + */ +function getAdUrlByRegion(bid) { + let ENDPOINT; + + if (bid.params.region && ENDPOINTS[bid.params.region]) { + ENDPOINT = ENDPOINTS[bid.params.region]; + } else { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + ENDPOINT = ENDPOINTS['EU']; + break; + case 'Australia': + ENDPOINT = ENDPOINTS['JPN']; + break; + case 'Asia': + ENDPOINT = ENDPOINTS['SGP']; + break; + case 'America': + ENDPOINT = ENDPOINTS['US_WEST']; + break; + default: ENDPOINT = ENDPOINTS['SGP']; + } + } catch (err) { + ENDPOINT = ENDPOINTS['SGP']; } } + + return ENDPOINT; } /** diff --git a/modules/gammaBidAdapter.md b/modules/gammaBidAdapter.md index 2902be78492..bcb26d0b86e 100644 --- a/modules/gammaBidAdapter.md +++ b/modules/gammaBidAdapter.md @@ -12,6 +12,14 @@ Connects to Gamma exchange for bids. Gamma bid adapter supports Banner, Video. +# Parameters + +| Name | Scope | Description | Example | +| :------------ | :------- | :------------------------ | :------------------- | +| `zoneId` | required | Zone ID | "1398219417" | +| `siteId` | required | Website ID | "1398219351" | +| `region` | optional (for prebid.js) | Prefix of the region to which prebid must send requests. Possible values: "SGP", "JPN", "US_WEST", "EU" | "SGP" | + # Test Parameters: For Banner ``` var adUnits = [{ @@ -22,8 +30,9 @@ var adUnits = [{ bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' } }] @@ -39,8 +48,9 @@ var adUnits = [{ bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1493280341' + siteId: '1398219351', + zoneId: '1614755846', + region: 'SGP' } }] @@ -59,8 +69,9 @@ In order to receive bids please map localhost to (any) test domain. bids: [{ bidder: 'gamma', params: { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' } }] }]; diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js index 35394df7d11..f3a28c08576 100644 --- a/test/spec/modules/gammaBidAdapter_spec.js +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -8,8 +8,9 @@ describe('gammaBidAdapter', function() { let bid = { 'bidder': 'gamma', 'params': { - siteId: '1465446377', - zoneId: '1515999290' + siteId: '1398219351', + zoneId: '1398219417', + region: 'SGP' }, 'adUnitCode': 'adunit-code', 'sizes': [ @@ -84,7 +85,7 @@ describe('gammaBidAdapter', function() { 'width': 300, 'height': 250, 'creativeId': '1515999070', - 'dealId': 'gax-paj2qarjf2g', + 'dealId': 'gax-lvpjgs5b9k4n', 'currency': 'USD', 'netRevenue': true, 'ttl': 300, From cddb9344d75a2154369142804d883cce04642e76 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:16:00 -0400 Subject: [PATCH 589/760] AppNexus Bid Adapter - add support for video plcmt field (#11244) --- modules/appnexusBidAdapter.js | 48 ++++++++++++++--- test/spec/modules/appnexusBidAdapter_spec.js | 57 +++++++++++++++++++- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index a6dc05a101f..590ad264515 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -48,7 +48,7 @@ const URL = 'https://ib.adnxs.com/ut/v3/prebid'; const URL_SIMPLE = 'https://ib.adnxs-simple.com/ut/v3/prebid'; const VIDEO_TARGETING = ['id', 'minduration', 'maxduration', 'skippable', 'playback_method', 'frameworks', 'context', 'skipoffset']; -const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay']; +const VIDEO_RTB_TARGETING = ['minduration', 'maxduration', 'skip', 'skipafter', 'playbackmethod', 'api', 'startdelay', 'placement', 'plcmt']; const USER_PARAMS = ['age', 'externalUid', 'external_uid', 'segments', 'gender', 'dnt', 'language']; const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; @@ -72,7 +72,12 @@ const VIDEO_MAPPING = { 'mid_roll': 2, 'post_roll': 3, 'outstream': 4, - 'in-banner': 5 + 'in-banner': 5, + 'in-feed': 6, + 'interstitial': 7, + 'accompanying_content_pre_roll': 8, + 'accompanying_content_mid_roll': 9, + 'accompanying_content_post_roll': 10 } }; const NATIVE_MAPPING = { @@ -916,15 +921,15 @@ function bidToTag(bid) { tag['video_frameworks'] = apiTmp; } break; - case 'startdelay': + case 'plcmt': case 'placement': - const contextKey = 'context'; - if (typeof tag.video[contextKey] !== 'number') { + if (typeof tag.video.context !== 'number') { + const plcmt = videoMediaType['plcmt']; const placement = videoMediaType['placement']; const startdelay = videoMediaType['startdelay']; - const context = getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); - tag.video[contextKey] = VIDEO_MAPPING[contextKey][context]; + const contextVal = getContextFromPlcmt(plcmt, startdelay) || getContextFromPlacement(placement) || getContextFromStartDelay(startdelay); + tag.video.context = VIDEO_MAPPING.context[contextVal]; } break; } @@ -983,8 +988,12 @@ function getContextFromPlacement(ortbPlacement) { if (ortbPlacement === 2) { return 'in-banner'; - } else if (ortbPlacement > 2) { + } else if (ortbPlacement === 3) { return 'outstream'; + } else if (ortbPlacement === 4) { + return 'in-feed'; + } else if (ortbPlacement === 5) { + return 'intersitial'; } } @@ -1002,6 +1011,29 @@ function getContextFromStartDelay(ortbStartDelay) { } } +function getContextFromPlcmt(ortbPlcmt, ortbStartDelay) { + if (!ortbPlcmt) { + return; + } + + if (ortbPlcmt === 2) { + if (!ortbStartDelay) { + return; + } + if (ortbStartDelay === 0) { + return 'accompanying_content_pre_roll'; + } else if (ortbStartDelay === -1) { + return 'accompanying_content_mid_roll'; + } else if (ortbStartDelay === -2) { + return 'accompanying_content_post_roll'; + } + } else if (ortbPlcmt === 3) { + return 'interstitial'; + } else if (ortbPlcmt === 4) { + return 'outstream'; + } +} + function hasUserInfo(bid) { return !!bid.params.user; } diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index cf6a1704bde..cc86a8a0aaa 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -343,7 +343,7 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); - it('should include ORTB video values when video params were not set', function () { + it('should include ORTB video values when matching video params were not all set', function () { let bidRequest = deepClone(bidRequests[0]); bidRequest.params = { placementId: '1234235', @@ -377,6 +377,61 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); + it('should include ORTB video values when video params is empty - case 1', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + placement: 3, + mimes: ['video/mp4'], + skip: 0, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: false, + context: 4 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + + it('should include ORTB video values when video params is empty - case 2', function () { + let bidRequest = deepClone(bidRequests[0]); + bidRequest.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'outstream', + plcmt: 2, + startdelay: -1, + mimes: ['video/mp4'], + skip: 1, + minduration: 5, + api: [1, 5, 6], + playbackmethod: [2, 4] + } + }; + + const request = spec.buildRequests([bidRequest]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].video).to.deep.equal({ + minduration: 5, + playback_method: 2, + skippable: true, + context: 9 + }); + expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) + }); + it('should add video property when adUnit includes a renderer', function () { const videoData = { mediaTypes: { From c27f64b1e059c83706435db0b1eeffc88e9b3559 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 2 Apr 2024 17:11:57 -0700 Subject: [PATCH 590/760] Optimize splitChunk config; import paapi from fledgeFromGpt (#11269) --- gulpfile.js | 3 +-- modules/fledgeForGpt.js | 5 +++++ webpack.conf.js | 23 ++++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f3d44243ef8..17c421f4dc1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -295,8 +295,7 @@ function bundle(dev, moduleArr) { [coreFile].concat(moduleFiles).map(name => path.basename(name)).forEach((file) => { (depGraph[file] || []).forEach((dep) => dependencies.add(helpers.getBuiltPath(dev, dep))); }); - - const entries = [coreFile].concat(Array.from(dependencies), moduleFiles); + const entries = _.uniq([coreFile].concat(Array.from(dependencies), moduleFiles)); var outputFileName = argv.bundleName ? argv.bundleName : 'prebid.js'; diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js index 7e841234e24..bda4494faaf 100644 --- a/modules/fledgeForGpt.js +++ b/modules/fledgeForGpt.js @@ -7,6 +7,11 @@ import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; +// import parent module to keep backwards-compat for NPM consumers after paapi was split from fledgeForGpt +// there's a special case in webpack.conf.js to avoid duplicating build output on non-npm builds +// TODO: remove this in prebid 9 +// eslint-disable-next-line prebid/validate-imports +import './paapi.js'; const MODULE = 'fledgeForGpt'; let getPAAPIConfig; diff --git a/webpack.conf.js b/webpack.conf.js index 0ead550e446..8d29c786595 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -128,10 +128,31 @@ module.exports = { return [lib, def]; }) ); + const core = path.resolve('./src'); + const paapiMod = path.resolve('./modules/paapi.js'); + const nodeModules = path.resolve('./node_modules'); + return Object.assign(libraries, { + core: { + name: 'chunk-core', + test: (module) => { + return module.resource?.startsWith(core) || module.resource?.startsWith(nodeModules); + } + }, + paapi: { + // fledgeForGpt imports paapi to keep backwards compat for NPM consumers + // this makes the paapi module its own chunk, pulled in by both paapi and fledgeForGpt entry points, + // to avoid duplication + // TODO: remove this in prebid 9 + name: 'chunk-paapi', + test: (module) => { + return module.resource === paapiMod; + } + } + }, { default: false, defaultVendors: false - }) + }); })() } }, From 74a404cff5fac0455397944b5caa97a6e52f753e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+krzysztofequativ@users.noreply.github.com> Date: Wed, 3 Apr 2024 18:07:33 +0200 Subject: [PATCH 591/760] Smartadserver Bid Adapter: Update default value of startDelay video parameter (#11225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching * Add gpid support * Support additional video params * vpmt as array of numbers * Fix comment * Update default startDelay * Include videoMediaType's startdelay * Handle specified midroll --------- Co-authored-by: Meven Courouble Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> Co-authored-by: Dariusz O --- modules/smartadserverBidAdapter.js | 10 ++-- .../modules/smartadserverBidAdapter_spec.js | 48 ++++++++++++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 9146bba6514..7edaaa36957 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -132,18 +132,16 @@ export const spec = { * @returns positive integer value of startdelay */ getStartDelayForVideoBidRequest: function(videoMediaType, videoParams) { - if (videoParams !== undefined && videoParams.startDelay) { + if (videoParams?.startDelay) { return videoParams.startDelay; - } else if (videoMediaType !== undefined) { - if (videoMediaType.startdelay == 0) { - return 1; - } else if (videoMediaType.startdelay == -1) { + } else if (videoMediaType?.startdelay) { + if (videoMediaType.startdelay > 0 || videoMediaType.startdelay == -1) { return 2; } else if (videoMediaType.startdelay == -2) { return 3; } } - return 2;// Default value for all exotic cases set to bid.params.video.startDelay midroll hence 2. + return 1; // SADR-5619 }, /** diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index 58b4cd8c0d0..b01d95e2a4c 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -787,7 +787,7 @@ describe('Smart bid adapter tests', function () { const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); - expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); }); it('Verify videoData params override meta values', function () { @@ -1097,7 +1097,7 @@ describe('Smart bid adapter tests', function () { const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('videoData'); expect(requestContent.videoData).not.to.have.property('videoProtocol').eq(true); - expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(1); }); it('Verify videoData params override meta values', function () { @@ -1143,6 +1143,50 @@ describe('Smart bid adapter tests', function () { expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); }); + + it('should handle value of videoMediaType.startdelay', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + startdelay: -2 + } + }, + params: { + siteId: 123, + pageId: 456, + formatId: 78 + } + }]); + + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(3); + }); + + it('should return specified value of videoMediaType.startdelay', function () { + const request = spec.buildRequests([{ + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[640, 480]], + startdelay: 60 + } + }, + params: { + siteId: 123, + pageId: 456, + formatId: 78 + } + }]); + + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('adBreak').and.to.equal(2); + }); }); describe('External ids tests', function () { From b8786a94097eb83dbca36691f1819c2923c2aa13 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 3 Apr 2024 09:45:37 -0700 Subject: [PATCH 592/760] core: allow bid adapters to return null fledgeAuctionConfigs (#11271) * core: allow bid adapters to return null fledgeAuctionConfigs * Accept paapiAuctionConfigs from adapters * add test case --- src/adapters/bidderFactory.js | 32 +++++-- test/spec/unit/core/bidderFactory_spec.js | 106 ++++++++++++++-------- 2 files changed, 92 insertions(+), 46 deletions(-) diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 4f9237fc8d3..337ae47f338 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -89,8 +89,8 @@ import {ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD} from '../activities/activ /** * @typedef {object} BidderAuctionResponse An object encapsulating an adapter response for current Auction * - * @property {Array} bids Contextual bids returned by this adapter, if any - * @property {object|null} fledgeAuctionConfigs Optional FLEDGE response, as a map of impid -> auction_config + * @property {Array} bids? Contextual bids returned by this adapter, if any + * @property {Array<{bidId: String, config: {}}>} paapiAuctionConfigs? Array of paapi auction configs, each scoped to a particular bidId */ /** @@ -361,6 +361,18 @@ export function newBidder(spec) { } } +// Transition from 'fledge' to 'paapi' +// TODO: remove this in prebid 9 +const PAAPI_RESPONSE_PROPS = ['paapiAuctionConfigs', 'fledgeAuctionConfigs']; +const RESPONSE_PROPS = ['bids'].concat(PAAPI_RESPONSE_PROPS); +function getPaapiConfigs(adapterResponse) { + const [paapi, fledge] = PAAPI_RESPONSE_PROPS.map(prop => adapterResponse[prop]); + if (paapi != null && fledge != null) { + throw new Error(`Adapter response should use ${PAAPI_RESPONSE_PROPS[0]} over ${PAAPI_RESPONSE_PROPS[1]}, not both`); + } + return paapi ?? fledge; +} + /** * Run a set of bid requests - that entails converting them to HTTP requests, sending * them over the network, and parsing the responses. @@ -422,15 +434,21 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe return; } - let bids; - // Extract additional data from a structured {BidderAuctionResponse} response - if (response && isArray(response.fledgeAuctionConfigs)) { - response.fledgeAuctionConfigs.forEach(onPaapi); + // adapters can reply with: + // a single bid + // an array of bids + // a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]}) + + let bids, paapiConfigs; + if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { bids = response.bids; + paapiConfigs = getPaapiConfigs(response); } else { bids = response; } - + if (isArray(paapiConfigs)) { + paapiConfigs.forEach(onPaapi); + } if (bids) { if (isArray(bids)) { bids.forEach(addBid); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index aba64733f90..5fe5a1accfc 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1456,67 +1456,95 @@ describe('bidderFactory', () => { transactionId: 'au', }] }; - const fledgeAuctionConfig = { + const paapiConfig = { bidId: '1', config: { foo: 'bar' } } - describe('when response has FLEDGE auction config', function() { - let fledgeStub; - function fledgeHook(next, ...args) { - fledgeStub(...args); + it('should unwrap bids', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); + }); + + it('does not unwrap bids from a bid that happens to have a "bids" property', () => { + const bidder = newBidder(spec); + const bid = Object.assign({ + bids: ['a', 'b'] + }, bids[0]); + spec.interpretResponse.returns(bid); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); + }) + + describe('when response has PAAPI auction config', function() { + let paapiStub; + + function paapiHook(next, ...args) { + paapiStub(...args); } before(() => { - addComponentAuction.before(fledgeHook); + addComponentAuction.before(paapiHook); }); after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); + addComponentAuction.getHooks({hook: paapiHook}).remove(); }) beforeEach(function () { - fledgeStub = sinon.stub(); + paapiStub = sinon.stub(); }); - it('should unwrap bids', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); - }); + const PAAPI_PROPS = ['fledgeAuctionConfigs', 'paapiAuctionConfigs']; - it('should call fledgeManager with FLEDGE configs', function() { + it(`should not accept both ${PAAPI_PROPS.join(' and ')}`, () => { const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: bids, - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - - expect(fledgeStub.calledOnce).to.equal(true); - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(true); - expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + spec.interpretResponse.returns(Object.fromEntries(PAAPI_PROPS.map(prop => [prop, [paapiConfig]]))) + expect(() => { + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + }).to.throw; }) - it('should call fledgeManager with FLEDGE configs even if no bids returned', function() { - const bidder = newBidder(spec); - spec.interpretResponse.returns({ - bids: [], - fledgeAuctionConfigs: [fledgeAuctionConfig] - }); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + PAAPI_PROPS.forEach(paapiProp => { + describe(`using ${paapiProp}`, () => { + it('should call paapi hook with PAAPI configs', function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids: bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - expect(fledgeStub.calledOnce).to.be.true; - sinon.assert.calledWith(fledgeStub, bidRequest.bids[0], fledgeAuctionConfig.config); - expect(addBidResponseStub.calledOnce).to.equal(false); + expect(paapiStub.calledOnce).to.equal(true); + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(true); + expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + }) + + Object.entries({ + 'missing': undefined, + 'an empty array': [] + }).forEach(([t, bids]) => { + it(`should call paapi hook with PAAPI configs even when bids is ${t}`, function() { + const bidder = newBidder(spec); + spec.interpretResponse.returns({ + bids, + [paapiProp]: [paapiConfig] + }); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); + + expect(paapiStub.calledOnce).to.be.true; + sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig.config); + expect(addBidResponseStub.calledOnce).to.equal(false); + }) + }) + }) }) }) }) From f63b3ad3ae0d50922263444d1252107c80afd4d5 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 3 Apr 2024 17:53:48 -0700 Subject: [PATCH 593/760] Build system: fix e2e tests (#11287) --- test/fake-server/fixtures/basic-outstream/request.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/fake-server/fixtures/basic-outstream/request.json b/test/fake-server/fixtures/basic-outstream/request.json index e9f3302ab4c..6d522058cff 100644 --- a/test/fake-server/fixtures/basic-outstream/request.json +++ b/test/fake-server/fixtures/basic-outstream/request.json @@ -19,6 +19,7 @@ "prebid": true, "disable_psa": true, "video": { + "context": 4, "skippable": true, "playback_method": 2 }, @@ -39,6 +40,7 @@ "prebid": true, "disable_psa": true, "video": { + "context": 4, "skippable": true, "playback_method": 2 }, From 32e952cabbfb6b0391eb8668874ff041327c5cba Mon Sep 17 00:00:00 2001 From: giri-playgroundxyz <74629665+giri-playgroundxyz@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:08:44 +1100 Subject: [PATCH 594/760] PXYZ Bid Adapter : add GumGum iframe url to usersync list (#11280) * add advertiserDomain to bid object * PGA-240 - Add gumgum iframe to usersync list * PGA-240 - Update pxyz bid adapter test to include gumgum iframe url * PGA-240 - Remove unused syncOptions variable --------- Co-authored-by: jgan91 Co-authored-by: Jonathan Mullins Co-authored-by: Jonathan Mullins --- modules/pxyzBidAdapter.js | 5 +++- test/spec/modules/pxyzBidAdapter_spec.js | 33 +++++++----------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/modules/pxyzBidAdapter.js b/modules/pxyzBidAdapter.js index 8b9dbea339b..12bd04c744d 100644 --- a/modules/pxyzBidAdapter.js +++ b/modules/pxyzBidAdapter.js @@ -129,10 +129,13 @@ export const spec = { return bids; }, - getUserSyncs: function (syncOptions) { + getUserSyncs: function () { return [{ type: 'image', url: '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID' + }, { + type: 'iframe', + url: '//rtb.gumgum.com/getuid/15801?r=https%3A%2F%2Fads.playground.xyz%2Fusersync%3Fpartner%3Dgumgum%26uid%3D' }]; } } diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js index 3a336c86e46..36e7a1e9ad6 100644 --- a/test/spec/modules/pxyzBidAdapter_spec.js +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -210,30 +210,15 @@ describe('pxyzBidAdapter', function () { }); describe('getUserSyncs', function () { - const syncUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; - - describe('when iframeEnabled is true', function () { - const syncOptions = { - 'iframeEnabled': true - } - it('should return one image type user sync pixel', function () { - let result = spec.getUserSyncs(syncOptions); - expect(result.length).to.equal(1); - expect(result[0].type).to.equal('image') - expect(result[0].url).to.equal(syncUrl); - }); - }); - - describe('when iframeEnabled is false', function () { - const syncOptions = { - 'iframeEnabled': false - } - it('should return one image type user sync pixel', function () { - let result = spec.getUserSyncs(syncOptions); - expect(result.length).to.equal(1); - expect(result[0].type).to.equal('image') - expect(result[0].url).to.equal(syncUrl); - }); + const syncImageUrl = '//ib.adnxs.com/getuidnb?https://ads.playground.xyz/usersync?partner=appnexus&uid=$UID'; + const syncIframeUrl = '//rtb.gumgum.com/getuid/15801?r=https%3A%2F%2Fads.playground.xyz%2Fusersync%3Fpartner%3Dgumgum%26uid%3D'; + it('should return one image type user sync pixel', function () { + let result = spec.getUserSyncs(); + expect(result.length).to.equal(2); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(syncImageUrl); + expect(result[1].type).to.equal('iframe') + expect(result[1].url).to.equal(syncIframeUrl); }); }) }); From 0bcd57f30aab06eb2c1c40ecbb58e3e25c78e211 Mon Sep 17 00:00:00 2001 From: Basil Goldman Date: Thu, 4 Apr 2024 16:26:22 +0300 Subject: [PATCH 595/760] Anyclip Bid Adapter : initial release (#11200) * prepare anyclipBidAdapter * added spec test for anyclipBidAdapter * cleanup * fixed readme * added getFloor support --------- Co-authored-by: Basil Goldman <> --- modules/anyclipBidAdapter.js | 213 ++++++++++++++++++++ modules/anyclipBidAdapter.md | 52 +++++ test/spec/modules/anyclipBidAdapter_spec.js | 160 +++++++++++++++ 3 files changed, 425 insertions(+) create mode 100644 modules/anyclipBidAdapter.js create mode 100644 modules/anyclipBidAdapter.md create mode 100644 test/spec/modules/anyclipBidAdapter_spec.js diff --git a/modules/anyclipBidAdapter.js b/modules/anyclipBidAdapter.js new file mode 100644 index 00000000000..cb9103899a4 --- /dev/null +++ b/modules/anyclipBidAdapter.js @@ -0,0 +1,213 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {deepAccess, isArray, isFn, logError, logInfo} from '../src/utils.js'; +import {config} from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + */ + +const BIDDER_CODE = 'anyclip'; +const ENDPOINT_URL = 'https://prebid.anyclip.com'; +const DEFAULT_CURRENCY = 'USD'; +const NET_REVENUE = false; + +/* + * Get the bid floor value from the bidRequest object, either using the getFloor function or by accessing the 'params.floor' property. + * If the bid floor cannot be determined, return 0 as a fallback value. + */ +function getBidFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return deepAccess(bidRequest, 'params.floor', 0); + } + + try { + const bidFloor = bidRequest.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * @param {object} bid + * @return {boolean} + */ + isBidRequestValid: (bid = {}) => { + const bidder = deepAccess(bid, 'bidder'); + const params = deepAccess(bid, 'params', {}); + const mediaTypes = deepAccess(bid, 'mediaTypes', {}); + const banner = deepAccess(mediaTypes, BANNER, {}); + + const isValidBidder = (bidder === BIDDER_CODE); + const isValidSize = (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); + const hasSizes = mediaTypes[BANNER] ? isValidSize : false; + const hasRequiredBidParams = Boolean(params.publisherId && params.supplyTagId); + + const isValid = isValidBidder && hasSizes && hasRequiredBidParams; + if (!isValid) { + logError(`Invalid bid request: isValidBidder: ${isValidBidder}, hasSizes: ${hasSizes}, hasRequiredBidParams: ${hasRequiredBidParams}`); + } + return isValid; + }, + + /** + * @param {BidRequest[]} validBidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ + buildRequests: (validBidRequests, bidderRequest) => { + const bidRequest = validBidRequests[0]; + + let refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + + const timeout = bidderRequest.timeout; + const timeoutAdjustment = timeout - ((20 / 100) * timeout); // timeout adjustment - 20% + + if (isPubTagAvailable()) { + // Options + const options = { + publisherId: bidRequest.params.publisherId, + supplyTagId: bidRequest.params.supplyTagId, + url: refererInfo.page, + domain: refererInfo.domain, + prebidTimeout: timeoutAdjustment, + gpid: bidRequest.adUnitCode, + ext: { + transactionId: bidRequest.transactionId + }, + sizes: bidRequest.sizes.map((size) => { + return {width: size[0], height: size[1]} + }) + } + // Floor + const floor = parseFloat(getBidFloor(bidRequest)); + if (!isNaN(floor)) { + options.ext.floor = floor; + } + // Supply Chain (Schain) + if (bidRequest?.schain) { + options.schain = bidRequest.schain + } + // GDPR & Consent (EU) + if (bidderRequest?.gdprConsent) { + options.gdpr = (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + options.consent = bidderRequest.gdprConsent.consentString; + } + // GPP + if (bidderRequest?.gppConsent?.gppString) { + options.gpp = { + gppVersion: bidderRequest.gppConsent.gppVersion, + sectionList: bidderRequest.gppConsent.sectionList, + applicableSections: bidderRequest.gppConsent.applicableSections, + gppString: bidderRequest.gppConsent.gppString + } + } + // CCPA (US Privacy) + if (bidderRequest?.uspConsent) { + options.usPrivacy = bidderRequest.uspConsent; + } + // COPPA + if (config.getConfig('coppa') === true) { + options.coppa = 1; + } + // Eids + if (bidRequest?.userIdAsEids) { + const eids = bidRequest.userIdAsEids; + if (eids && eids.length) { + options.eids = eids; + } + } + + // Request bids + const requestBidsPromise = window._anyclip.pubTag.requestBids(options); + if (requestBidsPromise !== undefined) { + requestBidsPromise + .then(() => { + logInfo('PubTag requestBids done'); + }) + .catch((err) => { + logError('PubTag requestBids error', err); + }); + } + + // Request + const payload = { + tmax: timeoutAdjustment + } + + return { + method: 'GET', + url: ENDPOINT_URL, + data: payload, + bidRequest + } + } + }, + + /** + * @param {*} serverResponse + * @param {ServerRequest} bidRequest + * @return {Bid[]} + */ + interpretResponse: (serverResponse, { bidRequest }) => { + const bids = []; + + if (bidRequest && isPubTagAvailable()) { + const bidResponse = window._anyclip.pubTag.getBids(bidRequest.transactionId); + if (bidResponse) { + const { adServer } = bidResponse; + if (adServer) { + bids.push({ + requestId: bidRequest.bidId, + creativeId: adServer.bid.creativeId, + cpm: bidResponse.cpm, + width: adServer.bid.width, + height: adServer.bid.height, + currency: adServer.bid.currency || DEFAULT_CURRENCY, + netRevenue: NET_REVENUE, + ttl: adServer.bid.ttl, + ad: adServer.bid.ad, + meta: adServer.bid.meta + }); + } + } + } + + return bids; + }, + + /** + * @param {Bid} bid + */ + onBidWon: (bid) => { + if (isPubTagAvailable()) { + window._anyclip.pubTag.bidWon(bid); + } + } +} + +/** + * @return {boolean} + */ +const isPubTagAvailable = () => { + return !!(window._anyclip && window._anyclip.pubTag); +} + +registerBidder(spec); diff --git a/modules/anyclipBidAdapter.md b/modules/anyclipBidAdapter.md new file mode 100644 index 00000000000..ed67c9f6722 --- /dev/null +++ b/modules/anyclipBidAdapter.md @@ -0,0 +1,52 @@ +# Overview + +``` +Module Name: AnyClip Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@anyclip.com +``` + +# Description + +Connects to AnyClip Marketplace for bids. + +For more information about [AnyClip](https://anyclip.com), please contact [support@anyclip.com](support@anyclip.com). + +AnyClip bid adapter supports Banner currently*. + +Use `anyclip` as bidder. + +# Bid Parameters + +| Key | Required | Example | Description | +|---------------|----------|--------------------------|---------------------------------------| +| `publisherId` | Yes | `'12345'` | The publisher ID provided by AnyClip | +| `supplyTagId` | Yes | `'-mptNo0BycUG4oCDgGrU'` | The supply tag ID provided by AnyClip | +| `floor` | No | `0.5` | Floor price | + + +# Sample Ad Unit: For Publishers +## Sample Banner only Ad Unit +```js +var adUnits = [{ + code: 'adunit1', // ad slot HTML element ID + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90] + ] + } + }, + bids: [{ + bidder: 'anyclip', + params: { + publisherId: '12345', // required, string + supplyTagId: '-mptNo0BycUG4oCDgGrU', // required, string + floor: 0.5 // optional, floor + } + }] +}] +``` + + diff --git a/test/spec/modules/anyclipBidAdapter_spec.js b/test/spec/modules/anyclipBidAdapter_spec.js new file mode 100644 index 00000000000..3de36f9fe06 --- /dev/null +++ b/test/spec/modules/anyclipBidAdapter_spec.js @@ -0,0 +1,160 @@ +import { expect } from 'chai'; +import { spec } from 'modules/anyclipBidAdapter.js'; + +describe('anyclipBidAdapter', function () { + afterEach(function () { + global._anyclip = undefined; + }); + + let bid; + + function mockBidRequest() { + return { + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [728, 90], + [468, 60] + ] + } + }, + bidder: 'anyclip', + params: { + publisherId: '12345', + supplyTagId: '-mptNo0BycUG4oCDgGrU' + } + }; + }; + + describe('isBidRequestValid', function () { + this.beforeEach(function () { + bid = mockBidRequest(); + }); + + it('should return true if all required fields are present', function () { + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('should return false if bidder does not correspond', function () { + bid.bidder = 'abc'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if params object is missing', function () { + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if publisherId is missing from params', function () { + delete bid.params.publisherId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if supplyTagId is missing from params', function () { + delete bid.params.supplyTagId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if mediaTypes is missing', function () { + delete bid.mediaTypes; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if banner is missing from mediaTypes ', function () { + delete bid.mediaTypes.banner; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is missing from banner object', function () { + delete bid.mediaTypes.banner.sizes; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is not an array', function () { + bid.mediaTypes.banner.sizes = 'test'; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return false if sizes is an empty array', function () { + bid.mediaTypes.banner.sizes = []; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + refererInfo: { + page: 'http://example.com', + domain: 'example.com', + }, + timeout: 3000 + }; + + this.beforeEach(function () { + bid = mockBidRequest(); + Object.assign(bid, { + adUnitCode: '1', + transactionId: '123', + sizes: bid.mediaTypes.banner.sizes + }); + }); + + it('when pubtag is not available, return undefined', function () { + expect(spec.buildRequests([bid], bidderRequest)).to.undefined; + }); + it('when pubtag is available, creates a ServerRequest object with method, URL and data', function() { + global._anyclip = { + PubTag: function() {}, + pubTag: { + requestBids: function() {} + } + }; + expect(spec.buildRequests([bid], bidderRequest)).to.exist; + }); + }); + + describe('interpretResponse', function() { + it('should return an empty array when parsing a no bid response', function () { + const response = {}; + const request = {}; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + it('should return bids array', function() { + const response = {}; + const request = { + bidRequest: { + bidId: 'test-bidId', + transactionId: '123' + } + }; + + global._anyclip = { + PubTag: function() {}, + pubTag: { + getBids: function(transactionId) { + return { + adServer: { + bid: { + ad: 'test-ad', + creativeId: 'test-crId', + meta: { + advertiserDomains: ['anyclip.com'] + }, + width: 300, + height: 250, + ttl: 300 + } + }, + cpm: 1.23, + } + } + } + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].ttl).to.equal(300); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].netRevenue).to.false; + expect(bids[0].meta.advertiserDomains[0]).to.equal('anyclip.com'); + }); + }); +}); From 797adc19998c3dced1024fc2712afd76adb193f7 Mon Sep 17 00:00:00 2001 From: ahzgg <163184035+ahzgg@users.noreply.github.com> Date: Thu, 4 Apr 2024 10:25:05 -0600 Subject: [PATCH 596/760] GumGum Bid Adapter : add support for displaymanager and displaymanagerver fields (#11286) * displaymanager: add field for displaymanager and displaymanagerver * fix: displayManager to include 'gumgum' * fix: test case --- modules/gumgumBidAdapter.js | 3 ++- test/spec/modules/gumgumBidAdapter_spec.js | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 7f8627ec5f7..60851d2b4dd 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -318,7 +318,8 @@ function buildRequests(validBidRequests, bidderRequest) { const gpid = deepAccess(ortb2Imp, 'ext.data.pbadslot') || deepAccess(ortb2Imp, 'ext.data.adserver.adslot'); let sizes = [1, 1]; let data = {}; - + data.displaymanager = 'Prebid.js - gumgum'; + data.displaymanagerver = '$prebid.version$'; const date = new Date(); const lt = date.getTime(); const to = date.getTimezoneOffset(); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 52cfd0294e7..29e372d0f87 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -192,6 +192,8 @@ describe('gumgumAdapter', function () { const request = { ...bidRequests[0] }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data.aun).to.equal(bidRequests[0].adUnitCode); + expect(bidRequest.data.displaymanager).to.equal('Prebid.js - gumgum'); + expect(bidRequest.data.displaymanagerver).to.equal(JCSI.pbv); }); it('should set pubProvidedId if the uid and pubProvidedId are available', function () { const request = { ...bidRequests[0] }; From d1d1564c689aef6d1190b20e5fa2152c03a00ecd Mon Sep 17 00:00:00 2001 From: nalexand <35492736+nalexand@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:14:44 +0200 Subject: [PATCH 597/760] MediaImpact Bid Adapter : add custom size parameter to request (#11254) * Add mediaimpact bid adapter * Add mediaimpact bid adapter tests * Add custom sizes --------- Co-authored-by: koshe --- modules/mediaimpactBidAdapter.js | 9 +++++++-- modules/mediaimpactBidAdapter.md | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js index 9a86632f052..4ce11201507 100644 --- a/modules/mediaimpactBidAdapter.js +++ b/modules/mediaimpactBidAdapter.js @@ -27,9 +27,14 @@ export const spec = { }; validBidRequests.forEach(function(validBidRequest) { + let sizes = validBidRequest.sizes; + if (typeof validBidRequest.params.sizes !== 'undefined') { + sizes = validBidRequest.params.sizes; + } + let bidRequestObject = { adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, + sizes: sizes, bidId: validBidRequest.bidId, referer: referer }; @@ -47,7 +52,7 @@ export const spec = { bidRequests.push(bidRequestObject); - beaconParams.sizes.push(spec.joinSizesToString(validBidRequest.sizes)); + beaconParams.sizes.push(spec.joinSizesToString(sizes)); beaconParams.referer = encodeURIComponent(referer); }); diff --git a/modules/mediaimpactBidAdapter.md b/modules/mediaimpactBidAdapter.md index 2fc6697fffb..fb1a3ea27b2 100644 --- a/modules/mediaimpactBidAdapter.md +++ b/modules/mediaimpactBidAdapter.md @@ -35,7 +35,8 @@ About us : https://mediaimpact.com.ua { bidder: "mediaimpact", params: { - partnerId: 6698 + partnerId: 6698, + sizes: [[300, 600]], } } ] From af21c55ef22382e2ad50c435294298b7ac8e665e Mon Sep 17 00:00:00 2001 From: Nayan Savla Date: Thu, 4 Apr 2024 10:32:21 -0700 Subject: [PATCH 598/760] Yieldmo Bid Adapter : support for cookie deprecation label (#11294) * Sending Cdep label in Banner Sending Cdep label in the banner request. * Update yieldmoBidAdapter.js Using deepAccess for consistency. --- modules/yieldmoBidAdapter.js | 9 +++++++++ test/spec/modules/yieldmoBidAdapter_spec.js | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index 1bb0488bf5d..fe99adcec5f 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -77,6 +77,7 @@ export const spec = { let serverRequests = []; const eids = getEids(bidRequests[0]) || []; const topicsData = getTopics(bidderRequest); + const cdep = getCdep(bidderRequest); if (bannerBidRequests.length > 0) { let serverRequest = { pbav: '$prebid.version$', @@ -105,6 +106,9 @@ export const spec = { if (gpc) { serverRequest.gpc = gpc; } + if (cdep) { + serverRequest.cdep = cdep; + } if (canAccessTopWindow()) { serverRequest.pr = (LOCAL_WINDOW.document && LOCAL_WINDOW.document.referrer) || ''; @@ -438,6 +442,11 @@ function getGPCSignal(bidderRequest) { return gpc; } +function getCdep(bidderRequest) { + const cdep = deepAccess(bidderRequest, 'ortb2.device.ext.cdep') || null; + return cdep; +} + function getTopics(bidderRequest) { const userData = deepAccess(bidderRequest, 'ortb2.user.data') || []; const topicsData = userData.filter((dataObj) => { diff --git a/test/spec/modules/yieldmoBidAdapter_spec.js b/test/spec/modules/yieldmoBidAdapter_spec.js index 3c842c3a308..f37ef9178dd 100644 --- a/test/spec/modules/yieldmoBidAdapter_spec.js +++ b/test/spec/modules/yieldmoBidAdapter_spec.js @@ -414,6 +414,25 @@ describe('YieldmoAdapter', function () { })); }); + it('should add cdep to the banner bid request', function () { + const biddata = build( + [mockBannerBid()], + mockBidderRequest({ + ortb2: { + device: { + ext: { + cdep: 'test_cdep' + }, + }, + }, + }) + ); + + expect(biddata[0].data.cdep).to.equal( + 'test_cdep' + ); + }); + it('should send gpc in the banner bid request', function () { const biddata = build( [mockBannerBid()], From e405ca9f0365de045347bb3b3d0f35d09d295660 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 4 Apr 2024 11:35:24 -0700 Subject: [PATCH 599/760] Build system: fix source maps (#11293) * Build system: fix source maps * Update webpack.conf.js * turn off webpack optimization for unit tests --- karma.conf.maker.js | 3 +-- webpack.conf.js | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/karma.conf.maker.js b/karma.conf.maker.js index e05d5b08afd..7b8ca13b20b 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -15,8 +15,7 @@ function newWebpackConfig(codeCoverage, disableFeatures) { mode: 'development', devtool: 'inline-source-map', }); - - delete webpackConfig.entry; + ['entry', 'optimization'].forEach(prop => delete webpackConfig[prop]); webpackConfig.module.rules .flatMap((r) => r.use) diff --git a/webpack.conf.js b/webpack.conf.js index 8d29c786595..c74ac5c2b32 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -133,10 +133,16 @@ module.exports = { const nodeModules = path.resolve('./node_modules'); return Object.assign(libraries, { + common_deps: { + name: 'common_deps', + test(module) { + return module.resource?.startsWith(nodeModules); + } + }, core: { name: 'chunk-core', test: (module) => { - return module.resource?.startsWith(core) || module.resource?.startsWith(nodeModules); + return module.resource?.startsWith(core); } }, paapi: { From a76da0e808bc1874c56ac136a1becc0255daa514 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 5 Apr 2024 10:18:55 -0700 Subject: [PATCH 600/760] Core: fix build for node 12 (#11299) --- webpack.conf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.conf.js b/webpack.conf.js index c74ac5c2b32..c934b09d439 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -136,13 +136,13 @@ module.exports = { common_deps: { name: 'common_deps', test(module) { - return module.resource?.startsWith(nodeModules); + return module.resource && module.resource.startsWith(nodeModules); } }, core: { name: 'chunk-core', test: (module) => { - return module.resource?.startsWith(core); + return module.resource && module.resource.startsWith(core); } }, paapi: { From dc3b7c69ec725811cbd8d0678b5b3d92e9d4b82d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 5 Apr 2024 18:17:57 +0000 Subject: [PATCH 601/760] Prebid 8.43.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cb04ff9c05..0438541fd39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.43.0-pre", + "version": "8.43.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index df0c12a3e16..019350c7a29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.43.0-pre", + "version": "8.43.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 0aed5ce822192cd508e7c858bc17075ca6346f2c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 5 Apr 2024 18:17:57 +0000 Subject: [PATCH 602/760] Increment version to 8.44.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0438541fd39..b65a43a197e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.43.0", + "version": "8.44.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 019350c7a29..b7a1e1bafab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.43.0", + "version": "8.44.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From eac5e043e55538a92677100d457437b76ca898ed Mon Sep 17 00:00:00 2001 From: Victor <103455651+victorlassomarketing@users.noreply.github.com> Date: Mon, 8 Apr 2024 02:24:00 -0700 Subject: [PATCH 603/760] Lasso Bid Adapter: add gpp support (#11256) * Add gpp support and variable name update * correct unit test --- modules/lassoBidAdapter.js | 13 +++++++++++-- test/spec/modules/lassoBidAdapter_spec.js | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/lassoBidAdapter.js b/modules/lassoBidAdapter.js index e1f9636e4f1..6215af03a97 100644 --- a/modules/lassoBidAdapter.js +++ b/modules/lassoBidAdapter.js @@ -37,7 +37,6 @@ export const spec = { url: encodeURIComponent(window.location.href), bidderRequestId: bidRequest.bidderRequestId, adUnitCode: bidRequest.adUnitCode, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bidRequest.auctionId, bidId: bidRequest.bidId, transactionId: bidRequest.ortb2Imp?.ext?.tid, @@ -48,11 +47,20 @@ export const spec = { params: JSON.stringify(bidRequest.params), crumbs: JSON.stringify(bidRequest.crumbs), prebidVersion: '$prebid.version$', - version: 3, + version: 4, coppa: config.getConfig('coppa') == true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined } + if ( + bidderRequest && + bidderRequest.gppConsent && + bidderRequest.gppConsent.gppString + ) { + payload.gpp = bidderRequest.gppConsent.gppString; + payload.gppSid = bidderRequest.gppConsent.applicableSections; + } + return { method: 'GET', url: getBidRequestUrl(aimXR, bidRequest.params), @@ -74,6 +82,7 @@ export const spec = { const bidResponse = { requestId: response.bidid, + bidId: response.bidid, cpm: response.bid.price, currency: response.cur, width: response.bid.w, diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index 3695889aca0..ad4040c0452 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -126,6 +126,7 @@ describe('lassoBidAdapter', function () { it('should get the correct bid response', function () { let expectedResponse = { requestId: '123456789', + bidId: '123456789', cpm: 1, currency: 'USD', width: 728, From b37bacad16c29485418b52593961dc2184e03cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Mon, 8 Apr 2024 18:58:28 +0200 Subject: [PATCH 604/760] Criteo Bid Adapter: Fix invalid DSA adrender field mapping (#11306) --- modules/criteoBidAdapter.js | 4 ++-- test/spec/modules/criteoBidAdapter_spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index c1fcac4ae2f..2c0cacb7909 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -255,8 +255,8 @@ export const spec = { if (slot.ext?.meta?.networkName) { bid.meta = Object.assign({}, bid.meta, { networkName: slot.ext.meta.networkName }) } - if (slot.ext?.dsa?.adrender) { - bid.meta = Object.assign({}, bid.meta, { adrender: slot.ext.dsa.adrender }) + if (slot.ext?.dsa) { + bid.meta = Object.assign({}, bid.meta, { dsa: slot.ext.dsa }) } if (slot.native) { if (bidRequest.params.nativeCallback) { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 1709acb465f..2cdb09f2098 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2060,7 +2060,7 @@ describe('The Criteo bidding adapter', function () { }; const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(1); - expect(bids[0].meta.adrender).to.equal(1); + expect(bids[0].meta.dsa.adrender).to.equal(1); }); it('should properly parse a bid response with a networkId with twin ad unit banner win', function () { From 7f93cb2ece1805fa3548ee65bd83ca9b066574bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20M=C3=BCller?= Date: Mon, 8 Apr 2024 22:37:50 +0200 Subject: [PATCH 605/760] Agma Analytics Adapter: adds deviceHeight and deviceWidth to request (#11305) * Adds deviceHeight and deviceWidth to request * Increase internal version number --- modules/agmaAnalyticsAdapter.js | 5 ++++- test/spec/modules/agmaAnalyticsAdapter_spec.js | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js index e43dee063c5..f3933cc7625 100644 --- a/modules/agmaAnalyticsAdapter.js +++ b/modules/agmaAnalyticsAdapter.js @@ -17,7 +17,7 @@ import { config } from '../src/config.js'; const GVLID = 1122; const ModuleCode = 'agma'; const analyticsType = 'endpoint'; -const scriptVersion = '1.7.1'; +const scriptVersion = '1.8.0'; const batchDelayInMs = 1000; const agmaURL = 'https://pbc.agma-analytics.de/v1'; const pageViewId = generateUUID(); @@ -127,6 +127,7 @@ export const getPayload = (auctionIds, options) => { }; if (useExtendedPayload) { + const device = config.getConfig('device') || {}; const { x, y } = getScreen(); const userIdsAsEids = getUserIDs(); payload = { @@ -141,6 +142,8 @@ export const getPayload = (auctionIds, options) => { pageUrl: ri.page, screenWidth: x, screenHeight: y, + deviceWidth: device.w || screen.width, + deviceHeight: device.h || screen.height, userIdsAsEids, }; } diff --git a/test/spec/modules/agmaAnalyticsAdapter_spec.js b/test/spec/modules/agmaAnalyticsAdapter_spec.js index ba71624e3b3..df18e7bcf45 100644 --- a/test/spec/modules/agmaAnalyticsAdapter_spec.js +++ b/test/spec/modules/agmaAnalyticsAdapter_spec.js @@ -28,6 +28,8 @@ const extendedKey = [ 'referrer', 'screenHeight', 'screenWidth', + 'deviceWidth', + 'deviceHeight', 'scriptVersion', 'timestamp', 'timezoneOffset', @@ -333,6 +335,8 @@ describe('AGMA Analytics Adapter', () => { expect(request.url).to.equal(INGEST_URL); expect(requestBody).to.have.all.keys(extendedKey); expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(requestBody.deviceWidth).to.equal(screen.width); + expect(requestBody.deviceHeight).to.equal(screen.height); expect(server.requests).to.have.length(1); expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); }); From 668c7c7bdc06ff3061da6aacb761b390f015009d Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Tue, 9 Apr 2024 07:10:32 -0400 Subject: [PATCH 606/760] Remove the sending of experian rtd data through the fpd object (#11307) --- modules/sonobiBidAdapter.js | 12 +++++++----- test/spec/modules/sonobiBidAdapter_spec.js | 8 +++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 1ce7665ddfc..f8e338484b2 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -98,9 +98,16 @@ export const spec = { 'iqid': bidderSettings.get(BIDDER_CODE, 'storageAllowed') ? JSON.stringify(loadOrCreateFirstPartyData()) : null, }; + if (deepAccess(bidderRequest, 'ortb2.experianRtidData') && deepAccess(bidderRequest, 'ortb2.experianRtidKey')) { + payload.expData = deepAccess(bidderRequest, 'ortb2.experianRtidData'); + payload.expKey = deepAccess(bidderRequest, 'ortb2.experianRtidKey'); + } + const fpd = bidderRequest.ortb2; if (fpd) { + delete fpd.experianRtidData; // Omit the experian data since we already pass this through a dedicated query param + delete fpd.experianRtidKey payload.fpd = JSON.stringify(fpd); } @@ -156,11 +163,6 @@ export const spec = { payload.coppa = 0; } - if (deepAccess(bidderRequest, 'ortb2.experianRtidData') && deepAccess(bidderRequest, 'ortb2.experianRtidKey')) { - payload.expData = deepAccess(bidderRequest, 'ortb2.experianRtidData'); - payload.expKey = deepAccess(bidderRequest, 'ortb2.experianRtidKey'); - } - // If there is no key_maker data, then don't make the request. if (isEmpty(data)) { return null; diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index 83db7c0a812..fb50a90333d 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -501,7 +501,13 @@ describe('SonobiBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.data.expData).to.equal('IkhlbGxvLCB3b3JsZC4gSGVsbG8sIHdvcmxkLiBIZWxsbywgd29ybGQuIg=='); expect(bidRequests.data.expKey).to.equal('sovrn-encryption-key-1'); - }) + }); + + it('should return a properly formatted request with experianRtidData and exexperianRtidKeypKey omitted from fpd', function () { + const bidRequests = spec.buildRequests(bidRequest, bidderRequests) + expect(bidRequests.data.fpd.indexOf('experianRtidData')).to.equal(-1); + expect(bidRequests.data.fpd.indexOf('exexperianRtidKeypKey')).to.equal(-1); + }); it('should return null if there is nothing to bid on', function () { const bidRequests = spec.buildRequests([{ params: {} }], bidderRequests) From 4577159c0ec31896e5e0bd7654638861a265c189 Mon Sep 17 00:00:00 2001 From: CPG Date: Tue, 9 Apr 2024 13:24:22 +0200 Subject: [PATCH 607/760] Appnexus Bid Adapter: added emetriq as an alias (#11302) * Add "emetriq" as new "appnexus" alias * New Adapter: emetriq --- modules/appnexusBidAdapter.js | 1 + modules/sirdataRtdProvider.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 590ad264515..5210d6f7562 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -111,6 +111,7 @@ export const spec = { aliases: [ { code: 'appnexusAst', gvlid: 32 }, { code: 'emxdigital', gvlid: 183 }, + { code: 'emetriq', gvlid: 213 }, { code: 'pagescience', gvlid: 32 }, { code: 'gourmetads', gvlid: 32 }, { code: 'matomy', gvlid: 32 }, diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index aaa3c48856b..97d0ec5219c 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -30,6 +30,7 @@ const partnerIds = { 'appnexus': 27446, 'appnexusAst': 27446, 'brealtime': 27446, + 'emetriq': 27446, 'emxdigital': 27446, 'pagescience': 27446, 'gourmetads': 33394, @@ -353,6 +354,7 @@ export function addSegmentData(reqBids, data, moduleConfig, onDone) { case 'appnexus': case 'appnexusAst': case 'brealtime': + case 'emetriq': case 'emxdigital': case 'pagescience': case 'gourmetads': From ca5162b18526b27d47f02d9b087772a0125e1852 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Tue, 9 Apr 2024 17:05:53 +0300 Subject: [PATCH 608/760] Adkernel Bid Adapter: remove legacy native impl (#11291) --- modules/adkernelBidAdapter.js | 113 +++---------------- test/spec/modules/adkernelBidAdapter_spec.js | 74 ++++++++---- 2 files changed, 68 insertions(+), 119 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 491808f2602..ae02a8967b1 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,6 +1,5 @@ import { _each, - cleanObj, contains, createTrackPixelHtml, deepAccess, @@ -20,7 +19,6 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; /** @@ -39,7 +37,7 @@ const VIDEO_FPD = ['battr', 'pos']; const NATIVE_FPD = ['battr', 'api']; const BANNER_PARAMS = ['pos']; const BANNER_FPD = ['btype', 'battr', 'pos', 'api']; -const VERSION = '1.6'; +const VERSION = '1.7'; const SYNC_IFRAME = 1; const SYNC_IMAGE = 2; const SYNC_TYPES = { @@ -48,29 +46,6 @@ const SYNC_TYPES = { }; const GVLID = 14; -const NATIVE_MODEL = [ - {name: 'title', assetType: 'title'}, - {name: 'icon', assetType: 'img', type: 1}, - {name: 'image', assetType: 'img', type: 3}, - {name: 'body', assetType: 'data', type: 2}, - {name: 'body2', assetType: 'data', type: 10}, - {name: 'sponsoredBy', assetType: 'data', type: 1}, - {name: 'phone', assetType: 'data', type: 8}, - {name: 'address', assetType: 'data', type: 9}, - {name: 'price', assetType: 'data', type: 6}, - {name: 'salePrice', assetType: 'data', type: 7}, - {name: 'cta', assetType: 'data', type: 12}, - {name: 'rating', assetType: 'data', type: 3}, - {name: 'downloads', assetType: 'data', type: 5}, - {name: 'likes', assetType: 'data', type: 4}, - {name: 'displayUrl', assetType: 'data', type: 11} -]; - -const NATIVE_INDEX = NATIVE_MODEL.reduce((acc, val, idx) => { - acc[val.name] = {id: idx, ...val}; - return acc; -}, {}); - const MULTI_FORMAT_SUFFIX = '__mf'; const MULTI_FORMAT_SUFFIX_BANNER = 'b' + MULTI_FORMAT_SUFFIX; const MULTI_FORMAT_SUFFIX_VIDEO = 'v' + MULTI_FORMAT_SUFFIX; @@ -139,9 +114,6 @@ export const spec = { * @returns {ServerRequest[]} */ buildRequests: function (bidRequests, bidderRequest) { - // convert Native ORTB definition to old-style prebid native definition - bidRequests = convertOrtbRequestToProprietaryNative(bidRequests); - let impGroups = groupImpressionsByHostZone(bidRequests, bidderRequest.refererInfo); let requests = []; let schain = bidRequests[0].schain; @@ -199,7 +171,9 @@ export const spec = { prBid.height = imp.video.h; } else if ('native' in imp) { prBid.mediaType = NATIVE; - prBid.native = buildNativeAd(JSON.parse(rtbBid.adm)); + prBid.native = { + ortb: buildNativeAd(rtbBid.adm) + }; } if (isStr(rtbBid.dealid)) { prBid.dealId = rtbBid.dealid; @@ -345,11 +319,9 @@ function buildImps(bidRequest, secure) { } else { typedImp = imp; } - let nativeRequest = buildNativeRequest(mediaTypes.native); typedImp.native = { ...getDefinedParamsOrEmpty(bidRequest.ortb2Imp, NATIVE_FPD), - ver: '1.1', - request: JSON.stringify(nativeRequest) + request: JSON.stringify(bidRequest.nativeOrtbRequest) }; initImpBidfloor(typedImp, bidRequest, sizes, isMultiformat ? '*' : NATIVE); result.push(typedImp); @@ -371,51 +343,6 @@ function getDefinedParamsOrEmpty(object, params) { return getDefinedParams(object, params); } -/** - * Builds native request from native adunit - */ -function buildNativeRequest(nativeReq) { - let request = {ver: '1.1', assets: []}; - for (let k of Object.keys(nativeReq)) { - let v = nativeReq[k]; - let desc = NATIVE_INDEX[k]; - if (desc === undefined) { - continue; - } - let assetRoot = { - id: desc.id, - required: ~~v.required, - }; - if (desc.assetType === 'img') { - assetRoot[desc.assetType] = buildImageAsset(desc, v); - } else if (desc.assetType === 'data') { - assetRoot.data = cleanObj({type: desc.type, len: v.len}); - } else if (desc.assetType === 'title') { - assetRoot.title = {len: v.len || 90}; - } else { - return; - } - request.assets.push(assetRoot); - } - return request; -} - -/** - * Builds image asset request - */ -function buildImageAsset(desc, val) { - let img = { - type: desc.type - }; - if (val.sizes) { - [img.w, img.h] = val.sizes; - } else if (val.aspect_ratios) { - img.wmin = val.aspect_ratios[0].min_width; - img.hmin = val.aspect_ratios[0].min_height; - } - return cleanObj(img); -} - /** * Checks if configuration allows specified sync method * @param syncRule {Object} @@ -540,12 +467,11 @@ function makeRegulations(bidderRequest) { * @returns */ function makeBaseRequest(bidderRequest, imps, fpd) { - let {timeout} = bidderRequest; let request = { 'id': bidderRequest.bidderRequestId, 'imp': imps, 'at': 1, - 'tmax': parseInt(timeout) + 'tmax': parseInt(bidderRequest.timeout) }; if (!isEmpty(fpd.bcat)) { request.bcat = fpd.bcat; @@ -665,26 +591,15 @@ function validateNativeImageSize(img) { } /** - * Creates native ad for native 1.1 response + * Creates native ad for native 1.2 response */ -function buildNativeAd(nativeResp) { - const {assets, link, imptrackers, jstracker, privacy} = nativeResp.native; - let nativeAd = { - clickUrl: link.url, - impressionTrackers: imptrackers, - javascriptTrackers: jstracker ? [jstracker] : undefined, - privacyLink: privacy, - }; - _each(assets, asset => { - let assetName = NATIVE_MODEL[asset.id].name; - let assetType = NATIVE_MODEL[asset.id].assetType; - nativeAd[assetName] = asset[assetType].text || asset[assetType].value || cleanObj({ - url: asset[assetType].url, - width: asset[assetType].w, - height: asset[assetType].h - }); - }); - return cleanObj(nativeAd); +function buildNativeAd(adm) { + let resp = JSON.parse(adm); + // temporary workaround for top-level native object wrapper + if ('native' in resp) { + resp = resp.native; + } + return resp; } function stripMultiformatSuffix(impid) { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index ade34478c20..cdfc9795b85 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -125,7 +125,8 @@ describe('Adkernel adapter', function () { bidId: 'Bid_01', bidderRequestId: 'req-001', auctionId: 'auc-001' - }, bid_native = { + }, + bid_native = { bidder: 'adkernel', params: {zoneId: 1, host: 'rtb.adkernel.com'}, mediaTypes: { @@ -171,6 +172,33 @@ describe('Adkernel adapter', function () { } } }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { + id: 0, required: 1, title: {len: 80} + }, { + id: 1, required: 1, data: {type: 2}}, + { + id: 2, required: 1, data: {type: 10} + }, { + id: 3, required: 1, img: {type: 1, wmin: 50, hmin: 50} + }, { + id: 4, required: 1, img: {type: 3, w: 300, h: 200} + }, { + id: 5, required: 0, data: {type: 3} + }, { + id: 6, required: 0, data: {type: 6} + }, { + id: 7, required: 0, data: {type: 12} + }, { + id: 8, required: 0, data: {type: 1} + }, { + id: 9, required: 0, data: {type: 11} + } + ], + privacy: 1 + }, adUnitCode: 'ad-unit-1', transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', bidId: 'Bid_01', @@ -679,18 +707,18 @@ describe('Adkernel adapter', function () { expect(bidRequests[0].imp[0]).to.have.property('native'); expect(bidRequests[0].imp[0].native).to.have.property('request'); let request = JSON.parse(bidRequests[0].imp[0].native.request); - expect(request).to.have.property('ver', '1.1'); + expect(request).to.have.property('ver', '1.2'); expect(request.assets).to.have.length(10); expect(request.assets[0]).to.be.eql({id: 0, required: 1, title: {len: 80}}); - expect(request.assets[1]).to.be.eql({id: 3, required: 1, data: {type: 2}}); - expect(request.assets[2]).to.be.eql({id: 4, required: 1, data: {type: 10}}); - expect(request.assets[3]).to.be.eql({id: 1, required: 1, img: {wmin: 50, hmin: 50, type: 1}}); - expect(request.assets[4]).to.be.eql({id: 2, required: 1, img: {w: 300, h: 200, type: 3}}); - expect(request.assets[5]).to.be.eql({id: 11, required: 0, data: {type: 3}}); - expect(request.assets[6]).to.be.eql({id: 8, required: 0, data: {type: 6}}); - expect(request.assets[7]).to.be.eql({id: 10, required: 0, data: {type: 12}}); - expect(request.assets[8]).to.be.eql({id: 5, required: 0, data: {type: 1}}); - expect(request.assets[9]).to.be.eql({id: 14, required: 0, data: {type: 11}}); + expect(request.assets[1]).to.be.eql({id: 1, required: 1, data: {type: 2}}); + expect(request.assets[2]).to.be.eql({id: 2, required: 1, data: {type: 10}}); + expect(request.assets[3]).to.be.eql({id: 3, required: 1, img: {wmin: 50, hmin: 50, type: 1}}); + expect(request.assets[4]).to.be.eql({id: 4, required: 1, img: {w: 300, h: 200, type: 3}}); + expect(request.assets[5]).to.be.eql({id: 5, required: 0, data: {type: 3}}); + expect(request.assets[6]).to.be.eql({id: 6, required: 0, data: {type: 6}}); + expect(request.assets[7]).to.be.eql({id: 7, required: 0, data: {type: 12}}); + expect(request.assets[8]).to.be.eql({id: 8, required: 0, data: {type: 1}}); + expect(request.assets[9]).to.be.eql({id: 9, required: 0, data: {type: 11}}); }); it('native response processing', () => { @@ -707,15 +735,21 @@ describe('Adkernel adapter', function () { expect(resp.meta.secondaryCatIds).to.be.eql(['IAB1-4', 'IAB8-16', 'IAB25-5']); expect(resp).to.have.property('mediaType', NATIVE); expect(resp).to.have.property('native'); - expect(resp.native).to.have.property('clickUrl', 'http://rtb.com/click?i=pTuOlf5KHUo_0'); - expect(resp.native.impressionTrackers).to.be.eql(['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp']); - expect(resp.native).to.have.property('title', 'Title'); - expect(resp.native).to.have.property('body', 'Description'); - expect(resp.native).to.have.property('body2', 'Additional description'); - expect(resp.native.icon).to.be.eql({url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', width: 50, height: 50}); - expect(resp.native.image).to.be.eql({url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', width: 300, height: 200}); - expect(resp.native).to.have.property('sponsoredBy', 'Sponsor.com'); - expect(resp.native).to.have.property('displayUrl', 'displayurl.com'); + expect(resp.native).to.have.property('ortb'); + + expect(resp.native.ortb).to.be.eql({ + assets: [ + {id: 0, title: {text: 'Title'}}, + {id: 3, data: {value: 'Description'}}, + {id: 4, data: {value: 'Additional description'}}, + {id: 1, img: {url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0&imgt=icon', w: 50, h: 50}}, + {id: 2, img: {url: 'http://rtb.com/thumbnail?i=pTuOlf5KHUo_0', w: 300, h: 200}}, + {id: 5, data: {value: 'Sponsor.com'}}, + {id: 14, data: {value: 'displayurl.com'}} + ], + link: {url: 'http://rtb.com/click?i=pTuOlf5KHUo_0'}, + imptrackers: ['http://rtb.com/win?i=pTuOlf5KHUo_0&f=imp'] + }); }); }); }); From db0b1bdc23fd89b94dbd43bad652d3997ed15b15 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Tue, 9 Apr 2024 11:19:52 -0400 Subject: [PATCH 609/760] refactors code for userIds; updates userId tests (#29) (#11298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: “Nick <“nick.llerandi”@kargo.com> --- modules/kargoBidAdapter.js | 41 ++++++++++++----------- test/spec/modules/kargoBidAdapter_spec.js | 36 ++++++++++++++++++++ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index e86d7022987..fe22915223e 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -359,56 +359,57 @@ function getUserIds(tdidAdapter, usp, gdpr, eids, gpp) { crbIDs: crb.syncIds || {} }; - // Pull Trade Desk ID from adapter - if (tdidAdapter) { - userIds.tdID = tdidAdapter; - } - - // Pull Trade Desk ID from our storage + // Pull Trade Desk ID if (!tdidAdapter && crb.tdID) { userIds.tdID = crb.tdID; + } else if (tdidAdapter) { + userIds.tdID = tdidAdapter; } + // USP if (usp) { userIds.usp = usp; } - try { - if (gdpr) { - userIds['gdpr'] = { - consent: gdpr.consentString || '', - applies: !!gdpr.gdprApplies, - } - } - } catch (e) { + // GDPR + if (gdpr) { + userIds.gdpr = { + consent: gdpr.consentString || '', + applies: !!gdpr.gdprApplies, + }; } + // Kargo ID if (crb.lexId != null) { userIds.kargoID = crb.lexId; } + // Client ID if (crb.clientId != null) { userIds.clientID = crb.clientId; } + // Opt Out if (crb.optOut != null) { userIds.optOut = crb.optOut; } + // User ID Sub-Modules (userIdAsEids) if (eids != null) { userIds.sharedIDEids = eids; } + // GPP if (gpp) { - const parsedGPP = {} - if (gpp && gpp.consentString) { - parsedGPP.gppString = gpp.consentString + const parsedGPP = {}; + if (gpp.consentString) { + parsedGPP.gppString = gpp.consentString; } - if (gpp && gpp.applicableSections) { - parsedGPP.applicableSections = gpp.applicableSections + if (gpp.applicableSections) { + parsedGPP.applicableSections = gpp.applicableSections; } if (!isEmpty(parsedGPP)) { - userIds.gpp = parsedGPP + userIds.gpp = parsedGPP; } } diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 20a43d0397f..eb8f310201d 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -112,6 +112,24 @@ describe('kargo adapter tests', function () { } } ] + }, + { + 'source': 'adquery.io', + 'uids': [ + { + 'id': 'adqueryId-123', + 'atype': 1 + } + ] + }, + { + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'criteoId-456', + 'atype': 1 + } + ] } ], floorData: { @@ -582,6 +600,24 @@ describe('kargo adapter tests', function () { } } ] + }, + { + source: 'adquery.io', + uids: [ + { + id: 'adqueryId-123', + atype: 1 + } + ] + }, + { + source: 'criteo.com', + uids: [ + { + id: 'criteoId-456', + atype: 1 + } + ] } ], data: [ From beeb9017b5a398eca4facb014e7db2f05825187d Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Tue, 9 Apr 2024 16:04:11 -0400 Subject: [PATCH 610/760] Removed sending of experian data to Sonobi's bid endpoint (#11309) --- modules/sonobiBidAdapter.js | 5 ----- test/spec/modules/sonobiBidAdapter_spec.js | 8 -------- 2 files changed, 13 deletions(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index f8e338484b2..e1b51affd09 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -98,11 +98,6 @@ export const spec = { 'iqid': bidderSettings.get(BIDDER_CODE, 'storageAllowed') ? JSON.stringify(loadOrCreateFirstPartyData()) : null, }; - if (deepAccess(bidderRequest, 'ortb2.experianRtidData') && deepAccess(bidderRequest, 'ortb2.experianRtidKey')) { - payload.expData = deepAccess(bidderRequest, 'ortb2.experianRtidData'); - payload.expKey = deepAccess(bidderRequest, 'ortb2.experianRtidKey'); - } - const fpd = bidderRequest.ortb2; if (fpd) { diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index fb50a90333d..c7f954cfdcf 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -495,14 +495,6 @@ describe('SonobiBidAdapter', function () { expect(bidRequests.data.hfa).to.equal('hfakey') }) - it('should return a properly formatted request with expData and expKey', function () { - bidderRequests.ortb2.experianRtidData = 'IkhlbGxvLCB3b3JsZC4gSGVsbG8sIHdvcmxkLiBIZWxsbywgd29ybGQuIg=='; - bidderRequests.ortb2.experianRtidKey = 'sovrn-encryption-key-1'; - const bidRequests = spec.buildRequests(bidRequest, bidderRequests) - expect(bidRequests.data.expData).to.equal('IkhlbGxvLCB3b3JsZC4gSGVsbG8sIHdvcmxkLiBIZWxsbywgd29ybGQuIg=='); - expect(bidRequests.data.expKey).to.equal('sovrn-encryption-key-1'); - }); - it('should return a properly formatted request with experianRtidData and exexperianRtidKeypKey omitted from fpd', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests) expect(bidRequests.data.fpd.indexOf('experianRtidData')).to.equal(-1); From de42f8d23da4d84c6e761aec1c2e3510e6dd3bd9 Mon Sep 17 00:00:00 2001 From: Remi Henriot Date: Wed, 10 Apr 2024 09:01:17 +0200 Subject: [PATCH 611/760] Adagio Analytics: change bidder code for aliases and add bidder cpm (#11301) --- modules/adagioAnalyticsAdapter.js | 79 ++++++++-------- .../modules/adagioAnalyticsAdapter_spec.js | 94 ++++++++++++------- 2 files changed, 100 insertions(+), 73 deletions(-) diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index de0aa1cb5d7..82b3b356c81 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -17,6 +17,8 @@ const ADAGIO_GVLID = 617; const VERSION = '3.0.0'; const PREBID_VERSION = '$prebid.version$'; const ENDPOINT = 'https://c.4dex.io/pba.gif'; +const CURRENCY_USD = 'USD'; +const ADAGIO_CODE = 'adagio'; const cache = { auctions: {}, getAuction: function(auctionId, adUnitCode) { @@ -92,17 +94,11 @@ function removeDuplicates(arr, getKey) { }); }; -function getAdapterNameForAlias(aliasName) { - return adapterManager.aliasRegistry[aliasName] || aliasName; -}; - -function isAdagio(value) { - if (!value) { +function isAdagio(alias) { + if (!alias) { return false } - - return value.toLowerCase().includes('adagio') || - getAdapterNameForAlias(value).toLowerCase().includes('adagio'); + return (alias + adapterManager.aliasRegistry[alias]).toLowerCase().includes(ADAGIO_CODE); }; function getMediaTypeAlias(mediaType) { @@ -129,6 +125,26 @@ function addKeyPrefix(obj, prefix) { }, {}); } +function getUsdCpm(cpm, currency) { + let netCpm = cpm + + if (typeof currency === 'string' && currency.toUpperCase() !== CURRENCY_USD) { + if (typeof getGlobal().convertCurrency === 'function') { + netCpm = parseFloat(Number(getGlobal().convertCurrency(cpm, currency, CURRENCY_USD))).toFixed(3); + } else { + netCpm = null + } + } + return netCpm +} + +function getCurrencyData(bid) { + return { + netCpm: getUsdCpm(bid.cpm, bid.currency), + orginalCpm: getUsdCpm(bid.originalCpm, bid.originalCurrency) + } +} + /** * sendRequest to Adagio. It filter null values and encode each query param. * @param {Object} qp @@ -258,7 +274,7 @@ function handlerAuctionInit(event) { t_v: params.testVersion || null, mts: mediaTypesKeys.join(','), ban_szs: bannerSizes.join(','), - bdrs: bidders.map(bidder => getAdapterNameForAlias(bidder.bidder)).sort().join(','), + bdrs: bidders.map(bidder => bidder.bidder).sort().join(','), adg_mts: adagioMediaTypes.join(',') }; @@ -299,15 +315,22 @@ function handlerAuctionEnd(event) { const adUnitCodes = cache.getAllAdUnitCodes(auctionId); adUnitCodes.forEach(adUnitCode => { - const mapper = (bidder) => event.bidsReceived.find(bid => bid.adUnitCode === adUnitCode && bid.bidder === bidder) ? '1' : '0'; + const bidResponseMapper = (bidder) => { + const bid = event.bidsReceived.find(bid => bid.adUnitCode === adUnitCode && bid.bidder === bidder) + return bid ? '1' : '0' + } + const bidCpmMapper = (bidder) => { + const bid = event.bidsReceived.find(bid => bid.adUnitCode === adUnitCode && bid.bidder === bidder) + return bid ? getCurrencyData(bid).netCpm : null + } cache.updateAuction(auctionId, adUnitCode, { - bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(mapper).join(',') + bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidResponseMapper).join(','), + bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(',') }); sendNewBeacon(auctionId, adUnitCode); }); } - function handlerBidWon(event) { let auctionId = getTargetedAuctionId(event); @@ -315,20 +338,7 @@ function handlerBidWon(event) { return; } - let adsCurRateToUSD = (event.currency === 'USD') ? 1 : null; - let ogCurRateToUSD = (event.originalCurrency === 'USD') ? 1 : null; - try { - if (typeof getGlobal().convertCurrency === 'function') { - // Currency module is loaded, we can calculate the conversion rate. - - // Get the conversion rate from the original currency to USD. - ogCurRateToUSD = getGlobal().convertCurrency(1, event.originalCurrency, 'USD'); - // Get the conversion rate from the ad server currency to USD. - adsCurRateToUSD = getGlobal().convertCurrency(1, event.currency, 'USD'); - } - } catch (error) { - logError('Error on Adagio Analytics Adapter - handlerBidWon', error); - } + const currencyData = getCurrencyData(event) const adagioAuctionCacheId = ( (event.latestTargetedAuctionId && event.latestTargetedAuctionId !== event.auctionId) @@ -336,19 +346,12 @@ function handlerBidWon(event) { : null); cache.updateAuction(auctionId, event.adUnitCode, { - win_bdr: getAdapterNameForAlias(event.bidder), + win_bdr: event.bidder, win_mt: getMediaTypeAlias(event.mediaType), win_ban_sz: event.mediaType === BANNER ? `${event.width}x${event.height}` : null, - // ad server currency - win_cpm: event.cpm, - cur: event.currency, - cur_rate: adsCurRateToUSD, - - // original currency from bidder - og_cpm: event.originalCpm, - og_cur: event.originalCurrency, - og_cur_rate: ogCurRateToUSD, + win_net_cpm: currencyData.netCpm, + win_og_cpm: currencyData.orginalCpm, // cache bid id auct_id_c: adagioAuctionCacheId, @@ -428,7 +431,7 @@ adagioAdapter.enableAnalytics = config => { adapterManager.registerAnalyticsAdapter({ adapter: adagioAdapter, - code: 'adagio', + code: ADAGIO_CODE, gvlid: ADAGIO_GVLID, }); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 5ffd7b0b685..64740f32b06 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -1,8 +1,8 @@ import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter.js'; import { expect } from 'chai'; import * as utils from 'src/utils.js'; -import { getGlobal } from 'src/prebidGlobal.js'; import { server } from 'test/mocks/xhr.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); @@ -647,17 +647,19 @@ describe('adagio analytics adapter', () => { }); it('builds and sends auction data', () => { - getGlobal().convertCurrency = (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - }; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + convertCurrency: (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + } + }); events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); @@ -697,6 +699,7 @@ describe('adagio analytics adapter', () => { expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('1,1,0'); + expect(search.bdrs_cpm).to.equal('1.42,2.052,'); } { @@ -710,27 +713,25 @@ describe('adagio analytics adapter', () => { expect(search.win_bdr).to.equal('another'); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); - expect(search.win_cpm).to.equal('1.71'); - expect(search.cur).to.equal('EUR'); - expect(search.cur_rate).to.equal('1.2'); - expect(search.og_cpm).to.equal('1.62'); - expect(search.og_cur).to.equal('GBP'); - expect(search.og_cur_rate).to.equal('1.6'); + expect(search.win_net_cpm).to.equal('2.052'); + expect(search.win_og_cpm).to.equal('2.592'); } }); it('builds and sends auction data with a cached bid win', () => { - getGlobal().convertCurrency = (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - }; + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + convertCurrency: (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + } + }); events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); @@ -794,6 +795,7 @@ describe('adagio analytics adapter', () => { expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); expect(search.bdrs_bid).to.equal('0,0,0'); + expect(search.bdrs_cpm).to.equal(',,'); } { @@ -808,12 +810,8 @@ describe('adagio analytics adapter', () => { expect(search.win_bdr).to.equal('adagio'); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); - expect(search.win_cpm).to.equal('1.42'); - expect(search.cur).to.equal('USD'); - expect(search.cur_rate).to.equal('1'); - expect(search.og_cpm).to.equal('1.42'); - expect(search.og_cur).to.equal('USD'); - expect(search.og_cur_rate).to.equal('1'); + expect(search.win_net_cpm).to.equal('1.42'); + expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.not.exist; } @@ -829,5 +827,31 @@ describe('adagio analytics adapter', () => { expect(search.rndr).to.equal('0'); } }); + + it('send an "empty" cpm when adserver currency != USD and convertCurrency() is undefined', () => { + sandbox.stub(prebidGlobal, 'getGlobal').returns({}); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); + events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); + + expect(server.requests.length).to.equal(3, 'requests count'); + + // fail to compute bidder cpm and send an "empty" cpm + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.e_sid).to.equal('42'); + expect(search.e_pba_test).to.equal('true'); + expect(search.bdrs_bid).to.equal('1,1,0'); + expect(search.bdrs_cpm).to.equal('1.42,,'); + } + }); }); }); From 7dbcb47845e11329be49049c276617aa3567ab8d Mon Sep 17 00:00:00 2001 From: MartinGumGum <109325501+MartinGumGum@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:47:13 -0700 Subject: [PATCH 612/760] GumGum Bid Adapter : Add support for the DSA (#11311) * Fix for the multi-size in- slot * added comment * displaymanager: add field for displaymanager and displaymanagerver * fix: displayManager to include 'gumgum' * fix: test case * Add support for DSA in the bid request --------- Co-authored-by: ahzgg Co-authored-by: ahzgg <163184035+ahzgg@users.noreply.github.com> --- modules/gumgumBidAdapter.js | 5 ++++- test/spec/modules/gumgumBidAdapter_spec.js | 23 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 60851d2b4dd..ff249ec9b50 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -422,6 +422,10 @@ function buildRequests(validBidRequests, bidderRequest) { data.gppString = bidderRequest.ortb2.regs.gpp data.gppSid = Array.isArray(bidderRequest.ortb2.regs.gpp_sid) ? bidderRequest.ortb2.regs.gpp_sid.join(',') : '' } + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + data.dsa = dsa + } if (coppa) { data.coppa = coppa; } @@ -549,7 +553,6 @@ function interpretResponse(serverResponse, bidRequest) { mediaType: type || mediaType }; let sizes = parseSizesInput(bidRequest.sizes); - if (maxw && maxh) { sizes = [`${maxw}x${maxh}`]; } else if (product === 5 && includes(sizes, '1x1')) { diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 29e372d0f87..3720d357b51 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -589,6 +589,29 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.gppString).to.eq('') expect(bidRequest.data.gppSid).to.eq('') }); + it('should add DSA information to payload if available', function () { + // Define the sample ORTB2 object with DSA information + const ortb2 = { + regs: { + ext: { + dsa: { + dsarequired: '1', + pubrender: '2', + datatopub: '3', + transparency: [{ + domain: 'test.com', + dsaparams: [1, 2, 3] + }] + } + } + } + }; + const fakeBidRequest = { ortb2 }; + // Call the buildRequests function to generate the bid request + const [bidRequest] = spec.buildRequests(bidRequests, fakeBidRequest); + // Assert that the DSA information in the bid request matches the provided ORTB2 data + expect(bidRequest.data.dsa).to.deep.equal(fakeBidRequest.ortb2.regs.ext.dsa); + }); it('should not set coppa parameter if coppa config is set to false', function () { config.setConfig({ coppa: false From 458036a383c89a08f046d6873f362ad4d5edbbf6 Mon Sep 17 00:00:00 2001 From: Karim Mourra Date: Thu, 11 Apr 2024 01:01:18 -0300 Subject: [PATCH 613/760] JW Player RTD Module : populate content url, title and description (#11178) * caches items * updates fetch tests * verifies against entire object * updates docs * appens to ext * renames tests * parses source from playlist * Update modules/jwplayerRtdProvider.js Co-authored-by: Demetrio Girardi * Update modules/jwplayerRtdProvider.js Co-authored-by: Demetrio Girardi * adds indentation * allow override configuration * adds tests * uses labels * populates tests * updates tests * adds config docs * updates demo * uses ix * adds todos --------- Co-authored-by: Demetrio Girardi --- .../jwplayerRtdProvider_example.html | 70 +- modules/jwplayerRtdProvider.js | 119 ++- modules/jwplayerRtdProvider.md | 25 +- test/spec/modules/jwplayerRtdProvider_spec.js | 701 +++++++++++++++++- 4 files changed, 816 insertions(+), 99 deletions(-) rename integrationExamples/{gpt => realTimeData}/jwplayerRtdProvider_example.html (51%) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html similarity index 51% rename from integrationExamples/gpt/jwplayerRtdProvider_example.html rename to integrationExamples/realTimeData/jwplayerRtdProvider_example.html index 41c27b70ece..4f29ef1c406 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html @@ -1,8 +1,10 @@ + + /* Paste JW Player script tag here */ + - JW Player RTD Provider Example + function renderHighestBid() { + const highestBids = pbjs.getHighestCpmBids('video-ad-unit'); + const highestBid = highestBids && highestBids.length && highestBids[0]; + if (!highestBid) { + return; + } - -
Div-1
-
- -
diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index e8c1c445816..29ce0da5317 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -23,10 +23,20 @@ import {getGlobal} from '../src/prebidGlobal.js'; const SUBMODULE_NAME = 'jwplayer'; const JWPLAYER_DOMAIN = SUBMODULE_NAME + '.com'; -const segCache = {}; +const ENRICH_ALWAYS = 'always'; +const ENRICH_WHEN_EMPTY = 'whenEmpty'; +const ENRICH_NEVER = 'never'; +const overrideValidationRegex = /^(always|never|whenEmpty)$/; +const playlistItemCache = {}; const pendingRequests = {}; let activeRequestCount = 0; let resumeBidRequest; +// defaults to 'always' for backwards compatibility +// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY +let overrideContentId = ENRICH_ALWAYS; +let overrideContentUrl = ENRICH_WHEN_EMPTY; +let overrideContentTitle = ENRICH_WHEN_EMPTY; +let overrideContentDescription = ENRICH_WHEN_EMPTY; /** @type {RtdSubmodule} */ export const jwplayerSubmodule = { @@ -38,7 +48,7 @@ export const jwplayerSubmodule = { /** * add targeting data to bids and signal completion to realTimeData module * @function - * @param {Obj} bidReqConfig + * @param {object} bidReqConfig * @param {function} onDone */ getBidRequestData: enrichBidRequest, @@ -53,6 +63,7 @@ config.getConfig('realTimeData', ({realTimeData}) => { return; } fetchTargetingInformation(params); + setOverrides(params); }); submodule('realTimeData', jwplayerSubmodule); @@ -71,15 +82,32 @@ export function fetchTargetingInformation(jwTargeting) { }); } +export function setOverrides(params) { + // For backwards compatibility, default to always unless overridden by Publisher. + // TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY + overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_ALWAYS); + overrideContentUrl = sanitizeOverrideParam(params.overrideContentUrl, ENRICH_WHEN_EMPTY); + overrideContentTitle = sanitizeOverrideParam(params.overrideContentTitle, ENRICH_WHEN_EMPTY); + overrideContentDescription = sanitizeOverrideParam(params.overrideContentDescription, ENRICH_WHEN_EMPTY); +} + +function sanitizeOverrideParam(overrideParam, defaultValue) { + if (overrideValidationRegex.test(overrideParam)) { + return overrideParam; + } + + return defaultValue; +} + export function fetchTargetingForMediaId(mediaId) { const ajax = ajaxBuilder(); // TODO: Avoid checking undefined vs null by setting a callback to pendingRequests. pendingRequests[mediaId] = null; ajax(`https://cdn.${JWPLAYER_DOMAIN}/v2/media/${mediaId}`, { success: function (response) { - const segment = parseSegment(response); - cacheSegments(segment, mediaId); - onRequestCompleted(mediaId, !!segment); + const item = parsePlaylistItem(response); + cachePlaylistItem(item, mediaId); + onRequestCompleted(mediaId, !!item); }, error: function () { logError('failed to retrieve targeting information'); @@ -88,8 +116,8 @@ export function fetchTargetingForMediaId(mediaId) { }); } -function parseSegment(response) { - let segment; +function parsePlaylistItem(response) { + let item; try { const data = JSON.parse(response); if (!data) { @@ -101,16 +129,16 @@ function parseSegment(response) { throw ('Empty playlist'); } - segment = playlist[0].jwpseg; + item = playlist[0]; } catch (err) { logError(err); } - return segment; + return item; } -function cacheSegments(jwpseg, mediaId) { - if (jwpseg && mediaId) { - segCache[mediaId] = jwpseg; +function cachePlaylistItem(playlistItem, mediaId) { + if (playlistItem && mediaId) { + playlistItemCache[mediaId] = playlistItem; } } @@ -167,7 +195,7 @@ export function enrichAdUnits(adUnits, ortb2Fragments = {}) { const contentData = getContentData(mediaId, contentSegments); const targeting = formatTargetingResponse(vat); enrichBids(adUnit.bids, targeting, contentId, contentData); - addOrtbSiteContent(ortb2Fragments.global, contentId, contentData); + addOrtbSiteContent(ortb2Fragments.global, contentId, contentData, vat.title, vat.description, vat.mediaUrl); }; loadVat(jwTargeting, onVatResponse); }); @@ -217,18 +245,27 @@ function loadVatForPendingRequest(playerDivId, mediaID, callback) { } export function getVatFromCache(mediaID) { - const segments = segCache[mediaID]; + const item = playlistItemCache[mediaID]; - if (!segments) { + if (!item) { return null; } + const mediaUrl = item.file ?? getFileFromSources(item); + return { - segments, + segments: item.jwpseg, + title: item.title, + description: item.description, + mediaUrl, mediaID }; } +function getFileFromSources(playlistItem) { + return playlistItem.sources?.find?.(source => !!source.file)?.file; +} + export function getVatFromPlayer(playerDivId, mediaID) { const player = getPlayer(playerDivId); if (!player) { @@ -241,12 +278,18 @@ export function getVatFromPlayer(playerDivId, mediaID) { } mediaID = mediaID || item.mediaid; + const title = item.title; + const description = item.description; + const mediaUrl = item.file; const segments = item.jwpseg; - cacheSegments(segments, mediaID) + cachePlaylistItem(item, mediaID) return { segments, - mediaID + mediaID, + title, + mediaUrl, + description }; } @@ -313,11 +356,7 @@ export function getContentData(mediaId, segments) { return contentData; } -export function addOrtbSiteContent(ortb2, contentId, contentData) { - if (!contentId && !contentData) { - return; - } - +export function addOrtbSiteContent(ortb2, contentId, contentData, contentTitle, contentDescription, contentUrl) { if (ortb2 == null) { ortb2 = {}; } @@ -325,11 +364,24 @@ export function addOrtbSiteContent(ortb2, contentId, contentData) { let site = ortb2.site = ortb2.site || {}; let content = site.content = site.content || {}; - if (contentId) { + if (shouldOverride(content.id, contentId, overrideContentId)) { content.id = contentId; } - const currentData = content.data = content.data || []; + if (shouldOverride(content.url, contentUrl, overrideContentUrl)) { + content.url = contentUrl; + } + + if (shouldOverride(content.title, contentTitle, overrideContentTitle)) { + content.title = contentTitle; + } + + if (shouldOverride(content.ext && content.ext.description, contentDescription, overrideContentDescription)) { + content.ext = content.ext || {}; + content.ext.description = contentDescription; + } + + const currentData = content.data || []; // remove old jwplayer data const data = currentData.filter(datum => datum.name !== JWPLAYER_DOMAIN); @@ -337,11 +389,26 @@ export function addOrtbSiteContent(ortb2, contentId, contentData) { data.push(contentData); } - content.data = data; + if (data.length) { + content.data = data; + } return ortb2; } +function shouldOverride(currentValue, newValue, configValue) { + switch (configValue) { + case ENRICH_ALWAYS: + return !!newValue; + case ENRICH_NEVER: + return false; + case ENRICH_WHEN_EMPTY: + return !!newValue && currentValue === undefined; + default: + return false; + } +} + function enrichBids(bids, targeting, contentId, contentData) { if (!bids) { return; diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index a4c2f3621e1..936cd1d10a2 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -78,6 +78,19 @@ realTimeData = { }; ``` +## Configuration syntax + +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Real time data module name | Always 'jwplayer' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.mediaIDs | Array of Strings | Media Ids for prefetching | Optional | +| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'always' | +| params.overrideContentUrl | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.url | Defaults to 'whenEmpty' | +| params.overrideContentTitle | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.title | Defaults to 'whenEmpty' | +| params.overrideContentDescription | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.ext.description | Defaults to 'whenEmpty' | + # Usage for Bid Adapters: Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. @@ -94,6 +107,8 @@ Example: site: { content: { id: 'jw_abc123', + title: 'media title', + url: 'https:www.cdn.com/media.mp4', data: [{ name: 'jwplayer.com', ext: { @@ -105,7 +120,10 @@ Example: }, { id: '456' }] - }] + }], + ext: { + description: 'media description' + } } } } @@ -116,7 +134,10 @@ where: - `ortb2` is an object containing first party data - `site` is an object containing page specific information - `content` is an object containing metadata for the media. It may contain the following information: - - `id` is a unique identifier for the specific media asset + - `id` is a unique identifier for the specific media asset, + - `title` is the title of the media content + - `url` is the url of the media asset + - `ext.description` is the description of the media content - `data` is an array containing segment taxonomy objects that have the following parameters: - `name` is the `jwplayer.com` string indicating the provider name - `ext.segtax` whose `502` value is the unique identifier for JW Player's proprietary taxonomy diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 12fe9a7e800..c57c8a685e7 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -11,6 +11,7 @@ import { getContentSegments, getVatFromCache, getVatFromPlayer, + setOverrides, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; @@ -47,7 +48,9 @@ describe('jwplayerRtdProvider', function() { playlist: [ { file: 'test.mp4', - jwpseg: validSegments + jwpseg: validSegments, + title: 'test', + description: 'this is a test' } ] }) @@ -57,7 +60,44 @@ describe('jwplayerRtdProvider', function() { const validTargeting = { segments: validSegments, - mediaID: testIdForSuccess + mediaID: testIdForSuccess, + mediaUrl: 'test.mp4', + title: 'test', + description: 'this is a test' + }; + + expect(targetingInfo).to.deep.equal(validTargeting); + }); + + it('should obtain file from sources', function () { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + sources: [{ + label: 'missing file', + }, { + file: 'source.mp4', + label: 'valid file' + }], + jwpseg: validSegments, + title: 'test', + description: 'this is a test' + } + ] + }) + ); + + const targetingInfo = getVatFromCache(testIdForSuccess); + + const validTargeting = { + segments: validSegments, + mediaID: testIdForSuccess, + mediaUrl: 'source.mp4', + title: 'test', + description: 'this is a test' }; expect(targetingInfo).to.deep.equal(validTargeting); @@ -82,16 +122,12 @@ describe('jwplayerRtdProvider', function() { expect(targetingInfo).to.be.null; }); - it('should not write to cache when segments are absent', function() { + it('should not write to cache when playlist is empty', function() { request.respond( 200, responseHeader, JSON.stringify({ - playlist: [ - { - file: 'test.mp4' - } - ] + playlist: [] }) ); const targetingInfo = getVatFromCache(testIdForFailure); @@ -150,26 +186,41 @@ describe('jwplayerRtdProvider', function() { describe('When jwplayer.js is on page', function () { const playlistItemWithSegmentMock = { mediaid: mediaIdWithSegment, + title: 'Media With Segment', + description: 'The media has segments', + file: 'mediaWithSegments.mp4', jwpseg: validSegments }; const targetingForMediaWithSegment = { segments: validSegments, - mediaID: mediaIdWithSegment + mediaID: mediaIdWithSegment, + title: 'Media With Segment', + description: 'The media has segments', + mediaUrl: 'mediaWithSegments.mp4', }; const playlistItemNoSegmentMock = { - mediaid: mediaIdNoSegment + mediaid: mediaIdNoSegment, + title: 'Media Without Segment', + description: 'The media has no segments', + file: 'mediaWithoutSegments.mp4', }; const currentItemSegments = ['test_seg_3', 'test_seg_4']; const currentPlaylistItemMock = { mediaid: mediaIdForCurrentItem, - jwpseg: currentItemSegments + jwpseg: currentItemSegments, + title: 'Current Item', + description: 'The current playlist item', + file: 'currentItem.mp4', }; const targetingForCurrentItem = { segments: currentItemSegments, - mediaID: mediaIdForCurrentItem + mediaID: mediaIdForCurrentItem, + title: 'Current Item', + description: 'The current playlist item', + mediaUrl: 'currentItem.mp4', }; const playerInstanceMock = { @@ -199,23 +250,23 @@ describe('jwplayerRtdProvider', function() { expect(targeting).to.be.null; }); - it('returns segments when media ID matches a playlist item with segments', function () { + it('returns targeting when media ID matches a playlist item', function () { const targeting = getVatFromPlayer(validPlayerID, mediaIdWithSegment); expect(targeting).to.deep.equal(targetingForMediaWithSegment); }); - it('caches segments when media ID matches a playist item with segments', function () { + it('caches item when media ID matches a valid playist item', function () { getVatFromPlayer(validPlayerID, mediaIdWithSegment); const vat = getVatFromCache(mediaIdWithSegment); - expect(vat.segments).to.deep.equal(validSegments); + expect(vat).to.deep.equal(targetingForMediaWithSegment); }); - it('returns segments of current item when media ID is missing', function () { + it('returns targeting of current item when media ID is missing', function () { const targeting = getVatFromPlayer(validPlayerID); expect(targeting).to.deep.equal(targetingForCurrentItem); }); - it('caches segments from the current item', function () { + it('caches metadata from the current item', function () { getVatFromPlayer(validPlayerID); window.jwplayer = null; @@ -225,10 +276,11 @@ describe('jwplayerRtdProvider', function() { it('returns undefined segments when segments are absent', function () { const targeting = getVatFromPlayer(validPlayerID, mediaIdNoSegment); - expect(targeting).to.deep.equal({ - mediaID: mediaIdNoSegment, - segments: undefined - }); + expect(targeting.segments).to.be.undefined; + expect(targeting.mediaID).to.equal(mediaIdNoSegment); + expect(targeting.title).to.equal('Media Without Segment'); + expect(targeting.description).to.equal('The media has no segments'); + expect(targeting.mediaUrl).to.equal('mediaWithoutSegments.mp4'); }); describe('Get Bid Request Data', function () { @@ -489,7 +541,9 @@ describe('jwplayerRtdProvider', function() { playlist: [ { file: 'test.mp4', - jwpseg: validSegments + jwpseg: validSegments, + title: 'test title', + description: 'test description', } ] }) @@ -498,6 +552,9 @@ describe('jwplayerRtdProvider', function() { expect(ortb2Fragments.global).to.have.property('site'); expect(ortb2Fragments.global.site).to.have.property('content'); expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('url', 'test.mp4'); + expect(ortb2Fragments.global.site.content).to.have.property('title', 'test title'); + expect(ortb2Fragments.global.site.content.ext).to.have.property('description', 'test description'); expect(ortb2Fragments.global.site.content).to.have.property('data'); const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(1); @@ -742,6 +799,15 @@ describe('jwplayerRtdProvider', function() { }); describe(' Add Ortb Site Content', function () { + beforeEach(() => { + setOverrides({ + overrideContentId: 'always', + overrideContentUrl: 'whenEmpty', + overrideContentTitle: 'whenEmpty', + overrideContentDescription: 'whenEmpty' + }); + }); + it('should maintain object structure when id and data params are empty', function () { const ortb2 = { site: { @@ -766,9 +832,15 @@ describe('jwplayerRtdProvider', function() { it('should create a structure compliant with the oRTB 2 spec', function() { const ortb2 = {} const expectedId = 'expectedId'; + const expectedUrl = 'expectedUrl'; + const expectedTitle = 'expectedTitle'; + const expectedDescription = 'expectedDescription'; const expectedData = { datum: 'datum' }; - addOrtbSiteContent(ortb2, expectedId, expectedData); + addOrtbSiteContent(ortb2, expectedId, expectedData, expectedTitle, expectedDescription, expectedUrl); expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); + expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); + expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); expect(ortb2).to.have.nested.property('site.content.data'); expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); }); @@ -777,11 +849,14 @@ describe('jwplayerRtdProvider', function() { const ortb2 = { site: { content: { - id: 'oldId' + id: 'oldId', + ext: { + random_field: 'randomField' + } }, random: { random_sub: 'randomSub' - } + }, }, app: { content: { @@ -791,23 +866,30 @@ describe('jwplayerRtdProvider', function() { }; const expectedId = 'expectedId'; + const expectedUrl = 'expectedUrl'; + const expectedTitle = 'expectedTitle'; + const expectedDescription = 'expectedDescription'; const expectedData = { datum: 'datum' }; - addOrtbSiteContent(ortb2, expectedId, expectedData); + addOrtbSiteContent(ortb2, expectedId, expectedData, expectedTitle, expectedDescription, expectedUrl); expect(ortb2).to.have.nested.property('site.random.random_sub', 'randomSub'); expect(ortb2).to.have.nested.property('app.content.id', 'appId'); + expect(ortb2).to.have.nested.property('site.content.ext.random_field', 'randomField'); expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); + expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); + expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); expect(ortb2).to.have.nested.property('site.content.data'); expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); }); - it('should set content id', function () { + it('should set content id by default when absent from ortb2', function () { const ortb2 = {}; const expectedId = 'expectedId'; addOrtbSiteContent(ortb2, expectedId); expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); - it('should override content id', function () { + it('should override content id by default', function () { const ortb2 = { site: { content: { @@ -821,7 +903,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); - it('should keep previous content id when not set', function () { + it('should keep previous content id when new value is not available', function () { const previousId = 'oldId'; const ortb2 = { site: { @@ -836,6 +918,122 @@ describe('jwplayerRtdProvider', function() { expect(ortb2).to.have.nested.property('site.content.id', previousId); }); + it('should override content id when override is always', function () { + setOverrides({ + overrideContentId: 'always', + }); + + const ortb2 = { + site: { + content: { + id: 'oldId' + } + } + }; + + const expectedId = 'expectedId'; + addOrtbSiteContent(ortb2, expectedId); + expect(ortb2).to.have.nested.property('site.content.id', expectedId); + }); + + it('should keep previous content id when override is always and new value is not available', function () { + setOverrides({ + overrideContentId: 'always', + }); + + const ortb2 = { + site: { + content: { + id: 'oldId' + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); + }); + + it('should populate content id when override is whenEmpty and value is empty', function () { + setOverrides({ + overrideContentId: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'newId'); + }); + + it('should keep previous content id when override is whenEmpty and value is already populated', function () { + setOverrides({ + overrideContentId: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + id: 'oldId' + } + } + }; + + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); + }); + + it('should keep previous content id when override is whenEmpty and new value is not available', function () { + setOverrides({ + overrideContentId: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2.site.content.id).to.be.undefined; + }); + + it('should keep previous content id when overrideContentId is set to never', function () { + setOverrides({ + overrideContentId: 'never', + }); + + const ortb2 = { + site: { + content: { + id: 'oldId' + } + } + }; + + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); + }); + + it('should not populate content id when override is set to never', function () { + setOverrides({ + overrideContentId: 'never', + }); + + const ortb2 = { + site: { + content: {} + } + }; + + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2.site.content.id).to.be.undefined; + }); + it('should set content data', function () { const ortb2 = {}; const expectedData = { datum: 'datum' }; @@ -878,6 +1076,451 @@ describe('jwplayerRtdProvider', function() { expect(ortb2.site.content.data[0]).to.be.deep.equal(expectedData); expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); + + it('should set content title by default when absent from ortb2', function () { + const ortb2 = {}; + const expectedTitle = 'expectedTitle'; + addOrtbSiteContent(ortb2, null, null, expectedTitle); + expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); + }); + + it('should keep previous content title by default when already defined', function () { + const ortb2 = { + site: { + content: { + title: 'oldTitle' + } + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2).to.have.nested.property('site.content.title', 'oldTitle'); + }); + + it('should keep previous content title by default when new value is not available', function () { + const ortb2 = { + site: { + content: { + title: 'oldTitle', + data: [{ datum: 'first_datum' }] + } + } + }; + + addOrtbSiteContent(ortb2, null, { datum: 'new_datum' }); + expect(ortb2).to.have.nested.property('site.content.title', 'oldTitle'); + }); + + it('should override content title when override is always', function () { + setOverrides({ + overrideContentTitle: 'always', + }); + + const ortb2 = { + site: { + content: { + title: 'oldTitle' + } + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2).to.have.nested.property('site.content.title', 'newTitle'); + }); + + it('should keep previous content title when override is always and new value is not available', function () { + setOverrides({ + overrideContentTitle: 'always', + }); + + const ortb2 = { + site: { + content: { + title: 'oldTitle' + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2).to.have.nested.property('site.content.title', 'oldTitle'); + }); + + it('should populate content title when override is whenEmpty and value is empty', function () { + setOverrides({ + overrideContentTitle: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2).to.have.nested.property('site.content.title', 'newTitle'); + }); + + it('should keep previous content title when override is whenEmpty and value is already populated', function () { + setOverrides({ + overrideContentTitle: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + title: 'oldTitle' + } + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2).to.have.nested.property('site.content.title', 'oldTitle'); + }); + + it('should keep previous content title when override is whenEmpty and new value is not available', function () { + setOverrides({ + overrideContentTitle: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2.site.content.title).to.be.undefined; + }); + + it('should keep previous content title when override is set to never', function () { + setOverrides({ + overrideContentTitle: 'never', + }); + + const ortb2 = { + site: { + content: { + title: 'oldTitle' + } + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2).to.have.nested.property('site.content.title', 'oldTitle'); + }); + + it('should not populate content title when override is set to never', function () { + setOverrides({ + overrideContentTitle: 'never', + }); + + const ortb2 = { + site: { + content: {} + } + }; + + addOrtbSiteContent(ortb2, null, null, 'newTitle'); + expect(ortb2.site.content.title).to.be.undefined; + }); + + it('should set content description by default when absent from ortb2', function () { + const ortb2 = {}; + const expectedDescription = 'expectedDescription'; + addOrtbSiteContent(ortb2, null, null, null, expectedDescription); + expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); + }); + + it('should keep previous content description by default when already defined', function () { + const ortb2 = { + site: { + content: { + ext: { + description: 'oldDescription' + } + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, 'newDescription'); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'oldDescription'); + }); + + it('should override content description when override is always', function () { + setOverrides({ + overrideContentDescription: 'always', + }); + + const ortb2 = { + site: { + content: { + ext: { + description: 'oldDescription' + } + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, 'newDescription'); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'newDescription'); + }); + + it('should keep previous content description when override is always and new value is not available', function () { + setOverrides({ + overrideContentDescription: 'always', + }); + + const ortb2 = { + site: { + content: { + ext: { + description: 'oldDescription' + } + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'oldDescription'); + }); + + it('should populate content description when override is whenEmpty and value is empty', function () { + setOverrides({ + overrideContentDescription: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, 'newDescription'); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'newDescription'); + }); + + it('should keep previous content description when override is whenEmpty and value is already populated', function () { + setOverrides({ + overrideContentDescription: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + ext: { + description: 'oldDescription' + } + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, 'newDescription'); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'oldDescription'); + }); + + it('should keep previous content description when override is whenEmpty and new value is not available', function () { + setOverrides({ + overrideContentDescription: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2.site.content.ext).to.be.undefined; + }); + + it('should keep previous content description when override is set to never', function () { + setOverrides({ + overrideContentDescription: 'never', + }); + + const ortb2 = { + site: { + content: { + ext: { + description: 'oldDescription' + } + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, 'newDescription'); + expect(ortb2).to.have.nested.property('site.content.ext.description', 'oldDescription'); + }); + + it('should not populate content description when override is set to never', function () { + setOverrides({ + overrideContentDescription: 'never', + }); + + const ortb2 = { + site: { + content: {} + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2.site.content.ext).to.be.undefined; + }); + + it('should set content url by default when absent from ortb2', function () { + const ortb2 = {}; + const expectedUrl = 'expectedUrl'; + addOrtbSiteContent(ortb2, null, null, null, null, expectedUrl); + expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); + }); + + it('should keep previous content url by default when new value is not available', function () { + const ortb2 = { + site: { + content: { + url: 'oldUrl', + data: [{ datum: 'first_datum' }] + } + } + }; + + addOrtbSiteContent(ortb2, null, { datum: 'new_datum' }); + expect(ortb2).to.have.nested.property('site.content.url', 'oldUrl'); + }); + + it('should keep previous content url by default when already defined', function () { + const ortb2 = { + site: { + content: { + url: 'oldUrl', + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, null, 'newUrl'); + expect(ortb2).to.have.nested.property('site.content.url', 'oldUrl'); + }); + + it('should override content url when override is always', function () { + setOverrides({ + overrideContentUrl: 'always', + }); + + const ortb2 = { + site: { + content: { + url: 'oldUrl', + } + } + }; + + const expectedUrl = 'expectedUrl'; + addOrtbSiteContent(ortb2, null, null, null, null, expectedUrl); + expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); + }); + + it('should keep previous content url when override is always and new value is not available', function () { + setOverrides({ + overrideContentUrl: 'always', + }); + + const ortb2 = { + site: { + content: { + url: 'oldUrl', + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2).to.have.nested.property('site.content.url', 'oldUrl'); + }); + + it('should populate content url when override is whenEmpty and value is empty', function () { + setOverrides({ + overrideContentUrl: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + const expectedUrl = 'expectedUrl'; + addOrtbSiteContent(ortb2, null, null, null, null, expectedUrl); + expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); + }); + + it('should keep previous content url when override is whenEmpty and value is already populated', function () { + setOverrides({ + overrideContentUrl: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + url: 'oldUrl', + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, null, 'newUrl'); + expect(ortb2).to.have.nested.property('site.content.url', 'oldUrl'); + }); + + it('should keep previous content url when override is whenEmpty and new value is not available', function () { + setOverrides({ + overrideContentUrl: 'whenEmpty', + }); + + const ortb2 = { + site: { + content: { + } + } + }; + + addOrtbSiteContent(ortb2); + expect(ortb2.site.content.url).to.be.undefined; + }); + + it('should keep previous content url when override is set to never', function () { + setOverrides({ + overrideContentUrl: 'never', + }); + + const ortb2 = { + site: { + content: { + url: 'oldUrl', + } + } + }; + + addOrtbSiteContent(ortb2, null, null, null, null, 'newUrl'); + expect(ortb2).to.have.nested.property('site.content.url', 'oldUrl'); + }); + + it('should not populate content url when override is set to never', function () { + setOverrides({ + overrideContentUrl: 'never', + }); + + const ortb2 = { + site: { + content: {} + } + }; + + addOrtbSiteContent(ortb2, null, null, null, null, 'newUrl'); + expect(ortb2.site.content.url).to.be.undefined; + }); }); describe('Add Targeting to Bid', function () { From 5e48d3b5df34db28862f3358e5253584ef7d75e3 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 11 Apr 2024 16:34:39 +0400 Subject: [PATCH 614/760] Limelight Digital Bid Adapter : add page field (#11312) * Limelight Digital Bid Adapter: Add page field * fix typo * add referrer field * remove referrer field --- modules/limelightDigitalBidAdapter.js | 3 ++- .../modules/limelightDigitalBidAdapter_spec.js | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index acc76014abe..5cccf5300b3 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -169,7 +169,8 @@ function buildPlacement(bidRequest) { custom2: bidRequest.params.custom2, custom3: bidRequest.params.custom3, custom4: bidRequest.params.custom4, - custom5: bidRequest.params.custom5 + custom5: bidRequest.params.custom5, + page: bidRequest.refererInfo.page } } } diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 0e6f4817e5e..6348d6a1ac6 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -17,6 +17,9 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, + refererInfo: { + page: 'https://publisher.com/page1' + }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', mediaTypes: { @@ -65,6 +68,9 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, + refererInfo: { + page: 'https://publisher.com/page2' + }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], @@ -115,6 +121,9 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, + refererInfo: { + page: 'https://publisher.com/page3' + }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], @@ -162,6 +171,9 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, + refererInfo: { + page: 'https://publisher.com/page4' + }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', video: { @@ -237,7 +249,8 @@ describe('limelightDigitalAdapter', function () { 'custom2', 'custom3', 'custom4', - 'custom5' + 'custom5', + 'page' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -251,6 +264,7 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom3).to.be.a('string'); expect(adUnit.custom4).to.be.a('string'); expect(adUnit.custom5).to.be.a('string'); + expect(adUnit.page).to.be.a('string'); }) }) }) @@ -685,4 +699,5 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.publisherId).to.equal(bid.params.publisherId); expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); expect(adUnit.supplyChain).to.deep.equal(bid.schain); + expect(adUnit.page).to.equal(bid.refererInfo.page); } From 3aed8aa09e829893241875bb113f306a88700737 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Thu, 11 Apr 2024 22:52:28 +1000 Subject: [PATCH 615/760] Adnuntius Bid Adapter: change ad request format (#11321) --- modules/adnuntiusBidAdapter.js | 2 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index eb5f3c19dea..97419fb94bd 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -182,7 +182,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const queryParamsAndValues = []; queryParamsAndValues.push('tzo=' + new Date().getTimezoneOffset()) - queryParamsAndValues.push('format=json') + queryParamsAndValues.push('format=prebid') const gdprApplies = deepAccess(bidderRequest, 'gdprConsent.gdprApplies'); const consentString = deepAccess(bidderRequest, 'gdprConsent.consentString'); if (gdprApplies !== undefined) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 71f0a6a3a6c..0e0206c2933 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -37,12 +37,12 @@ describe('adnuntiusBidAdapter', function() { }); const tzo = new Date().getTimezoneOffset(); - const ENDPOINT_URL_BASE = `${URL}${tzo}&format=json`; + const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; - const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=json&consentString=consentString&gdpr=1&userId=${usi}`; + const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`; const adapter = newBidder(spec); const bidderRequests = [ From 007a3b0e2e1e3d21965fb9355fbf7f1de77b0c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Leclerc?= Date: Thu, 11 Apr 2024 15:04:18 +0200 Subject: [PATCH 616/760] teads: Add more device information to payload (#11316) --- modules/teadsBidAdapter.js | 2 ++ test/spec/modules/teadsBidAdapter_spec.js | 35 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 1108c12c822..a3c8d3e24dc 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -62,6 +62,8 @@ export const spec = { timeToFirstByte: getTimeToFirstByte(window), data: bids, deviceWidth: screen.width, + deviceHeight: screen.height, + devicePixelRatio: topWindow.devicePixelRatio, screenOrientation: screen.orientation?.type, historyLength: topWindow.history?.length, viewportHeight: topWindow.visualViewport?.height, diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 1e044651315..81e09b09d08 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -255,6 +255,33 @@ describe('teadsBidAdapter', () => { expect(payload.pageReferrer).to.deep.equal(document.referrer); }); + it('should add width info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceWidth = screen.width + + expect(payload.deviceWidth).to.exist; + expect(payload.deviceWidth).to.deep.equal(deviceWidth); + }); + + it('should add height info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const deviceHeight = screen.height + + expect(payload.deviceHeight).to.exist; + expect(payload.deviceHeight).to.deep.equal(deviceHeight); + }); + + it('should add pixelRatio info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + const pixelRatio = window.top.devicePixelRatio + + expect(payload.devicePixelRatio).to.exist; + expect(payload.devicePixelRatio).to.deep.equal(pixelRatio); + }); + it('should add screenOrientation info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); @@ -290,6 +317,14 @@ describe('teadsBidAdapter', () => { expect(payload.viewportWidth).to.deep.equal(window.top.visualViewport.width); }); + it('should add viewportHeight info to payload', function () { + const request = spec.buildRequests(bidRequests, bidderRequestDefault); + const payload = JSON.parse(request.data); + + expect(payload.viewportHeight).to.exist; + expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); + }); + it('should add hardwareConcurrency info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); From e51046edd5daddb902a12d821bdc6aa0073d94bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Thu, 11 Apr 2024 15:20:26 +0200 Subject: [PATCH 617/760] Grid Bid Adapter : fix invalid DSA adrender field mapping (#11313) * Criteo Bid Adapter: Fix invalid DSA adrender field mapping * Grid Bid Adapter: Fix invalid DSA adrender field mapping --- modules/gridBidAdapter.js | 4 ++-- test/spec/modules/gridBidAdapter_spec.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index d56639ed714..f7db6d878f1 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -554,8 +554,8 @@ function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bid bidResponse.meta.demandSource = serverBid.ext.bidder.grid.demandSource; } - if (serverBid.ext && serverBid.ext.dsa && serverBid.ext.dsa.adrender) { - bidResponse.meta.adrender = serverBid.ext.dsa.adrender; + if (serverBid.ext && serverBid.ext.dsa) { + bidResponse.meta.dsa = serverBid.ext.dsa; } if (serverBid.content_type === 'video') { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index abaa4b37fcd..efd7b06685f 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1430,7 +1430,9 @@ describe('TheMediaGrid Adapter', function () { 'netRevenue': true, 'ttl': 360, 'meta': { - adrender: 1, + dsa: { + adrender: 1 + }, advertiserDomains: [] }, }, From 9aff4adeab156a5ecc5949fd24e4c4facd49fc33 Mon Sep 17 00:00:00 2001 From: teqblaze <162988436+teqblaze@users.noreply.github.com> Date: Thu, 11 Apr 2024 18:42:59 +0300 Subject: [PATCH 618/760] Loyal Bid Adapter: initial release (#11253) * init loyal adapter * fix linter * upd * fix linter --------- Co-authored-by: loyal --- modules/loyalBidAdapter.js | 190 +++++++++++ modules/loyalBidAdapter.md | 79 +++++ test/spec/modules/loyalBidAdapter_spec .js | 375 +++++++++++++++++++++ 3 files changed, 644 insertions(+) create mode 100644 modules/loyalBidAdapter.js create mode 100644 modules/loyalBidAdapter.md create mode 100644 test/spec/modules/loyalBidAdapter_spec .js diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js new file mode 100644 index 00000000000..30fdeb44233 --- /dev/null +++ b/modules/loyalBidAdapter.js @@ -0,0 +1,190 @@ +import { logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; + +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'loyal'; +const AD_URL = 'https://us-east-1.loyal.app/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor, + eids: [] + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + // TODO: does the fallback make sense here? + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + tmax: bidderRequest.timeout + }; + + if (bidderRequest.gdprConsent?.consentString) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + }, +}; + +registerBidder(spec); diff --git a/modules/loyalBidAdapter.md b/modules/loyalBidAdapter.md new file mode 100644 index 00000000000..db77c04c34f --- /dev/null +++ b/modules/loyalBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Loyal Bidder Adapter +Module Type: Loyal Bidder Adapter +Maintainer: hello@loyal.app +``` + +# Description + +Connects to Loyal exchange for bids. +Loyal bid adapter supports Banner, Video (instream and outstream) and Native. + +# Test Parameters +``` + var adUnits = [ + // Will return static test banner + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'loyal', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'loyal', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'loyal', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/test/spec/modules/loyalBidAdapter_spec .js b/test/spec/modules/loyalBidAdapter_spec .js new file mode 100644 index 00000000000..28e87fc7047 --- /dev/null +++ b/test/spec/modules/loyalBidAdapter_spec .js @@ -0,0 +1,375 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/loyalBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'loyal' + +describe('LoyalBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative', + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + }, + refererInfo: { + referer: 'https://test.com' + }, + timeout: 500 + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://us-east-1.loyal.app/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.an('array'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + + it('Returns empty data if no valid requests are passed', function () { + serverRequest = spec.buildRequests([], bidderRequest); + let data = serverRequest.data; + expect(data.placements).to.be.an('array').that.is.empty; + }); + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 19fce05a2ee0c6fc4f7d6c2217ba19feb5dc220e Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Fri, 12 Apr 2024 21:17:36 +0800 Subject: [PATCH 619/760] Discovery Bid Adapter : build UTM tag data (#11317) * feat(isBidRequestValid): just filter token once. not filter publisher and tagid * feat(isBidRequestValid): add unit test * feat(spec): fix eslint * feat(spec): fix unit test * feat(spec): fix unit test * feat(ext): change default value * feat(utm): build UTMTag data * feat(spec): fix utm test --------- Co-authored-by: yubei01 --- modules/discoveryBidAdapter.js | 21 +++++++++++ test/spec/modules/discoveryBidAdapter_spec.js | 36 ++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index de2fd3c3a94..7493dcb9af4 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -25,6 +25,9 @@ const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; export const THIRD_PARTY_COOKIE_ORIGIN = 'https://asset.popin.cc'; +const UTM_KEY = '_ss_pp_utm'; +let UTMValue = {}; + const NATIVERET = { id: 'id', bidfloor: 0, @@ -409,6 +412,20 @@ function getItems(validBidRequests, bidderRequest) { return items; } +export const buildUTMTagData = (url) => { + if (!storage.cookiesAreEnabled()) return; + const urlParams = utils.parseUrl(url).search; + const UTMParams = {}; + Object.keys(urlParams).forEach(key => { + if (/^utm_/.test(key)) { + UTMParams[key] = urlParams[key]; + } + }); + UTMValue = JSON.parse(storage.getCookie(UTM_KEY) || '{}'); + Object.assign(UTMValue, UTMParams); + storage.setCookie(UTM_KEY, JSON.stringify(UTMValue), getCurrentTimeToUTCString()); +} + /** * get rtb qequest params * @@ -443,6 +460,10 @@ function getParam(validBidRequests, bidderRequest) { const desc = getPageDescription(); const keywords = getPageKeywords(); + try { + buildUTMTagData(page); + } catch (error) { } + if (items && items.length) { let c = { // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index f1475ec3739..d148d5062a4 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -9,7 +9,8 @@ import { getConnectionDownLink, THIRD_PARTY_COOKIE_ORIGIN, COOKIE_KEY_MGUID, - getCurrentTimeToUTCString + getCurrentTimeToUTCString, + buildUTMTagData } from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; @@ -252,6 +253,39 @@ describe('discovery:BidAdapterTests', function () { expect(storage.setCookie.calledOnce).to.be.false; }); }) + describe('buildUTMTagData function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(utils, 'parseUrl').returns({ + search: { + utm_source: 'example.com' + } + }); + sandbox.stub(storage, 'cookiesAreEnabled'); + }) + + afterEach(() => { + sandbox.restore(); + }); + + it('should set UTM cookie', () => { + storage.cookiesAreEnabled.callsFake(() => true); + storage.getCookie.callsFake(() => null); + buildUTMTagData(); + expect(storage.setCookie.calledOnce).to.be.true; + }); + + it('should not set UTM when cookies are not enabled', () => { + storage.cookiesAreEnabled.callsFake(() => false); + storage.getCookie.callsFake(() => null); + buildUTMTagData(); + expect(storage.setCookie.calledOnce).to.be.false; + }); + }) }); it('discovery:validate_response_params', function () { From bee8ce7d041c2f90b1fec0a48b08070eb8a980c3 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 12 Apr 2024 17:15:41 +0000 Subject: [PATCH 620/760] Prebid 8.44.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index b65a43a197e..a641f81805c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.44.0-pre", + "version": "8.44.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b7a1e1bafab..463c0aed8ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.44.0-pre", + "version": "8.44.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From eb9b407b3056e333111c48c9477bf619f66262cb Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 12 Apr 2024 17:15:41 +0000 Subject: [PATCH 621/760] Increment version to 8.45.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index a641f81805c..6fb87a7dcdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.44.0", + "version": "8.45.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 463c0aed8ec..88a72f3c258 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.44.0", + "version": "8.45.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 32eebd03e987483933475e46edb11fe7c4e1c75e Mon Sep 17 00:00:00 2001 From: kapil-tuptewar <91458408+kapil-tuptewar@users.noreply.github.com> Date: Mon, 15 Apr 2024 16:41:59 +0530 Subject: [PATCH 622/760] removed transformbidparams from pubmaticbidadapter (#11328) --- modules/pubmaticBidAdapter.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index f28feaa534d..846c59c1ae5 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -5,7 +5,6 @@ import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; import CONSTANTS from '../src/constants.json'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -1448,20 +1447,6 @@ export const spec = { url: USER_SYNC_URL_IMAGE + syncurl }]; } - }, - - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - - transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { - return convertTypes({ - 'publisherId': 'string', - 'adSlot': 'string' - }, params); } }; From 7cbc29b74f60231cd3af29b0392dcd047d918912 Mon Sep 17 00:00:00 2001 From: Tachfine Date: Mon, 15 Apr 2024 13:23:22 +0200 Subject: [PATCH 623/760] Criteo Bid Adapter : add support for grid bid parameters (#11315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Support optional pubid bid parameter * 🩹 Fix publisher id in test * ✨ Support optional uid bid parameter * 🩹 Bidder param uid is an integer --- modules/criteoBidAdapter.js | 9 +++++ test/spec/modules/criteoBidAdapter_spec.js | 47 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 2c0cacb7909..968a847708e 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -473,6 +473,7 @@ function checkNativeSendId(bidRequest) { */ function buildCdbRequest(context, bidRequests, bidderRequest) { let networkId; + let pubid; let schain; let userIdAsEids; let regs = Object.assign({}, { @@ -490,6 +491,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { userIdAsEids = bidRequest.userIdAsEids; } networkId = bidRequest.params.networkId || networkId; + pubid = bidRequest.params.pubid || pubid; schain = bidRequest.schain || schain; const slot = { slotid: bidRequest.bidId, @@ -513,6 +515,10 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidRequest.nativeOrtbRequest?.assets) { slot.ext = Object.assign({}, slot.ext, { assets: bidRequest.nativeOrtbRequest.assets }); } + if (bidRequest.params.uid) { + slot.ext = Object.assign({}, slot.ext, { bidder: { uid: bidRequest.params.uid } }); + } + if (bidRequest.params.publisherSubId) { slot.publishersubid = bidRequest.params.publisherSubId; } @@ -581,6 +587,9 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } + if (pubid) { + request.publisher.id = pubid; + } request.source = { tid: bidderRequest.ortb2?.source?.tid diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 2cdb09f2098..083ae368afb 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1949,6 +1949,53 @@ describe('The Criteo bidding adapter', function () { expect(request.data.slots[0].ext).to.not.have.property('ae'); }); + it('should properly transmit the pubid and slot uid if available', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + ortb2Imp: { + ext: { + tid: 'transaction-123', + }, + }, + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + { + bidder: 'criteo', + adUnitCode: 'bid-234', + ortb2Imp: { + ext: { + tid: 'transaction-234', + }, + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + params: { + networkId: 456, + pubid: 'pub-888', + uid: 888 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.publisher.id).to.equal('pub-888'); + expect(request.data.slots[0].ext.bidder).to.be.undefined; + expect(request.data.slots[1].ext.bidder.uid).to.equal(888); + }); + it('should properly transmit device.ext.cdep if available', function () { const bidderRequest = { ortb2: { From 216c690cc54fa4163c13e05b68514c9b19bd97ce Mon Sep 17 00:00:00 2001 From: decemberWP <155962474+decemberWP@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:41:03 +0200 Subject: [PATCH 624/760] Piridsystem: New ID System for WPM (#11267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * add pirIdSystem * pirIdSystem * piridSystem - preCR * fix after CR --------- Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały --- modules/pirIdSystem.js | 62 +++++++++++++++++++++ modules/pirIdSystem.md | 27 ++++++++++ test/spec/modules/pirIdSystem_spec.js | 77 +++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 modules/pirIdSystem.js create mode 100644 modules/pirIdSystem.md create mode 100644 test/spec/modules/pirIdSystem_spec.js diff --git a/modules/pirIdSystem.js b/modules/pirIdSystem.js new file mode 100644 index 00000000000..b891e1d362a --- /dev/null +++ b/modules/pirIdSystem.js @@ -0,0 +1,62 @@ +/** + * This module adds pirId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/pirId + * @requires module:modules/userId + */ + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { submodule } from '../src/hook.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'pirId'; +const ID_TOKEN = 'WPxid'; +export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); + +/** + * Reads the ID token from local storage or cookies. + * @returns {string|undefined} The ID token, or undefined if not found. + */ +export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); + +/** @type {Submodule} */ +export const pirIdSubmodule = { + name: MODULE_NAME, + gvlid: 676, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {string} value + * @returns {(Object|undefined)} + */ + decode(value) { + return typeof value === 'string' ? { 'pirId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value + * @function + * @returns {(IdResponse|undefined)} + */ + getId() { + const pirIdToken = readId(); + + return pirIdToken ? { id: pirIdToken } : undefined; + }, + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), + eids: { + 'pirId': { + source: 'pir.wp.pl', + atype: 1 + }, + }, +}; + +submodule('userId', pirIdSubmodule); diff --git a/modules/pirIdSystem.md b/modules/pirIdSystem.md new file mode 100644 index 00000000000..913804f85c4 --- /dev/null +++ b/modules/pirIdSystem.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: pirIDSystem +Module Type: UserID Module +Maintainer: pawel.grudzien@grupawp.pl + +# Description + +User identification system for WPM + +### Prebid Params example + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'pirID', + storage: { + type: 'cookie', + name: 'pirIdToken', + expires: 7, + refreshInSeconds: 360 + }, + }] + } +}); +``` diff --git a/test/spec/modules/pirIdSystem_spec.js b/test/spec/modules/pirIdSystem_spec.js new file mode 100644 index 00000000000..5acc5a5eb9c --- /dev/null +++ b/test/spec/modules/pirIdSystem_spec.js @@ -0,0 +1,77 @@ +import { pirIdSubmodule, storage, readId } from 'modules/pirIdSystem.js'; +import sinon from 'sinon'; + +describe('pirIdSystem', () => { + let sandbox; + let getCookieStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId', () => { + it('should return an object with id when pirIdToken is found', () => { + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = pirIdSubmodule.getId(); + + expect(result).to.deep.equal({ id: 'testToken' }); + }); + + it('should return undefined when pirIdToken is not found', () => { + const result = pirIdSubmodule.getId(); + + expect(result).to.be.undefined; + }); + }); + + describe('decode', () => { + it('should return an object with pirId when value is a string', () => { + const result = pirIdSubmodule.decode('testId'); + + expect(result).to.deep.equal({ pirId: 'testId' }); + }); + + it('should return undefined when value is not a string', () => { + const result = pirIdSubmodule.decode({}); + + expect(result).to.be.undefined; + }); + }); + + describe('readId', () => { + it('should return data from local storage when it exists', () => { + getDataFromLocalStorageStub.returns('local_storage_data'); + + const result = readId(); + + expect(result).to.equal('local_storage_data'); + }); + + it('should return data from cookie when local storage data does not exist', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns('cookie_data'); + + const result = readId(); + + expect(result).to.equal('cookie_data'); + }); + + it('should return null when neither local storage data nor cookie data exists', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns(null); + + const result = readId(); + + expect(result).to.be.null; + }); + }); +}); From cf8cf49a7a3349f5aa984031d342f0a18a6da789 Mon Sep 17 00:00:00 2001 From: dalmenarDevST <116064809+dalmenarDevST@users.noreply.github.com> Date: Mon, 15 Apr 2024 15:46:15 +0200 Subject: [PATCH 625/760] add topics and eids to payload (#11338) --- modules/seedtagBidAdapter.js | 15 ++++-- test/spec/modules/seedtagBidAdapter_spec.js | 59 +++++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 6f36c8a191e..e17ff1301a3 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -1,7 +1,7 @@ -import { isArray, _map, triggerPixel } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { _map, isArray, triggerPixel } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -125,7 +125,7 @@ function buildBidRequest(validBidRequest) { adUnitCode: validBidRequest.adUnitCode, geom: geom(validBidRequest.adUnitCode), placement: params.placement, - requestCount: validBidRequest.bidderRequestsCount || 1, // FIXME : in unit test the parameter bidderRequestsCount is undefined + requestCount: validBidRequest.bidderRequestsCount || 1, // FIXME : in unit test the parameter bidderRequestsCount is undefinedt }; if (hasVideoMediaType(validBidRequest)) { @@ -284,6 +284,7 @@ export const spec = { auctionStart: bidderRequest.auctionStart || Date.now(), ttfb: ttfb(), bidRequests: _map(validBidRequests, buildBidRequest), + user: { topics: [], eids: [] } }; if (payload.cmp) { @@ -316,7 +317,15 @@ export const spec = { } } + if (bidderRequest.ortb2?.user?.data) { + payload.user.topics = bidderRequest.ortb2.user.data + } + if (validBidRequests[0] && validBidRequests[0].userIdAsEids) { + payload.user.eids = validBidRequests[0].userIdAsEids + } + const payloadString = JSON.stringify(payload); + return { method: 'POST', url: SEEDTAG_SSP_ENDPOINT, diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 516c5ec933a..2012c78d239 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -1,8 +1,8 @@ import { expect } from 'chai'; -import { spec, getTimeoutUrl } from 'modules/seedtagBidAdapter.js'; +import { getTimeoutUrl, spec } from 'modules/seedtagBidAdapter.js'; import * as utils from 'src/utils.js'; -import { config } from '../../../src/config.js'; import * as mockGpt from 'test/spec/integration/faker/googletag.js'; +import { config } from '../../../src/config.js'; const PUBLISHER_ID = '0000-0000-01'; const ADUNIT_ID = '000000'; @@ -536,8 +536,61 @@ describe('Seedtag Adapter', function () { expect(data.gppConsent).to.be.undefined; }); }); - }); + describe('User param', function () { + it('should be added to payload user data param when bidderRequest has ortb2 user info', function () { + var ortb2 = { + + user: { + + data: [ + { + ext: { + segtax: 601, + segclass: '4' + }, + segment: [ + { + id: '149' + } + ], + name: 'randomname' + } + + ] + } + } + bidderRequest['ortb2'] = ortb2 + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.user).to.exist; + expect(data.user.topics).to.exist; + expect(data.user.topics).to.be.an('array').that.is.not.empty; + expect(data.user.topics[0].ext).to.eql(ortb2.user.data[0].ext); + expect(data.user.topics[0].segment).to.eql(ortb2.user.data[0].segment); + expect(data.user.topics[0].name).to.eql(ortb2.user.data[0].name); + }) + + it('should be added to payload user eids param when validRequest has userId info', function () { + var userIdAsEids = [{ + source: 'sourceid', + uids: [{ + atype: 1, + id: 'randomId' + }] + }] + validBidRequests[0]['userIdAsEids'] = userIdAsEids + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + + expect(data.user).to.exist; + expect(data.user.eids).to.exist; + expect(data.user.eids).to.be.an('array').that.is.not.empty; + expect(data.user.eids).to.deep.equal(userIdAsEids); + }) + }); + }) describe('interpret response method', function () { it('should return a void array, when the server response are not correct.', function () { const request = { data: JSON.stringify({}) }; From 5ad4877e68a4be91dd4630d4687fa60c0d1d26fc Mon Sep 17 00:00:00 2001 From: g-kitiashvili <113773663+g-kitiashvili@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:07:34 +0400 Subject: [PATCH 626/760] beacon calls - we need to switch this domain to ce.lijit.com instead to better isolate raptor calls. (#11332) --- modules/sovrnBidAdapter.js | 2 +- test/spec/modules/sovrnBidAdapter_spec.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 64604618680..6dc5a3dc2ef 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -271,7 +271,7 @@ export const spec = { params.push(['informer', iidArr[0]]); tracks.push({ type: 'iframe', - url: 'https://ap.lijit.com/beacon?' + params.map(p => p.join('=')).join('&') + url: 'https://ce.lijit.com/beacon?' + params.map(p => p.join('=')).join('&') }); } } diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 274192d14a7..ad195b6924a 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -931,7 +931,7 @@ describe('sovrnBidAdapter', function() { it('should return if iid present on server response & iframe syncs enabled', function() { const expectedReturnStatement = { type: 'iframe', - url: 'https://ap.lijit.com/beacon?informer=13487408', + url: 'https://ce.lijit.com/beacon?informer=13487408', } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse) @@ -945,7 +945,7 @@ describe('sovrnBidAdapter', function() { } const expectedReturnStatement = { type: 'iframe', - url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, + url: `https://ce.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, '', null) @@ -957,7 +957,7 @@ describe('sovrnBidAdapter', function() { const uspString = '1NYN' const expectedReturnStatement = { type: 'iframe', - url: `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, + url: `https://ce.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString, null) @@ -972,7 +972,7 @@ describe('sovrnBidAdapter', function() { } const expectedReturnStatement = { type: 'iframe', - url: `https://ap.lijit.com/beacon?gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, + url: `https://ce.lijit.com/beacon?gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, '', gppConsent) @@ -993,7 +993,7 @@ describe('sovrnBidAdapter', function() { const expectedReturnStatement = { type: 'iframe', - url: `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, + url: `https://ce.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&gpp=${gppConsent.gppString}&gpp_sid=${gppConsent.applicableSections}&informer=13487408`, } const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString, gppConsent) From 2fd1b7412c2d7bafd2da8d7e1bc3888c19a1ef7e Mon Sep 17 00:00:00 2001 From: Shubham <127132399+shubhamc-ins@users.noreply.github.com> Date: Mon, 15 Apr 2024 19:50:55 +0530 Subject: [PATCH 627/760] Insticator Bid Adaptor: add support for different privacy platforms, ortb site first party data, fix video validation bug (#11279) * add support for: - dsa - us_privacy/ccpa - gpp - gppSid * video validation fix * support first part site details * support user data from bidder, and update server endpoint for alternative bidder configs * - enhance user object with ext and consent - remove video param validation * add test case for more than one privacy platforms * update user.ext.consent * update coppa and support video context --- modules/insticatorBidAdapter.js | 164 ++++++++-- .../spec/modules/insticatorBidAdapter_spec.js | 297 ++++++++++++++++-- 2 files changed, 412 insertions(+), 49 deletions(-) diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 4d9b95e5948..617ce49f171 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -12,35 +12,37 @@ const USER_ID_COOKIE_EXP = 2592000000; // 30 days const BID_TTL = 300; // 5 minutes const GVLID = 910; -const isSubarray = (arr, target) => { - if (!isArrayOfNums(arr) || arr.length === 0) { - return false; - } - const targetSet = new Set(target); - return arr.every(el => targetSet.has(el)); -}; - export const OPTIONAL_VIDEO_PARAMS = { 'minduration': (value) => isInteger(value), 'maxduration': (value) => isInteger(value), - 'protocols': (value) => isSubarray(value, [2, 3, 5, 6, 7, 8]), // protocols values supported by Inticator, according to the OpenRTB spec + 'protocols': (value) => isArrayOfNums(value), // protocols values supported by Inticator, according to the OpenRTB spec 'startdelay': (value) => isInteger(value), 'linearity': (value) => isInteger(value) && [1].includes(value), 'skip': (value) => isInteger(value) && [1, 0].includes(value), 'skipmin': (value) => isInteger(value), 'skipafter': (value) => isInteger(value), 'sequence': (value) => isInteger(value), - 'battr': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]), + 'battr': (value) => isArrayOfNums(value), 'maxextended': (value) => isInteger(value), 'minbitrate': (value) => isInteger(value), 'maxbitrate': (value) => isInteger(value), - 'playbackmethod': (value) => isSubarray(value, [1, 2, 3, 4]), + 'playbackmethod': (value) => isArrayOfNums(value), 'playbackend': (value) => isInteger(value) && [1, 2, 3].includes(value), - 'delivery': (value) => isSubarray(value, [1, 2, 3]), + 'delivery': (value) => isArrayOfNums(value), 'pos': (value) => isInteger(value) && [0, 1, 2, 3, 4, 5, 6, 7].includes(value), - 'api': (value) => isSubarray(value, [1, 2, 3, 4, 5, 6, 7]), + 'api': (value) => isArrayOfNums(value), }; +const ORTB_SITE_FIRST_PARTY_DATA = { + 'cat': v => Array.isArray(v) && v.every(c => typeof c === 'string'), + 'sectioncat': v => Array.isArray(v) && v.every(c => typeof c === 'string'), + 'pagecat': v => Array.isArray(v) && v.every(c => typeof c === 'string'), + 'search': v => typeof v === 'string', + 'mobile': v => isInteger(), + 'content': v => typeof v === 'object', + 'keywords': v => typeof v === 'string', +} + export const storage = getStorageManager({bidderCode: BIDDER_CODE}); config.setDefaults({ @@ -103,6 +105,7 @@ function buildVideo(bidRequest) { const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3; const plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt') || undefined; const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); if (!w && playerSize) { if (Array.isArray(playerSize[0])) { @@ -121,17 +124,26 @@ function buildVideo(bidRequest) { const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video'); const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + let optionalParams = {}; for (const param in OPTIONAL_VIDEO_PARAMS) { - if (bidRequestVideo[param]) { + if (bidRequestVideo[param] && OPTIONAL_VIDEO_PARAMS[param](bidRequestVideo[param])) { optionalParams[param] = bidRequestVideo[param]; } + // remove invalid optional params from bidder specific overrides + if (videoBidderParams[param] && !OPTIONAL_VIDEO_PARAMS[param](videoBidderParams[param])) { + delete videoBidderParams[param]; + } } if (plcmt) { optionalParams['plcmt'] = plcmt; } + if (context !== undefined) { + optionalParams['context'] = context; + } + let videoObj = { placement, mimes, @@ -190,31 +202,102 @@ function buildDevice(bidRequest) { return device; } +function _getCoppa(bidderRequest) { + const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa'); + + // If coppa is defined in the request, use it + if (coppa !== undefined) { + return coppa; + } + return config.getConfig('coppa') === true ? 1 : 0; +} + +function _getGppConsent(bidderRequest) { + let gpp = deepAccess(bidderRequest, 'gppConsent.gppString') + let gppSid = deepAccess(bidderRequest, 'gppConsent.applicableSections') + + if (!gpp || !gppSid) { + gpp = deepAccess(bidderRequest, 'ortb2.regs.gpp', '') + gppSid = deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []) + } + return { gpp, gppSid } +} + +function _getUspConsent(bidderRequest) { + return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; +} + function buildRegs(bidderRequest) { + let regs = { + ext: {}, + }; if (bidderRequest.gdprConsent) { - return { - ext: { - gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0, - gdprConsentString: bidderRequest.gdprConsent.consentString, - }, - }; + regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + regs.ext.gdprConsentString = bidderRequest.gdprConsent.consentString; + } + + regs.coppa = _getCoppa(bidderRequest); + + const { gpp, gppSid } = _getGppConsent(bidderRequest); + + if (gpp) { + regs.ext.gpp = gpp; + } + + if (gppSid) { + regs.ext.gppSid = gppSid; + } + + const usp = _getUspConsent(bidderRequest); + + if (usp) { + regs.ext.us_privacy = usp.uspConsent; + regs.ext.ccpa = usp.uspConsent + } + + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + regs.ext.dsa = dsa; } - return {}; + return regs; } function buildUser(bid) { const userId = getUserId() || generateUUID(); const yob = deepAccess(bid, 'params.user.yob') const gender = deepAccess(bid, 'params.user.gender') + const keywords = deepAccess(bid, 'params.user.keywords') + const data = deepAccess(bid, 'params.user.data') + const ext = deepAccess(bid, 'params.user.ext') setUserId(userId); - return { + const userData = { id: userId, - yob, - gender, - }; + } + + if (yob) { + userData.yob = yob; + } + + if (gender) { + userData.gender = gender; + } + + if (keywords) { + userData.keywords = keywords; + } + + if (data) { + userData.data = data; + } + + if (ext) { + userData.ext = ext; + } + + return userData } function extractSchain(bids, requestId) { @@ -283,6 +366,20 @@ function buildRequest(validBidRequests, bidderRequest) { req.user.ext = { eids }; } + const ortb2SiteData = deepAccess(bidderRequest, 'ortb2.site'); + if (ortb2SiteData) { + for (const key in ORTB_SITE_FIRST_PARTY_DATA) { + const value = ortb2SiteData[key]; + if (value && ORTB_SITE_FIRST_PARTY_DATA[key](value)) { + req.site[key] = value; + } + } + } + + if (bidderRequest.gdprConsent) { + deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + } + return req; } @@ -326,6 +423,13 @@ function buildBid(bid, bidderRequest) { bidResponse.vastUrl = 'data:text/xml;charset=utf-8;base64,' + window.btoa(bidResponse.vastXml.replace(/\\"/g, '"')); } + if (bid.ext && bid.ext.dsa) { + bidResponse.ext = { + ...bidResponse.ext, + dsa: bid.ext.dsa, + } + } + return bidResponse; } @@ -453,7 +557,6 @@ function validateVideo(bid) { if (video[param]) { if (!OPTIONAL_VIDEO_PARAMS[param](video[param])) { logError(`insticator: video ${param} is invalid or not supported by insticator`); - return false } } } @@ -485,6 +588,13 @@ export const spec = { let endpointUrl = config.getConfig('insticator.endpointUrl') || ENDPOINT; endpointUrl = endpointUrl.replace(/^http:/, 'https:'); + // Use the first bid request's bid_request_url if it exists ( for updating server url) + if (validBidRequests.length > 0) { + if (deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url')) { + endpointUrl = deepAccess(validBidRequests[0], 'params.bid_endpoint_request_url').replace(/^http:/, 'https:'); + } + } + if (validBidRequests.length > 0) { requests.push({ method: 'POST', diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 86f96834547..5e41cd6d7aa 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -75,9 +75,10 @@ describe('InsticatorBidAdapter', function () { ortb2: { source: { tid: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - } + }, }, timeout: 300, + gdprApplies: 1, gdprConsent: { consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', vendorData: {}, @@ -261,25 +262,6 @@ describe('InsticatorBidAdapter', function () { })).to.be.true; }); - it('should return false if optional video fields are not valid', () => { - expect(spec.isBidRequestValid({ - ...bidRequest, - ...{ - mediaTypes: { - video: { - mimes: [ - 'video/mp4', - 'video/mpeg', - ], - playerSize: [250, 300], - placement: 1, - startdelay: 'NaN', - }, - } - } - })).to.be.false; - }); - it('should return false if video min duration > max duration', () => { expect(spec.isBidRequestValid({ ...bidRequest, @@ -497,11 +479,58 @@ describe('InsticatorBidAdapter', function () { expect(data.user.id).to.equal(USER_ID_STUBBED); }); - it('should return empty regs object if no gdprConsent is passed', function () { + + it('should return with coppa regs object if no gdprConsent is passed', function () { const requests = spec.buildRequests([bidRequest], { ...bidderRequest, ...{ gdprConsent: false } }); const data = JSON.parse(requests[0].data); - expect(data.regs).to.be.an('object').that.is.empty; + expect(data.regs).to.be.an('object'); + expect(data.regs.coppa).to.be.oneOf([0, 1]); }); + + it('should return with us_privacy string if uspConsent is passed', function () { + const requests = spec.buildRequests([bidRequest], { ...bidderRequest, ...{ uspConsent: '1YNN' } }); + const data = JSON.parse(requests[0].data); + expect(data.regs).to.be.an('object'); + expect(data.regs.ext).to.be.an('object'); + expect(data.regs.ext.us_privacy).to.equal('1YNN'); + expect(data.regs.ext.ccpa).to.equal('1YNN'); + }); + + it('should return with gpp if gppConsent is passed', function () { + const requests = spec.buildRequests([bidRequest], { ...bidderRequest, ...{ gppConsent: { gppString: '1YNN', applicableSections: ['1', '2'] } } }); + const data = JSON.parse(requests[0].data); + expect(data.regs).to.be.an('object'); + expect(data.regs.ext).to.be.an('object'); + expect(data.regs.ext.gppSid).to.deep.equal(['1', '2']); + }); + + it('should create the request with dsa data and return with dsa object', function() { + const dsa = { + dsarequired: 2, + pubrender: 1, + datatopub: 2, + transparency: [{ + domain: 'google.com', + dsaparams: [1, 2] + }] + } + const bidRequestWithDsa = { + ...bidderRequest, + ortb2: { + regs: { + ext: { + dsa: dsa + } + } + } + } + const requests = spec.buildRequests([bidRequest], {...bidRequestWithDsa}); + const data = JSON.parse(requests[0].data); + expect(data.regs).to.be.an('object'); + expect(data.regs.ext).to.be.an('object'); + expect(data.regs.ext.dsa).to.deep.equal(dsa); + }); + it('should return empty array if no valid requests are passed', function () { expect(spec.buildRequests([], bidderRequest)).to.be.an('array').that.have.lengthOf(0); }); @@ -539,6 +568,129 @@ describe('InsticatorBidAdapter', function () { expect(data.imp[0].video.w).to.equal(640); expect(data.imp[0].video.h).to.equal(480); }); + + it('should have sites first party data if present in bidderRequest ortb2', function () { + bidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + site: { + keywords: 'keyword1,keyword2', + search: 'search', + content: { + title: 'title', + keywords: 'keyword3,keyword4', + genre: 'rock' + }, + cat: ['IAB1', 'IAB2'] + } + } + } + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data).to.have.property('site'); + expect(data.site).to.have.property('keywords'); + expect(data.site.keywords).to.equal('keyword1,keyword2'); + expect(data.site).to.have.property('search'); + expect(data.site.search).to.equal('search'); + expect(data.site).to.have.property('content'); + expect(data.site.content).to.have.property('title'); + expect(data.site.content.title).to.equal('title'); + expect(data.site.content).to.have.property('keywords'); + expect(data.site.content.keywords).to.equal('keyword3,keyword4'); + expect(data.site.content).to.have.property('genre'); + expect(data.site.content.genre).to.equal('rock'); + expect(data.site).to.have.property('cat'); + expect(data.site.cat).to.deep.equal(['IAB1', 'IAB2']); + }); + + it('should have device.sua if present in bidderRequest ortb2', function () { + bidderRequest = { + ...bidderRequest, + ortb2: { + ...bidderRequest.ortb2, + device: { + ...bidderRequest.ortb2.device, + sua: {} + } + } + } + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data).to.have.property('device'); + expect(data.device).to.have.property('sua'); + }) + + it('should use param bid_endpoint_request_url for request endpoint if present', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + bid_endpoint_request_url: 'https://example.com' + } + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + expect(requests[0].url).to.equal('https://example.com'); + }); + + it('should have user keywords if present in bidrequest', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + user: { + keywords: 'keyword1,keyword2' + } + } + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.user).to.have.property('keywords'); + expect(data.user.keywords).to.equal('keyword1,keyword2'); + }); + + it('should remove video params if they are invalid', function () { + const tempBiddRequest = { + ...bidRequest, + mediaTypes: { + ...bidRequest.mediaTypes, + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + 'video/x-flv', + 'video/webm', + 'video/ogg', + ], + protocols: 'NaN', + w: '300', + h: '250', + } + } + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].video).to.not.have.property('plcmt'); + }); + + it('should have user consent and gdpr string if gdprConsent is passed', function () { + const requests = spec.buildRequests([bidRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.regs).to.be.an('object'); + expect(data.regs.ext).to.be.an('object'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.regs.ext.gdprConsentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.user.ext).to.have.property('consent'); + expect(data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); + }); + + it('should have one or more privacy policies if present in bidrequest, like gpp, gdpr and us_privacy', function () { + const requests = spec.buildRequests([bidRequest], { ...bidderRequest, ...{ uspConsent: '1YNN' } }); + const data = JSON.parse(requests[0].data); + expect(data.regs.ext).to.have.property('gdpr'); + expect(data.regs.ext).to.have.property('us_privacy'); + expect(data.regs.ext).to.have.property('gppSid'); + }); }); describe('interpretResponse', function () { @@ -837,4 +989,105 @@ describe('InsticatorBidAdapter', function () { expect(bidResponse.vastUrl).to.match(/^data:text\/xml;charset=utf-8;base64,[\w+/=]+$/) }); }) + + describe(`Response with DSA data`, function() { + const bidRequestDsa = { + method: 'POST', + url: 'https://ex.ingage.tech/v1/openrtb', + options: { + contentType: 'application/json', + withCredentials: true, + }, + data: '', + bidderRequest: { + bidderRequestId: '22edbae2733bf6', + auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', + timeout: 300, + bids: [ + { + bidder: 'insticator', + params: { + adUnitId: '1a2b3c4d5e6f1a2b3c4d' + }, + adUnitCode: 'adunit-code-1', + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + playerSize: [[250, 300]], + placement: 2, + plcmt: 2, + } + }, + bidId: 'bid1', + } + ], + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: 2, + pubrender: 1, + datatopub: 2, + transparency: [{ + domain: 'google.com', + dsaparams: [1, 2] + }] + } + }} + }, + } + }; + + const bidResponseDsa = { + body: { + id: '22edbae2733bf6', + bidid: 'foo9876', + cur: 'USD', + seatbid: [ + { + seat: 'some-dsp', + bid: [ + { + ad: '', + impid: 'bid1', + crid: 'crid1', + price: 0.5, + w: 300, + h: 250, + adm: '', + exp: 60, + adomain: ['test1.com'], + ext: { + meta: { + test: 1, + }, + dsa: { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'google.com', + dsaparams: [1, 2] + }], + adrender: 1 + } + }, + } + ], + }, + ] + } + }; + const bidRequestWithDsa = utils.deepClone(bidRequestDsa); + it('should have related properties for DSA data', function() { + const serverResponseWithDsa = utils.deepClone(bidResponseDsa); + const bidResponse = spec.interpretResponse(serverResponseWithDsa, bidRequestWithDsa)[0]; + expect(bidResponse).to.have.any.keys('ext'); + expect(bidResponse.ext.dsa).to.have.property('behalf', 'Advertiser'); + expect(bidResponse.ext.dsa).to.have.property('paid', 'Advertiser'); + expect(bidResponse.ext.dsa).to.have.property('adrender', 1); + }); + }); }); From 3dda9d211d91e46080936349390f4a4dae610753 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Mon, 15 Apr 2024 13:25:28 -0400 Subject: [PATCH 628/760] KRKPD-996: refactors interpretResponse (#30) (#11340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactors interpretResponse * updates comment * simpler * removes unnecessary comment * removes more unnecessary comments * revert ttl * reverts ttl test values * revert some || changes * removes comments --------- Co-authored-by: “Nick <“nick.llerandi”@kargo.com> --- modules/kargoBidAdapter.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index fe22915223e..f3b3166ccad 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -195,25 +195,19 @@ function buildRequests(validBidRequests, bidderRequest) { } function interpretResponse(response, bidRequest) { - let bids = response.body; + const bids = response.body; const bidResponses = []; - if (isEmpty(bids)) { + if (isEmpty(bids) || typeof bids !== 'object') { return bidResponses; } - if (typeof bids !== 'object') { - return bidResponses; - } - - Object.entries(bids).forEach((entry) => { - const [bidID, adUnit] = entry; - + for (const [bidID, adUnit] of Object.entries(bids)) { let meta = { mediaType: adUnit.mediaType && BIDDER.SUPPORTED_MEDIA_TYPES.includes(adUnit.mediaType) ? adUnit.mediaType : BANNER }; - if (adUnit.metadata && adUnit.metadata.landingPageDomain) { + if (adUnit.metadata?.landingPageDomain) { meta.clickUrl = adUnit.metadata.landingPageDomain[0]; meta.advertiserDomains = adUnit.metadata.landingPageDomain; } @@ -243,7 +237,7 @@ function interpretResponse(response, bidRequest) { } bidResponses.push(bidResponse); - }) + } return bidResponses; } From ea8692c4a9f6e34e1bf93c5877b170f87e79ebf8 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Mon, 15 Apr 2024 19:36:18 +0200 Subject: [PATCH 629/760] ZetaGlobalSsp Bid Adapter: modify timeout object (#11236) * ZetaGlobalSsp Adapter: modify onTimeout object * - * fix unit test --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspBidAdapter.js | 9 ++++++++- test/spec/modules/zeta_global_sspBidAdapter_spec.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index 722c51dc058..f3e2e12c143 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -272,7 +272,14 @@ export const spec = { onTimeout: function(timeoutData) { if (timeoutData) { - ajax(TIMEOUT_URL, null, JSON.stringify(timeoutData), { + const payload = timeoutData.map(d => ({ + bidder: d?.bidder, + shortname: d?.params?.map(p => p?.tags?.shortname).find(p => p), + sid: d?.params?.map(p => p?.sid).find(p => p), + country: d?.ortb2?.device?.geo?.country, + devicetype: d?.ortb2?.device?.devicetype + })); + ajax(TIMEOUT_URL, null, JSON.stringify(payload), { method: 'POST', options: { withCredentials: false, diff --git a/test/spec/modules/zeta_global_sspBidAdapter_spec.js b/test/spec/modules/zeta_global_sspBidAdapter_spec.js index f9cfe2dde6a..7b5c0278019 100644 --- a/test/spec/modules/zeta_global_sspBidAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspBidAdapter_spec.js @@ -544,7 +544,7 @@ describe('Zeta Ssp Bid Adapter', function () { it('Timeout should exists and be a function', function () { expect(spec.onTimeout).to.exist.and.to.be.a('function'); - expect(spec.onTimeout({ timeout: 1000 })).to.be.undefined; + expect(spec.onTimeout([{bidder: '1'}])).to.be.undefined; }); it('Test schain provided', function () { From ed2b8239e269d0f09b8044b39d94c91f0eff69df Mon Sep 17 00:00:00 2001 From: Muki Seiler Date: Mon, 15 Apr 2024 21:04:43 +0200 Subject: [PATCH 630/760] Build Related : rename constants.json to constants.js (#11292) * Fix lintWarnings gulp parameter * Fix #10829 Rename constants.json to constant.js * Remove unnecessary quotes * Fix import in genericAnalyticsAdapter * remove unnecessary quotes * Change require to imports * Revert "Fix lintWarnings gulp parameter" This reverts commit e210f4e264172bd07d2c767dfd77402bae1d2282. * Add nolint and no-lint-warnings docs to README * WIP refactoring constants into separated exported files * Refactor the rest of imports * Fix invalid import * Add missing MESSAGSES import * Add missing S2S import * Fix broken constants.js import * Use proper events import in magnite adapter * Add import to pubmatic spec * Found another CONSTANTS import * Fix shadowed var * fix zeta global test * Add missing BID_STATUS import * Fix zeta global ssp analytics for real * fix lint error * Remove duplicated TARGETING_KEYS definition * Move FLOOR_SKIPPED_REASON into priceFloors.js * Remove unused CB constants * Move FLOOR_SKIPPED_REASON into pubmaticAnalyticsAdapter * Use proper import * Remove unused var and fix linting error --- README.md | 8 +- creative/constants.js | 14 +- creative/renderers/display/constants.js | 4 +- creative/renderers/native/constants.js | 4 +- .../analyticsAdapter/AnalyticsAdapter.js | 8 +- modules/33acrossAnalyticsAdapter.js | 6 +- modules/adWMGAnalyticsAdapter.js | 20 +- modules/adagioAnalyticsAdapter.js | 16 +- modules/adkernelAdnAnalyticsAdapter.js | 16 +- modules/adlooxAdServerVideo.js | 4 +- modules/adlooxAnalyticsAdapter.js | 8 +- modules/adomikAnalyticsAdapter.js | 14 +- modules/adpod.js | 6 +- modules/adxcgAnalyticsAdapter.js | 20 +- modules/adxpremiumAnalyticsAdapter.js | 18 +- modules/agmaAnalyticsAdapter.js | 8 +- modules/appierAnalyticsAdapter.js | 12 +- modules/asteriobidAnalyticsAdapter.js | 28 +-- modules/atsAnalyticsAdapter.js | 8 +- modules/automatadAnalyticsAdapter.js | 40 ++-- modules/bidViewability.js | 6 +- modules/bidViewabilityIO.js | 6 +- modules/bidwatchAnalyticsAdapter.js | 16 +- modules/brandmetricsRtdProvider.js | 12 +- modules/browsiRtdProvider.js | 6 +- modules/byDataAnalyticsAdapter.js | 6 +- modules/cleanioRtdProvider.js | 6 +- modules/concertAnalyticsAdapter.js | 12 +- modules/confiantRtdProvider.js | 4 +- modules/conversantAnalyticsAdapter.js | 6 +- modules/currency.js | 14 +- modules/datawrkzBidAdapter.js | 8 +- modules/debugging/pbsInterceptor.js | 4 +- modules/dfpAdServerVideo.js | 4 +- modules/dsaControl.js | 10 +- modules/eplanningAnalyticsAdapter.js | 16 +- modules/fintezaAnalyticsAdapter.js | 10 +- modules/gdprEnforcement.js | 6 +- modules/geoedgeRtdProvider.js | 4 +- modules/greenbidsAnalyticsAdapter.js | 14 +- modules/greenbidsRtdProvider.js | 4 +- modules/growthCodeAnalyticsAdapter.js | 28 +-- modules/hadronAnalyticsAdapter.js | 28 +-- modules/holidBidAdapter.js | 4 +- modules/id5AnalyticsAdapter.js | 16 +- modules/instreamTracking.js | 8 +- modules/invisiblyAnalyticsAdapter.js | 32 ++- modules/ixBidAdapter.js | 6 +- modules/kargoAnalyticsAdapter.js | 6 +- modules/konduitAnalyticsAdapter.js | 26 +-- modules/liveIntentAnalyticsAdapter.js | 4 +- modules/livewrappedAnalyticsAdapter.js | 20 +- modules/magniteAnalyticsAdapter.js | 28 ++- modules/malltvAnalyticsAdapter.js | 10 +- modules/mediafilterRtdProvider.js | 4 +- modules/medianetAnalyticsAdapter.js | 24 +-- modules/multibid/index.js | 4 +- modules/neuwoRtdProvider.js | 4 +- modules/nextMillenniumBidAdapter.js | 8 +- modules/nobidAnalyticsAdapter.js | 20 +- modules/ooloAnalyticsAdapter.js | 4 +- modules/oxxionAnalyticsAdapter.js | 16 +- modules/paapi.js | 4 +- modules/pirIdSystem.js | 2 +- modules/prebidServerBidAdapter/index.js | 16 +- .../prebidServerBidAdapter/ortbConverter.js | 6 +- modules/prebidmanagerAnalyticsAdapter.js | 28 +-- modules/priceFloors.js | 15 +- modules/pubmaticAnalyticsAdapter.js | 54 +++-- modules/pubmaticBidAdapter.js | 3 +- modules/pubwiseAnalyticsAdapter.js | 22 +- modules/pubxaiAnalyticsAdapter.js | 12 +- modules/qortexRtdProvider.js | 4 +- modules/roxotAnalyticsAdapter.js | 18 +- modules/rtdModule/index.js | 14 +- modules/scaleableAnalyticsAdapter.js | 10 +- modules/sigmoidAnalyticsAdapter.js | 14 +- modules/smaatoBidAdapter.js | 3 +- modules/sonobiAnalyticsAdapter.js | 22 +- modules/sovrnAnalyticsAdapter.js | 16 +- modules/staqAnalyticsAdapter.js | 18 +- modules/terceptAnalyticsAdapter.js | 14 +- modules/ucfunnelAnalyticsAdapter.js | 12 +- modules/userId/index.js | 6 +- modules/videoModule/index.js | 8 +- modules/yandexAnalyticsAdapter.js | 4 +- modules/yieldoneAnalyticsAdapter.js | 14 +- modules/yuktamediaAnalyticsAdapter.js | 18 +- modules/zeta_global_sspAnalyticsAdapter.js | 10 +- src/adRendering.js | 30 +-- src/adapterManager.js | 16 +- src/adapters/bidderFactory.js | 16 +- src/auction.js | 54 +++-- src/auctionManager.js | 8 +- src/bidderSettings.js | 4 +- src/config.js | 4 +- src/constants.js | 190 +++++++++++++++++ src/constants.json | 196 ------------------ src/events.js | 6 +- src/native.js | 14 +- src/prebid.js | 12 +- src/secureCreatives.js | 10 +- src/targeting.js | 26 +-- src/utils.js | 8 +- test/fixtures/fixtures.js | 88 ++++---- test/helpers/analytics.js | 12 +- test/spec/AnalyticsAdapter_spec.js | 6 +- test/spec/auctionmanager_spec.js | 164 +++++++-------- .../modules/33acrossAnalyticsAdapter_spec.js | 3 +- .../modules/adWMGAnalyticsAdapter_spec.js | 28 +-- .../modules/adagioAnalyticsAdapter_spec.js | 52 ++--- .../spec/modules/adkernelAdnAnalytics_spec.js | 12 +- .../modules/adlooxAnalyticsAdapter_spec.js | 12 +- .../modules/adomikAnalyticsAdapter_spec.js | 14 +- .../modules/adxcgAnalyticsAdapter_spec.js | 14 +- .../adxpremiumAnalyticsAdapter_spec.js | 14 +- .../spec/modules/agmaAnalyticsAdapter_spec.js | 28 +-- .../modules/appierAnalyticsAdapter_spec.js | 2 +- .../asteriobidAnalyticsAdapter_spec.js | 4 +- test/spec/modules/atsAnalyticsAdapter_spec.js | 16 +- .../modules/automatadAnalyticsAdapter_spec.js | 4 +- test/spec/modules/bidViewabilityIO_spec.js | 4 +- test/spec/modules/bidViewability_spec.js | 8 +- .../modules/bidwatchAnalyticsAdapter_spec.js | 13 +- .../modules/byDataAnalyticsAdapter_spec.js | 9 +- test/spec/modules/cleanioRtdProvider_spec.js | 12 +- .../modules/concertAnalyticsAdapter_spec.js | 13 +- test/spec/modules/confiantRtdProvider_spec.js | 4 +- .../conversantAnalyticsAdapter_spec.js | 122 +++++------ test/spec/modules/currency_spec.js | 12 +- test/spec/modules/dsaControl_spec.js | 8 +- .../modules/eplanningAnalyticsAdapter_spec.js | 33 +-- .../modules/fintezaAnalyticsAdapter_spec.js | 10 +- .../modules/genericAnalyticsAdapter_spec.js | 4 +- test/spec/modules/geoedgeRtdProvider_spec.js | 74 +++---- .../modules/greenbidsAnalyticsAdapter_spec.js | 2 +- .../spec/modules/greenbidsRtdProvider_spec.js | 4 +- .../growthCodeAnalyticsAdapter_spec.js | 4 +- .../modules/hadronAnalyticsAdapter_spec.js | 6 +- test/spec/modules/id5AnalyticsAdapter_spec.js | 10 +- test/spec/modules/id5IdSystem_spec.js | 4 +- .../modules/invisiblyAnalyticsAdapter_spec.js | 80 +++---- .../modules/kargoAnalyticsAdapter_spec.js | 6 +- .../modules/konduitAnalyticsAdapter_spec.js | 26 +-- .../modules/lemmaDigitalBidAdapter_spec.js | 2 +- .../liveIntentAnalyticsAdapter_spec.js | 6 +- .../livewrappedAnalyticsAdapter_spec.js | 35 ++-- .../modules/magniteAnalyticsAdapter_spec.js | 26 ++- .../modules/malltvAnalyticsAdapter_spec.js | 6 +- .../modules/mediafilterRtdProvider_spec.js | 4 +- .../modules/medianetAnalyticsAdapter_spec.js | 6 +- .../modules/nobidAnalyticsAdapter_spec.js | 46 ++-- .../spec/modules/ooloAnalyticsAdapter_spec.js | 66 +++--- .../modules/optimonAnalyticsAdapter_spec.js | 1 - .../modules/oxxionAnalyticsAdapter_spec.js | 14 +- test/spec/modules/paapi_spec.js | 22 +- .../modules/pianoDmpAnalyticsAdapter_spec.js | 18 +- .../modules/prebidServerBidAdapter_spec.js | 14 +- .../prebidmanagerAnalyticsAdapter_spec.js | 4 +- test/spec/modules/priceFloors_spec.js | 11 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 26 ++- test/spec/modules/pubmaticBidAdapter_spec.js | 2 +- .../modules/pubstackAnalyticsAdapter_spec.js | 1 - .../modules/pubwiseAnalyticsAdapter_spec.js | 28 +-- .../modules/pubxaiAnalyticsAdapter_spec.js | 14 +- test/spec/modules/qortexRtdProvider_spec.js | 4 +- test/spec/modules/realTimeDataModule_spec.js | 18 +- .../modules/relevantAnalyticsAdapter_spec.js | 6 +- .../modules/relevantdigitalBidAdapter_spec.js | 4 +- .../spec/modules/rivrAnalyticsAdapter_spec.js | 8 +- .../modules/roxotAnalyticsAdapter_spec.js | 50 ++--- .../modules/scaleableAnalyticsAdapter_spec.js | 10 +- .../modules/sigmoidAnalyticsAdapter_spec.js | 1 - .../modules/sonobiAnalyticsAdapter_spec.js | 15 +- .../modules/sovrnAnalyticsAdapter_spec.js | 4 +- .../spec/modules/staqAnalyticsAdapter_spec.js | 14 +- .../modules/terceptAnalyticsAdapter_spec.js | 14 +- test/spec/modules/trionBidAdapter_spec.js | 2 +- .../modules/ucfunnelAnalyticsAdapter_spec.js | 2 +- test/spec/modules/userId_spec.js | 8 +- test/spec/modules/videoModule/pbVideo_spec.js | 6 +- .../modules/yieldoneAnalyticsAdapter_spec.js | 43 ++-- .../yuktamediaAnalyticsAdapter_spec.js | 65 +++--- .../zeta_global_sspAnalyticsAdapter_spec.js | 16 +- test/spec/native_spec.js | 38 ++-- test/spec/unit/adRendering_spec.js | 28 +-- test/spec/unit/core/adapterManager_spec.js | 30 +-- test/spec/unit/core/bidderFactory_spec.js | 12 +- test/spec/unit/core/targeting_spec.js | 94 ++++----- test/spec/unit/pbjs_api_spec.js | 162 +++++++-------- test/spec/unit/secureCreatives_spec.js | 54 ++--- test/spec/utils_spec.js | 4 +- 192 files changed, 1836 insertions(+), 1866 deletions(-) create mode 100644 src/constants.js delete mode 100644 src/constants.json diff --git a/README.md b/README.md index e6d25a5cb5a..70e058d122b 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,12 @@ To lint the code: gulp lint ``` +To lint and only show errors + +```bash +gulp lint --no-lint-warnings +``` + To run the unit tests: ```bash @@ -245,7 +251,7 @@ gulp test To run the unit tests for a particular file (example for pubmaticBidAdapter_spec.js): ```bash -gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" +gulp test --file "test/spec/modules/pubmaticBidAdapter_spec.js" --nolint ``` To generate and view the code coverage reports: diff --git a/creative/constants.js b/creative/constants.js index 6bb92cfe3c2..d02c4c9d5e4 100644 --- a/creative/constants.js +++ b/creative/constants.js @@ -1,9 +1,9 @@ // eslint-disable-next-line prebid/validate-imports -import CONSTANTS from '../src/constants.json'; +import { AD_RENDER_FAILED_REASON, EVENTS, MESSAGES } from '../src/constants.js'; -export const MESSAGE_REQUEST = CONSTANTS.MESSAGES.REQUEST; -export const MESSAGE_RESPONSE = CONSTANTS.MESSAGES.RESPONSE; -export const MESSAGE_EVENT = CONSTANTS.MESSAGES.EVENT; -export const EVENT_AD_RENDER_FAILED = CONSTANTS.EVENTS.AD_RENDER_FAILED; -export const EVENT_AD_RENDER_SUCCEEDED = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED; -export const ERROR_EXCEPTION = CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION; +export const MESSAGE_REQUEST = MESSAGES.REQUEST; +export const MESSAGE_RESPONSE = MESSAGES.RESPONSE; +export const MESSAGE_EVENT = MESSAGES.EVENT; +export const EVENT_AD_RENDER_FAILED = EVENTS.AD_RENDER_FAILED; +export const EVENT_AD_RENDER_SUCCEEDED = EVENTS.AD_RENDER_SUCCEEDED; +export const ERROR_EXCEPTION = AD_RENDER_FAILED_REASON.EXCEPTION; diff --git a/creative/renderers/display/constants.js b/creative/renderers/display/constants.js index d291c79bb34..2493fb2d163 100644 --- a/creative/renderers/display/constants.js +++ b/creative/renderers/display/constants.js @@ -1,4 +1,4 @@ // eslint-disable-next-line prebid/validate-imports -import CONSTANTS from '../../../src/constants.json'; +import { AD_RENDER_FAILED_REASON } from '../../../src/constants.js'; -export const ERROR_NO_AD = CONSTANTS.AD_RENDER_FAILED_REASON.NO_AD; +export const ERROR_NO_AD = AD_RENDER_FAILED_REASON.NO_AD; diff --git a/creative/renderers/native/constants.js b/creative/renderers/native/constants.js index ac20275fca8..b82e2d1d54e 100644 --- a/creative/renderers/native/constants.js +++ b/creative/renderers/native/constants.js @@ -1,7 +1,7 @@ // eslint-disable-next-line prebid/validate-imports -import CONSTANTS from '../../../src/constants.json'; +import { MESSAGES } from '../../../src/constants.js'; -export const MESSAGE_NATIVE = CONSTANTS.MESSAGES.NATIVE; +export const MESSAGE_NATIVE = MESSAGES.NATIVE; export const ACTION_RESIZE = 'resizeNativeHeight'; export const ACTION_CLICK = 'click'; export const ACTION_IMP = 'fireNativeImpressionTrackers'; diff --git a/libraries/analyticsAdapter/AnalyticsAdapter.js b/libraries/analyticsAdapter/AnalyticsAdapter.js index e1933d215e4..395a21e5571 100644 --- a/libraries/analyticsAdapter/AnalyticsAdapter.js +++ b/libraries/analyticsAdapter/AnalyticsAdapter.js @@ -1,4 +1,4 @@ -import CONSTANTS from '../../src/constants.json'; +import { EVENTS } from '../../src/constants.js'; import {ajax} from '../../src/ajax.js'; import {logError, logMessage} from '../../src/utils.js'; import * as events from '../../src/events.js'; @@ -9,8 +9,8 @@ export const _internal = { const ENDPOINT = 'endpoint'; const BUNDLE = 'bundle'; -export const DEFAULT_INCLUDE_EVENTS = Object.values(CONSTANTS.EVENTS) - .filter(ev => ev !== CONSTANTS.EVENTS.AUCTION_DEBUG); +export const DEFAULT_INCLUDE_EVENTS = Object.values(EVENTS) + .filter(ev => ev !== EVENTS.AUCTION_DEBUG); let debounceDelay = 100; @@ -114,7 +114,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } const trackedEvents = (() => { const {includeEvents = DEFAULT_INCLUDE_EVENTS, excludeEvents = []} = (config || {}); return new Set( - Object.values(CONSTANTS.EVENTS) + Object.values(EVENTS) .filter(ev => includeEvents.includes(ev)) .filter(ev => !excludeEvents.includes(ev)) ); diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index e3539906b13..890c963fa71 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -1,12 +1,10 @@ import { deepAccess, logInfo, logWarn, logError, deepClone } from '../src/utils.js'; import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager, { coppaDataHandler, gdprDataHandler, gppDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; - /** - * @typedef {typeof import('../src/constants.json').EVENTS} EVENTS + * @typedef {typeof import('../src/constants.js').EVENTS} EVENTS */ -const { EVENTS } = CONSTANTS; +import { EVENTS } from '../src/constants.js'; /** @typedef {'pending'|'available'|'targetingSet'|'rendered'|'timeout'|'rejected'|'noBid'|'error'} BidStatus */ /** diff --git a/modules/adWMGAnalyticsAdapter.js b/modules/adWMGAnalyticsAdapter.js index dd0340071d1..ed1ac46363c 100644 --- a/modules/adWMGAnalyticsAdapter.js +++ b/modules/adWMGAnalyticsAdapter.js @@ -1,20 +1,18 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; const analyticsType = 'endpoint'; const url = 'https://analytics.wmgroup.us/analytic/collection'; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_WON, - BID_TIMEOUT, - NO_BID, - BID_RESPONSE - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_WON, + BID_TIMEOUT, + NO_BID, + BID_RESPONSE +} = EVENTS; let timestampInit = null; diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 82b3b356c81..bb5de41d3ce 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { BANNER } from '../src/mediaTypes.js'; import { getWindowTop, getWindowSelf, deepAccess, logInfo, logError } from '../src/utils.js'; @@ -12,7 +12,7 @@ import { getGlobal } from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; -const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]); +const events = Object.keys(EVENTS).map(key => EVENTS[key]); const ADAGIO_GVLID = 617; const VERSION = '3.0.0'; const PREBID_VERSION = '$prebid.version$'; @@ -383,22 +383,22 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { try { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: handlerAuctionInit(args); break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: handlerBidResponse(args); break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: handlerAuctionEnd(args); break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: handlerBidWon(args); break; // AD_RENDER_SUCCEEDED seems redundant with BID_WON. // case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: - case CONSTANTS.EVENTS.AD_RENDER_FAILED: - handlerAdRender(args, eventType === CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED); + case EVENTS.AD_RENDER_FAILED: + handlerAdRender(args, eventType === EVENTS.AD_RENDER_SUCCEEDED); break; } } catch (error) { diff --git a/modules/adkernelAdnAnalyticsAdapter.js b/modules/adkernelAdnAnalyticsAdapter.js index 48897f8516b..a32a97af3c6 100644 --- a/modules/adkernelAdnAnalyticsAdapter.js +++ b/modules/adkernelAdnAnalyticsAdapter.js @@ -1,5 +1,5 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { logError, parseUrl, _each } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; @@ -51,26 +51,26 @@ let analyticsAdapter = Object.assign(adapter({analyticsType: 'endpoint'}), } let handler = null; switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: if (analyticsAdapter.context.queue) { analyticsAdapter.context.queue.init(); } initPrivacy(analyticsAdapter.context.requestTemplate, args.bidderRequests); handler = trackAuctionInit; break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: handler = trackBidRequest; break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: handler = trackBidResponse; break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: handler = trackBidWon; break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: handler = trackBidTimeout; break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: handler = trackAuctionEnd; break; } @@ -79,7 +79,7 @@ let analyticsAdapter = Object.assign(adapter({analyticsType: 'endpoint'}), if (analyticsAdapter.context.queue) { analyticsAdapter.context.queue.push(events); } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { sendAll(); } } diff --git a/modules/adlooxAdServerVideo.js b/modules/adlooxAdServerVideo.js index bd715cb34f3..199fecafd13 100644 --- a/modules/adlooxAdServerVideo.js +++ b/modules/adlooxAdServerVideo.js @@ -9,7 +9,7 @@ import { registerVideoSupport } from '../src/adServerManager.js'; import { command as analyticsCommand, COMMAND } from './adlooxAnalyticsAdapter.js'; import { ajax } from '../src/ajax.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { targeting } from '../src/targeting.js'; import { logInfo, isFn, logError, isPlainObject, isStr, isBoolean, deepSetValue, deepClone, timestamp, logWarn } from '../src/utils.js'; @@ -74,7 +74,7 @@ function track(options, callback) { bid.ext.adloox.video.adserver = false; analyticsCommand(COMMAND.TRACK, { - eventType: CONSTANTS.EVENTS.BID_WON, + eventType: EVENTS.BID_WON, args: bid }); } diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 9284d543298..e31ea290e11 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -9,7 +9,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import {loadExternalScript} from '../src/adloader.js'; import {auctionManager} from '../src/auctionManager.js'; import {AUCTION_COMPLETED} from '../src/auction.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; import {find} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; import { @@ -220,9 +220,9 @@ analyticsAdapter.url = function(url, args, bid) { return url + a2qs(args); } -analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDetails) { +analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { if (!(auctionDetails.auctionStatus == AUCTION_COMPLETED && auctionDetails.bidsReceived.length > 0)) return; - analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = NOOP; + analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = NOOP; logMessage(MODULE, 'preloading verification JS'); @@ -235,7 +235,7 @@ analyticsAdapter[`handle_${CONSTANTS.EVENTS.AUCTION_END}`] = function(auctionDet insertElement(link); } -analyticsAdapter[`handle_${CONSTANTS.EVENTS.BID_WON}`] = function(bid) { +analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { if (deepAccess(bid, 'ext.adloox.video.adserver')) { logMessage(MODULE, `measuring '${bid.mediaType}' ad unit code '${bid.adUnitCode}' via Ad Server module`); return; diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js index 27a6821d9f5..d6e1547cce8 100644 --- a/modules/adomikAnalyticsAdapter.js +++ b/modules/adomikAnalyticsAdapter.js @@ -1,16 +1,16 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {logInfo} from '../src/utils.js'; import {find, findIndex} from '../src/polyfill.js'; // Events used in adomik analytics adapter. -const auctionInit = CONSTANTS.EVENTS.AUCTION_INIT; -const auctionEnd = CONSTANTS.EVENTS.AUCTION_END; -const bidRequested = CONSTANTS.EVENTS.BID_REQUESTED; -const bidResponse = CONSTANTS.EVENTS.BID_RESPONSE; -const bidWon = CONSTANTS.EVENTS.BID_WON; -const bidTimeout = CONSTANTS.EVENTS.BID_TIMEOUT; +const auctionInit = EVENTS.AUCTION_INIT; +const auctionEnd = EVENTS.AUCTION_END; +const bidRequested = EVENTS.BID_REQUESTED; +const bidResponse = EVENTS.BID_RESPONSE; +const bidWon = EVENTS.BID_WON; +const bidTimeout = EVENTS.BID_TIMEOUT; const ua = navigator.userAgent; var _sampled = true; diff --git a/modules/adpod.js b/modules/adpod.js index f6d8309cd9f..b6d13673178 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -38,7 +38,7 @@ import {config} from '../src/config.js'; import {ADPOD} from '../src/mediaTypes.js'; import {find, arrayFrom as from} from '../src/polyfill.js'; import {auctionManager} from '../src/auctionManager.js'; -import CONSTANTS from '../src/constants.json'; +import { TARGETING_KEYS } from '../src/constants.js'; const TARGETING_KEY_PB_CAT_DUR = 'hb_pb_cat_dur'; const TARGETING_KEY_CACHE_ID = 'hb_cache_id'; @@ -454,10 +454,10 @@ export function callPrebidCacheAfterAuction(bids, callback) { * @param {Object} bid */ export function sortByPricePerSecond(a, b) { - if (a.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { + if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { return 1; } - if (a.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket > b.adserverTargeting[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { + if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket > b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { return -1; } return 0; diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 21b6c1be783..7ad95121209 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { parseSizesInput, uniques, buildUrl, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getGlobal} from '../src/prebidGlobal.js'; /** @@ -22,29 +22,29 @@ var adxcgAnalyticsAdapter = Object.assign(adapter( }), { track ({eventType, args}) { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: adxcgAnalyticsAdapter.context.events.auctionInit = mapAuctionInit(args); adxcgAnalyticsAdapter.context.auctionTimestamp = args.timestamp; break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: adxcgAnalyticsAdapter.context.auctionId = args.auctionId; adxcgAnalyticsAdapter.context.events.bidRequests.push(mapBidRequested(args)); break; - case CONSTANTS.EVENTS.BID_ADJUSTMENT: + case EVENTS.BID_ADJUSTMENT: break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: adxcgAnalyticsAdapter.context.events.bidTimeout = args.map(item => item.bidder).filter(uniques); break; - case CONSTANTS.EVENTS.BIDDER_DONE: + case EVENTS.BIDDER_DONE: break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: adxcgAnalyticsAdapter.context.events.bidResponses.push(mapBidResponse(args, eventType)); break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: let outData2 = {bidWons: mapBidWon(args)}; send(outData2); break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: send(adxcgAnalyticsAdapter.context.events); break; } @@ -87,7 +87,7 @@ function mapBidResponse (bidResponse, eventType) { currency: bidResponse.currency, netRevenue: bidResponse.netRevenue, timeToRespond: bidResponse.timeToRespond, - bidId: eventType === CONSTANTS.EVENTS.BID_TIMEOUT ? bidResponse.bidId : bidResponse.requestId, + bidId: eventType === EVENTS.BID_TIMEOUT ? bidResponse.bidId : bidResponse.requestId, dealId: bidResponse.dealId, status: bidResponse.status, creativeId: bidResponse.creativeId.toString() diff --git a/modules/adxpremiumAnalyticsAdapter.js b/modules/adxpremiumAnalyticsAdapter.js index 9161c6338f4..e3f77e96725 100644 --- a/modules/adxpremiumAnalyticsAdapter.js +++ b/modules/adxpremiumAnalyticsAdapter.js @@ -2,7 +2,7 @@ import {deepClone, logError, logInfo} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {includes} from '../src/polyfill.js'; const analyticsType = 'endpoint'; @@ -12,15 +12,13 @@ let reqCountry = window.reqCountry || null; // Events needed const { - EVENTS: { - AUCTION_INIT, - BID_REQUESTED, - BID_TIMEOUT, - BID_RESPONSE, - BID_WON, - AUCTION_END - } -} = CONSTANTS; + AUCTION_INIT, + BID_REQUESTED, + BID_TIMEOUT, + BID_RESPONSE, + BID_WON, + AUCTION_END +} = EVENTS; let timeoutBased = false; let requestSent = false; diff --git a/modules/agmaAnalyticsAdapter.js b/modules/agmaAnalyticsAdapter.js index f3933cc7625..e2e01fb4d03 100644 --- a/modules/agmaAnalyticsAdapter.js +++ b/modules/agmaAnalyticsAdapter.js @@ -9,7 +9,7 @@ import { } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager, { gdprDataHandler } from '../src/adapterManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { config } from '../src/config.js'; @@ -22,10 +22,6 @@ const batchDelayInMs = 1000; const agmaURL = 'https://pbc.agma-analytics.de/v1'; const pageViewId = generateUUID(); -const { - EVENTS: { AUCTION_INIT }, -} = CONSTANTS; - // Helper functions const getScreen = () => { const w = window; @@ -212,7 +208,7 @@ agmaAnalytics.enableAnalytics = function (config = {}) { } agmaAnalytics.options = { - triggerEvent: AUCTION_INIT, + triggerEvent: EVENTS.AUCTION_INIT, ...options, }; diff --git a/modules/appierAnalyticsAdapter.js b/modules/appierAnalyticsAdapter.js index b4081feaf92..3664c49c424 100644 --- a/modules/appierAnalyticsAdapter.js +++ b/modules/appierAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {logError, logInfo, deepClone} from '../src/utils.js'; @@ -12,12 +12,10 @@ export const ANALYTICS_VERSION = '1.0.0'; const DEFAULT_SERVER = 'https://prebid-analytics.c.appier.net/v1'; const { - EVENTS: { - AUCTION_END, - BID_WON, - BID_TIMEOUT - } -} = CONSTANTS; + AUCTION_END, + BID_WON, + BID_TIMEOUT +} = EVENTS; export const BIDDER_STATUS = { BID: 'bid', diff --git a/modules/asteriobidAnalyticsAdapter.js b/modules/asteriobidAnalyticsAdapter.js index 516a3a65667..d5b6c0b4cf7 100644 --- a/modules/asteriobidAnalyticsAdapter.js +++ b/modules/asteriobidAnalyticsAdapter.js @@ -3,7 +3,7 @@ import { ajaxBuilder } from '../src/ajax.js' import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' import { getStorageManager } from '../src/storageManager.js' -import CONSTANTS from '../src/constants.json' +import { EVENTS } from '../src/constants.js' import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js' import {getRefererInfo} from '../src/refererDetection.js'; @@ -203,7 +203,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.eventType = eventType switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { pmEvent.auctionId = eventArgs.auctionId pmEvent.timeout = eventArgs.timeout pmEvent.adUnits = eventArgs.adUnits && eventArgs.adUnits.map(trimAdUnit) @@ -212,7 +212,7 @@ function handleEvent(eventType, eventArgs) { auctionTimeouts[pmEvent.auctionId] = pmEvent.timeout break } - case CONSTANTS.EVENTS.AUCTION_END: { + case EVENTS.AUCTION_END: { pmEvent.auctionId = eventArgs.auctionId pmEvent.end = eventArgs.end pmEvent.start = eventArgs.start @@ -222,15 +222,15 @@ function handleEvent(eventType, eventArgs) { pmEvent.end = Date.now() break } - case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + case EVENTS.BID_ADJUSTMENT: { break } - case CONSTANTS.EVENTS.BID_TIMEOUT: { + case EVENTS.BID_TIMEOUT: { pmEvent.bidders = eventArgs && eventArgs.map ? eventArgs.map(trimBid) : eventArgs pmEvent.duration = auctionTimeouts[pmEvent.auctionId] break } - case CONSTANTS.EVENTS.BID_REQUESTED: { + case EVENTS.BID_REQUESTED: { pmEvent.auctionId = eventArgs.auctionId pmEvent.bidderCode = eventArgs.bidderCode pmEvent.doneCbCallCount = eventArgs.doneCbCallCount @@ -241,7 +241,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.timeout = eventArgs.timeout break } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { pmEvent.bidderCode = eventArgs.bidderCode pmEvent.width = eventArgs.width pmEvent.height = eventArgs.height @@ -260,7 +260,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.adserverTargeting = eventArgs.adserverTargeting break } - case CONSTANTS.EVENTS.BID_WON: { + case EVENTS.BID_WON: { pmEvent.auctionId = eventArgs.auctionId pmEvent.adId = eventArgs.adId pmEvent.adserverTargeting = eventArgs.adserverTargeting @@ -278,7 +278,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.bidder = eventArgs.bidder break } - case CONSTANTS.EVENTS.BIDDER_DONE: { + case EVENTS.BIDDER_DONE: { pmEvent.auctionId = eventArgs.auctionId pmEvent.auctionStart = eventArgs.auctionStart pmEvent.bidderCode = eventArgs.bidderCode @@ -291,16 +291,16 @@ function handleEvent(eventType, eventArgs) { pmEvent.src = eventArgs.src break } - case CONSTANTS.EVENTS.SET_TARGETING: { + case EVENTS.SET_TARGETING: { break } - case CONSTANTS.EVENTS.REQUEST_BIDS: { + case EVENTS.REQUEST_BIDS: { break } - case CONSTANTS.EVENTS.ADD_AD_UNITS: { + case EVENTS.ADD_AD_UNITS: { break } - case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + case EVENTS.AD_RENDER_FAILED: { pmEvent.bid = eventArgs.bid pmEvent.message = eventArgs.message pmEvent.reason = eventArgs.reason @@ -317,7 +317,7 @@ function sendEvent(event) { eventQueue.push(event) logInfo(`${analyticsName} Event ${event.eventType}:`, event) - if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (event.eventType === EVENTS.AUCTION_END) { flush() } } diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index 8e92146694f..d94e68b7e55 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { logError, logInfo } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adaptermanager from '../src/adapterManager.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -346,12 +346,12 @@ atsAnalyticsAdapter.enableAnalytics = function (config) { }; atsAnalyticsAdapter.callHandler = function (evtype, args) { - if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + if (evtype === EVENTS.BID_REQUESTED) { handlerRequest = handlerRequest.concat(bidRequestedHandler(args)); - } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + } else if (evtype === EVENTS.BID_RESPONSE) { handlerResponse.push(bidResponseHandler(args)); } - if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + if (evtype === EVENTS.AUCTION_END) { let bidWonTimeout = atsAnalyticsAdapter.context.bidWonTimeout ? atsAnalyticsAdapter.context.bidWonTimeout : 2000; let events = []; setTimeout(() => { diff --git a/modules/automatadAnalyticsAdapter.js b/modules/automatadAnalyticsAdapter.js index 436418e7597..523e8d558ca 100644 --- a/modules/automatadAnalyticsAdapter.js +++ b/modules/automatadAnalyticsAdapter.js @@ -4,7 +4,7 @@ import { logMessage } from '../src/utils.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { config } from '../src/config.js' @@ -57,49 +57,49 @@ const processEvents = () => { try { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler) { window.atmtdAnalytics.auctionInitHandler(args); } else { shouldTryAgain = true } break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler) { window.atmtdAnalytics.bidRequestedHandler(args); } break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler) { window.atmtdAnalytics.bidResponseHandler(args); } break; - case CONSTANTS.EVENTS.BID_REJECTED: + case EVENTS.BID_REJECTED: if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler) { window.atmtdAnalytics.bidRejectedHandler(args); } break; - case CONSTANTS.EVENTS.BIDDER_DONE: + case EVENTS.BIDDER_DONE: if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler) { window.atmtdAnalytics.bidderDoneHandler(args); } break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler) { window.atmtdAnalytics.bidWonHandler(args); } break; - case CONSTANTS.EVENTS.NO_BID: + case EVENTS.NO_BID: if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler) { window.atmtdAnalytics.noBidHandler(args); } break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler) { window.atmtdAnalytics.bidderTimeoutHandler(args); } break; - case CONSTANTS.EVENTS.AUCTION_DEBUG: + case EVENTS.AUCTION_DEBUG: if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler) { window.atmtdAnalytics.auctionDebugHandler(args); } @@ -173,7 +173,7 @@ const initializeQueue = () => { timer = null; } - if (args[0] === CONSTANTS.EVENTS.AUCTION_INIT) { + if (args[0] === EVENTS.AUCTION_INIT) { const timeout = parseInt(config.getConfig('bidderTimeout')) + 1500 timer = setTimeout(() => { self.processEvents() @@ -198,7 +198,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { track({eventType, args}) { const shouldNotPushToQueue = !self.qBeingUsed switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: if (window.atmtdAnalytics && window.atmtdAnalytics.auctionInitHandler && shouldNotPushToQueue) { self.prettyLog('status', 'Aggregator loaded, initialising auction through handlers'); window.atmtdAnalytics.auctionInitHandler(args); @@ -207,7 +207,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: if (window.atmtdAnalytics && window.atmtdAnalytics.bidRequestedHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidRequestedHandler(args); } else { @@ -215,7 +215,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BID_REJECTED: + case EVENTS.BID_REJECTED: if (window.atmtdAnalytics && window.atmtdAnalytics.bidRejectedHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidRejectedHandler(args); } else { @@ -223,7 +223,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: if (window.atmtdAnalytics && window.atmtdAnalytics.bidResponseHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidResponseHandler(args); } else { @@ -231,7 +231,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BIDDER_DONE: + case EVENTS.BIDDER_DONE: if (window.atmtdAnalytics && window.atmtdAnalytics.bidderDoneHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidderDoneHandler(args); } else { @@ -239,7 +239,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: if (window.atmtdAnalytics && window.atmtdAnalytics.bidWonHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidWonHandler(args); } else { @@ -247,7 +247,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.NO_BID: + case EVENTS.NO_BID: if (window.atmtdAnalytics && window.atmtdAnalytics.noBidHandler && shouldNotPushToQueue) { window.atmtdAnalytics.noBidHandler(args); } else { @@ -255,7 +255,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.AUCTION_DEBUG: + case EVENTS.AUCTION_DEBUG: if (window.atmtdAnalytics && window.atmtdAnalytics.auctionDebugHandler && shouldNotPushToQueue) { window.atmtdAnalytics.auctionDebugHandler(args); } else { @@ -263,7 +263,7 @@ let atmtdAdapter = Object.assign({}, baseAdapter, { self.__atmtdAnalyticsQueue.push([eventType, args]) } break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: if (window.atmtdAnalytics && window.atmtdAnalytics.bidderTimeoutHandler && shouldNotPushToQueue) { window.atmtdAnalytics.bidderTimeoutHandler(args); } else { diff --git a/modules/bidViewability.js b/modules/bidViewability.js index be18095e369..c1aab83b7e6 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -4,7 +4,7 @@ import {config} from '../src/config.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; @@ -82,12 +82,12 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { } // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels - events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, respectiveBid); + events.emit(EVENTS.BID_VIEWABLE, respectiveBid); } }; export let init = () => { - events.on(CONSTANTS.EVENTS.AUCTION_INIT, () => { + events.on(EVENTS.AUCTION_INIT, () => { // read the config for the module const globalModuleConfig = config.getConfig(MODULE_NAME) || {}; // do nothing if module-config.enabled is not set to true diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js index ff7ec70e32c..61b8af66bf8 100644 --- a/modules/bidViewabilityIO.js +++ b/modules/bidViewabilityIO.js @@ -1,7 +1,7 @@ import { logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; const MODULE_NAME = 'bidViewabilityIO'; const CONFIG_ENABLED = 'enabled'; @@ -42,7 +42,7 @@ export let getViewableOptions = (bid) => { export let markViewed = (bid, entry, observer) => { return () => { observer.unobserve(entry.target); - events.emit(CONSTANTS.EVENTS.BID_VIEWABLE, bid); + events.emit(EVENTS.BID_VIEWABLE, bid); _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); } } @@ -77,7 +77,7 @@ export let init = () => { if (conf[MODULE_NAME][CONFIG_ENABLED] && CLIENT_SUPPORTS_IO) { // if the module is enabled and the browser supports Intersection Observer, // then listen to AD_RENDER_SUCCEEDED to setup IO's for supported mediaTypes - events.on(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { + events.on(EVENTS.AD_RENDER_SUCCEEDED, ({doc, bid, id}) => { if (isSupportedMediaType(bid)) { let viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); let element = document.getElementById(bid.adUnitCode); diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index 289e607686f..ffbd125eeab 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -1,6 +1,6 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -8,14 +8,12 @@ const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; const { - EVENTS: { - AUCTION_END, - BID_WON, - BID_RESPONSE, - BID_REQUESTED, - BID_TIMEOUT, - } -} = CONSTANTS; + AUCTION_END, + BID_WON, + BID_RESPONSE, + BID_REQUESTED, + BID_TIMEOUT, +} = EVENTS; let saveEvents = {} let allEvents = {} diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 2d9dcdfdf48..b463a5480f8 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -5,11 +5,11 @@ * @module modules/brandmetricsRtdProvider * @requires module:modules/realTimeData */ -import {submodule} from '../src/hook.js'; -import {deepAccess, deepSetValue, logError, mergeDeep, generateUUID} from '../src/utils.js'; -import {loadExternalScript} from '../src/adloader.js'; +import { submodule } from '../src/hook.js'; +import { deepAccess, deepSetValue, logError, mergeDeep, generateUUID } from '../src/utils.js'; +import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -156,7 +156,7 @@ function initializeBillableEvents() { handler: (ev) => { if (ev.source && ev.source.type === 'pbj') { const bid = ev.source.data; - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { vendor: 'brandmetrics', type: 'creative_in_view', measurementId: ev.mid, @@ -202,7 +202,7 @@ export const brandmetricsSubmodule = { logError(e) } }, - init: init + init } submodule('realTimeData', brandmetricsSubmodule) diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index ab3db2a5d20..ad4019ed03c 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -23,7 +23,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {find, includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; /** @@ -67,7 +67,7 @@ export function addBrowsiTag(data) { export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { vendor: 'browsi', type: 'pageview', billingId: generateUUID() @@ -363,7 +363,7 @@ function getTargetingData(uc, c, us, a) { } if (sendAdRequestEvent) { const transactionId = a.adUnits.find(adUnit => adUnit.code === auc).transactionId; - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { vendor: 'browsi', type: 'adRequest', billingId: generateUUID(), diff --git a/modules/byDataAnalyticsAdapter.js b/modules/byDataAnalyticsAdapter.js index 81fd4388c7d..508a5593f58 100644 --- a/modules/byDataAnalyticsAdapter.js +++ b/modules/byDataAnalyticsAdapter.js @@ -3,7 +3,7 @@ import Base64 from 'crypto-js/enc-base64'; import hmacSHA512 from 'crypto-js/hmac-sha512'; import enc from 'crypto-js/enc-utf8'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, BID_STATUS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import { auctionManager } from '../src/auctionManager.js'; @@ -12,7 +12,7 @@ import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const versionCode = '4.4.1' const secretKey = 'bydata@123456' -const { EVENTS: { NO_BID, BID_TIMEOUT, AUCTION_END, AUCTION_INIT, BID_WON } } = CONSTANTS +const { NO_BID, BID_TIMEOUT, AUCTION_END, AUCTION_INIT, BID_WON } = EVENTS const DEFAULT_EVENT_URL = 'https://pbjs-stream.bydata.com/topics/prebid' const analyticsType = 'endpoint' const isBydata = isKeyInUrl('bydata_debug') @@ -342,7 +342,7 @@ ascAdapter.dataProcess = function (t) { }) }); - var prebidWinningBids = auctionManager.getBidsReceived().filter(bid => bid.status === CONSTANTS.BID_STATUS.BID_TARGETING_SET); + var prebidWinningBids = auctionManager.getBidsReceived().filter(bid => bid.status === BID_STATUS.BID_TARGETING_SET); prebidWinningBids && prebidWinningBids.length > 0 && prebidWinningBids.forEach(pbbid => { payload['auctionData'] && payload['auctionData'].forEach(rwData => { if (rwData['bid'] === pbbid.requestId && rwData['brs'] === pbbid.size) { diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index f9bed5357ee..c3f3dd51a61 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -10,7 +10,7 @@ import { submodule } from '../src/hook.js'; import { loadExternalScript } from '../src/adloader.js'; import { logError, generateUUID, insertElement } from '../src/utils.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -158,8 +158,8 @@ function readConfig(config) { let startBillableEvents = function() { // Upon clean.io submodule initialization, every winner bid is considered to be protected // and therefore, subjected to billing - events.on(CONSTANTS.EVENTS.BID_WON, winnerBidResponse => { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.on(EVENTS.BID_WON, winnerBidResponse => { + events.emit(EVENTS.BILLABLE_EVENT, { vendor: 'clean.io', billingId: generateUUID(), type: 'impression', diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js index 210d1898338..742646960f3 100644 --- a/modules/concertAnalyticsAdapter.js +++ b/modules/concertAnalyticsAdapter.js @@ -1,7 +1,7 @@ import { logMessage } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; const analyticsType = 'endpoint'; @@ -13,12 +13,10 @@ const pageIncludedInSample = sampleAnalytics(); const url = 'https://bids.concert.io/analytics'; const { - EVENTS: { - BID_RESPONSE, - BID_WON, - AUCTION_END - } -} = CONSTANTS; + BID_RESPONSE, + BID_WON, + AUCTION_END +} = EVENTS; let queue = []; diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 6b1066a20f1..4c5475421bb 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -12,7 +12,7 @@ import { submodule } from '../src/hook.js'; import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; /** * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided @@ -89,7 +89,7 @@ function setUpMutationObserver() { function getEventHandlerFunction(propertyId) { return function reportBillableEvent(e) { if (e.data.type.indexOf('cnft:reportBillableEvent:' + propertyId) > -1) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { auctionId: e.data.auctionId, billingId: generateUUID(), transactionId: e.data.transactionId, diff --git a/modules/conversantAnalyticsAdapter.js b/modules/conversantAnalyticsAdapter.js index 0c58402ca87..44e712d54f7 100644 --- a/modules/conversantAnalyticsAdapter.js +++ b/modules/conversantAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getGlobal} from '../src/prebidGlobal.js'; import adapterManager from '../src/adapterManager.js'; import {logInfo, logWarn, logError, logMessage, deepAccess, isInteger} from '../src/utils.js'; @@ -8,9 +8,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; // Maintainer: mediapsr@epsilon.com -const { - EVENTS: { AUCTION_END, AD_RENDER_FAILED, BID_TIMEOUT, BID_WON, BIDDER_ERROR } -} = CONSTANTS; +const { AUCTION_END, AD_RENDER_FAILED, BID_TIMEOUT, BID_WON, BIDDER_ERROR } = EVENTS; // STALE_RENDER, TCF2_ENFORCEMENT would need to add extra calls for these as they likely occur after AUCTION_END? const GVLID = 24; const ANALYTICS_TYPE = 'endpoint'; diff --git a/modules/currency.js b/modules/currency.js index eaed4c50df2..c26e64a9400 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -1,6 +1,6 @@ import {logError, logInfo, logMessage, logWarn} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, REJECTION_REASON } from '../src/constants.js'; import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {getHook} from '../src/hook.js'; @@ -163,8 +163,8 @@ function initCurrency() { getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); getHook('addBidResponse').before(addBidResponseHook, 100); getHook('responsesReady').before(responsesReadyHook); - onEvent(CONSTANTS.EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - onEvent(CONSTANTS.EVENTS.AUCTION_INIT, loadRates); + onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + onEvent(EVENTS.AUCTION_INIT, loadRates); loadRates(); } @@ -173,8 +173,8 @@ function resetCurrency() { getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); - offEvent(CONSTANTS.EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - offEvent(CONSTANTS.EVENTS.AUCTION_INIT, loadRates); + offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + offEvent(EVENTS.AUCTION_INIT, loadRates); delete getGlobal().convertCurrency; adServerCurrency = 'USD'; @@ -230,7 +230,7 @@ export const addBidResponseHook = timedBidResponseHook('currency', function addB function rejectOnAuctionTimeout({auctionId}) { bidResponseQueue = bidResponseQueue.filter(([fn, ctx, adUnitCode, bid, reject]) => { if (bid.auctionId === auctionId) { - reject(CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY) + reject(REJECTION_REASON.CANNOT_CONVERT_CURRENCY) } else { return true; } @@ -250,7 +250,7 @@ function processBidResponseQueue() { } } catch (e) { logWarn('getCurrencyConversion threw error: ', e); - reject(CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + reject(REJECTION_REASON.CANNOT_CONVERT_CURRENCY); continue; } } diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index db795c89155..b5a096521a1 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -12,7 +12,7 @@ import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { createBid } from '../src/bidfactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import CONSTANTS from '../src/constants.json'; +import { STATUS } from '../src/constants.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; /** @@ -416,7 +416,7 @@ function buildBannerResponse(bidRequest, bidResponse) { if (bidRequest) { let bidResponse = createBid(1); placementCode = bidRequest.placementCode; - bidRequest.status = CONSTANTS.STATUS.GOOD; + bidRequest.status = STATUS.GOOD; responseCPM = parseFloat(bidderBid.price); if (responseCPM === 0 || isNaN(responseCPM)) { let bid = createBid(2); @@ -457,7 +457,7 @@ function buildNativeResponse(bidRequest, response) { if (bidRequest) { let bidResponse = createBid(1); placementCode = bidRequest.placementCode; - bidRequest.status = CONSTANTS.STATUS.GOOD; + bidRequest.status = STATUS.GOOD; responseCPM = parseFloat(bidderBid.price); if (responseCPM === 0 || isNaN(responseCPM)) { let bid = createBid(2); @@ -506,7 +506,7 @@ function buildVideoResponse(bidRequest, response) { if (bidRequest) { let bidResponse = createBid(1); placementCode = bidRequest.placementCode; - bidRequest.status = CONSTANTS.STATUS.GOOD; + bidRequest.status = STATUS.GOOD; responseCPM = parseFloat(bidderBid.price); if (responseCPM === 0 || isNaN(responseCPM)) { let bid = createBid(2); diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 73df01bf205..5c2eb458b18 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -1,5 +1,5 @@ import {deepClone, delayExecution} from '../../src/utils.js'; -import CONSTANTS from '../../src/constants.json'; +import { STATUS } from '../../src/constants.js'; export function makePbsInterceptor({createBid}) { return function pbsBidInterceptor(next, interceptBids, s2sBidRequest, bidRequests, ajax, { @@ -17,7 +17,7 @@ export function makePbsInterceptor({createBid}) { function addBid(bid, bidRequest) { onBid({ adUnit: bidRequest.adUnitCode, - bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid) + bid: Object.assign(createBid(STATUS.GOOD, bidRequest), bid) }) } bidRequests = bidRequests diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index 7f275992210..abf58aceb45 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -20,7 +20,7 @@ import {getHook, submodule} from '../src/hook.js'; import {auctionManager} from '../src/auctionManager.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getPPID} from '../src/adserver.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; @@ -360,7 +360,7 @@ function getCustParams(bid, options, urlCustParams) { ); // TODO: WTF is this? just firing random events, guessing at the argument, hoping noone notices? - events.emit(CONSTANTS.EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); + events.emit(EVENTS.SET_TARGETING, {[adUnit.code]: prebidTargetingSet}); // merge the prebid + publisher targeting sets const publisherTargetingSet = deepAccess(options, 'params.cust_params'); diff --git a/modules/dsaControl.js b/modules/dsaControl.js index b08a6ea1f4e..73a1dd19cd4 100644 --- a/modules/dsaControl.js +++ b/modules/dsaControl.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {auctionManager} from '../src/auctionManager.js'; import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; -import CONSTANTS from '../src/constants.json'; +import { REJECTION_REASON } from '../src/constants.js'; import {getHook} from '../src/hook.js'; import {logInfo, logWarn} from '../src/utils.js'; @@ -18,18 +18,18 @@ export const addBidResponseHook = timedBidResponseHook('dsa', function (fn, adUn if (!bid.meta?.dsa) { if (dsaRequest.dsarequired === 1) { // request says dsa is supported; response does not have dsa info; warn about it - logWarn(`dsaControl: ${CONSTANTS.REJECTION_REASON.DSA_REQUIRED}; will still be accepted as regs.ext.dsa.dsarequired = 1`, bid); + logWarn(`dsaControl: ${REJECTION_REASON.DSA_REQUIRED}; will still be accepted as regs.ext.dsa.dsarequired = 1`, bid); } else if ([2, 3].includes(dsaRequest.dsarequired)) { // request says dsa is required; response does not have dsa info; reject it - rejectReason = CONSTANTS.REJECTION_REASON.DSA_REQUIRED; + rejectReason = REJECTION_REASON.DSA_REQUIRED; } } else { if (dsaRequest.pubrender === 0 && bid.meta.dsa.adrender === 0) { // request says publisher can't render; response says advertiser won't; reject it - rejectReason = CONSTANTS.REJECTION_REASON.DSA_MISMATCH; + rejectReason = REJECTION_REASON.DSA_MISMATCH; } else if (dsaRequest.pubrender === 2 && bid.meta.dsa.adrender === 1) { // request says publisher will render; response says advertiser will; reject it - rejectReason = CONSTANTS.REJECTION_REASON.DSA_MISMATCH; + rejectReason = REJECTION_REASON.DSA_MISMATCH; } } } diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js index 9eb701b8ecc..45a0be54715 100644 --- a/modules/eplanningAnalyticsAdapter.js +++ b/modules/eplanningAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { logError } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; const analyticsType = 'endpoint'; const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; @@ -64,18 +64,18 @@ function bidTimeoutHandler(args) { function callHandler(evtype, args) { let handler = null; - if (evtype === CONSTANTS.EVENTS.AUCTION_INIT) { + if (evtype === EVENTS.AUCTION_INIT) { handler = auctionInitHandler; eplAnalyticsAdapter.context.events = []; - } else if (evtype === CONSTANTS.EVENTS.AUCTION_END) { + } else if (evtype === EVENTS.AUCTION_END) { handler = auctionEndHandler; - } else if (evtype === CONSTANTS.EVENTS.BID_REQUESTED) { + } else if (evtype === EVENTS.BID_REQUESTED) { handler = bidRequestedHandler; - } else if (evtype === CONSTANTS.EVENTS.BID_RESPONSE) { + } else if (evtype === EVENTS.BID_RESPONSE) { handler = bidResponseHandler - } else if (evtype === CONSTANTS.EVENTS.BID_TIMEOUT) { + } else if (evtype === EVENTS.BID_TIMEOUT) { handler = bidTimeoutHandler; - } else if (evtype === CONSTANTS.EVENTS.BID_WON) { + } else if (evtype === EVENTS.BID_WON) { handler = bidWonHandler; } @@ -95,7 +95,7 @@ var eplAnalyticsAdapter = Object.assign(adapter( callHandler(eventType, args); } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { try { let strjson = JSON.stringify(eplAnalyticsAdapter.context.events); ajax(eplAnalyticsAdapter.context.host + eplAnalyticsAdapter.context.ci + '?d=' + encodeURIComponent(strjson)); diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index be661c96061..ab41272c85f 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -3,7 +3,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_CODE = 'finteza'; @@ -330,16 +330,16 @@ function prepareTrackData(evtype, args) { let prepareParams = null; switch (evtype) { - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: prepareParams = prepareBidRequestedParams; break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: prepareParams = prepareBidResponseParams; break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: prepareParams = prepareBidWonParams; break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: prepareParams = prepareBidTimeoutParams; break; } diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js index 5b73ec19e08..caa498c7364 100644 --- a/modules/gdprEnforcement.js +++ b/modules/gdprEnforcement.js @@ -6,7 +6,7 @@ import {deepAccess, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../src/consentHandler.js'; import { MODULE_TYPE_ANALYTICS, @@ -292,11 +292,11 @@ function emitTCF2FinalResults() { geoBlocked: formatSet(geoBlocked) }; - events.emit(CONSTANTS.EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); + events.emit(EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults); [storageBlocked, biddersBlocked, analyticsBlocked, ufpdBlocked, eidsBlocked, geoBlocked].forEach(el => el.clear()); } -events.on(CONSTANTS.EVENTS.AUCTION_END, emitTCF2FinalResults); +events.on(EVENTS.AUCTION_END, emitTCF2FinalResults); /** * A configuration function that initializes some module variables, as well as adds hooks diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 0b0d9027c03..46f7e7f7d6d 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -19,7 +19,7 @@ import { submodule } from '../src/hook.js'; import { ajax } from '../src/ajax.js'; import { generateUUID, createInvisibleIframe, insertElement, isEmpty, logError } from '../src/utils.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -237,7 +237,7 @@ function fireBillableEventsForApplicableBids(params) { let data = message.data; if (isBillingMessage(data, params)) { let winningBid = auctionManager.findBidByAdId(data.adId); - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { vendor: SUBMODULE_NAME, billingId: data.impressionId, type: winningBid ? 'impression' : data.type, diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index b881e868bf3..fd2bc1c25ea 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {deepClone, generateUUID, logError, logInfo, logWarn} from '../src/utils.js'; @@ -11,13 +11,11 @@ export const ANALYTICS_VERSION = '2.2.0'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_TIMEOUT, - BILLABLE_EVENT, - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_TIMEOUT, + BILLABLE_EVENT, +} = EVENTS; export const BIDDER_STATUS = { BID: 'bid', diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js index 7fcd163a7c2..5496fc71c4e 100644 --- a/modules/greenbidsRtdProvider.js +++ b/modules/greenbidsRtdProvider.js @@ -2,7 +2,7 @@ import { logError, deepClone, generateUUID, deepSetValue, deepAccess } from '../ import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; const MODULE_NAME = 'greenbidsRtdProvider'; const MODULE_VERSION = '2.0.0'; @@ -28,7 +28,7 @@ function onAuctionInitEvent(auctionDetails) { let greenbidsId = deepAccess(auctionDetails.adUnits[0], 'ortb2Imp.ext.greenbids.greenbidsId', defaultId); /* greenbids was successfully called so we emit the event */ if (greenbidsId !== defaultId) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { type: 'auction', billingId: generateUUID(), auctionId: auctionDetails.auctionId, diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index 5c7cc254f1d..d2cd160a364 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -5,7 +5,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {logError, logInfo} from '../src/utils.js'; @@ -35,70 +35,70 @@ let growthCodeAnalyticsAdapter = Object.assign(adapter({url: url, analyticsType} let data = {}; if (!trackEvents.includes(eventType)) return; switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { data = eventData; startAuction = data.timestamp; bidRequestTimeout = data.timeout; break; } - case CONSTANTS.EVENTS.AUCTION_END: { + case EVENTS.AUCTION_END: { data = eventData; data.start = startAuction; data.end = Date.now(); break; } - case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + case EVENTS.BID_ADJUSTMENT: { data.bidders = eventData; break; } - case CONSTANTS.EVENTS.BID_TIMEOUT: { + case EVENTS.BID_TIMEOUT: { data.bidders = eventData; data.duration = bidRequestTimeout; break; } - case CONSTANTS.EVENTS.BID_REQUESTED: { + case EVENTS.BID_REQUESTED: { data = eventData; break; } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { data = eventData; delete data.ad; break; } - case CONSTANTS.EVENTS.BID_WON: { + case EVENTS.BID_WON: { data = eventData; delete data.ad; delete data.adUrl; break; } - case CONSTANTS.EVENTS.BIDDER_DONE: { + case EVENTS.BIDDER_DONE: { data = eventData; break; } - case CONSTANTS.EVENTS.SET_TARGETING: { + case EVENTS.SET_TARGETING: { data.targetings = eventData; break; } - case CONSTANTS.EVENTS.REQUEST_BIDS: { + case EVENTS.REQUEST_BIDS: { data = eventData; break; } - case CONSTANTS.EVENTS.ADD_AD_UNITS: { + case EVENTS.ADD_AD_UNITS: { data = eventData; break; } - case CONSTANTS.EVENTS.NO_BID: { + case EVENTS.NO_BID: { data = eventData break; } @@ -170,7 +170,7 @@ function sendEvent(event) { eventQueue.push(event); logInfo(MODULE_NAME + 'Analytics Event: ' + event); - if ((event.eventType === CONSTANTS.EVENTS.AUCTION_END) || (event.eventType === CONSTANTS.EVENTS.BID_WON)) { + if ((event.eventType === EVENTS.AUCTION_END) || (event.eventType === EVENTS.BID_WON)) { logToServer(); } } diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index e4c09c5b6c9..d9fd4fa6f19 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import * as utils from '../src/utils.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; @@ -57,70 +57,70 @@ let hadronAnalyticsAdapter = Object.assign(adapter({url: HADRON_ANALYTICS_URL, a var data = {}; if (!eventsToTrack.includes(eventType)) return; switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { data = args; startAuction = data.timestamp; bidRequestTimeout = data.timeout; break; } - case CONSTANTS.EVENTS.AUCTION_END: { + case EVENTS.AUCTION_END: { data = args; data.start = startAuction; data.end = Date.now(); break; } - case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + case EVENTS.BID_ADJUSTMENT: { data.bidders = args; break; } - case CONSTANTS.EVENTS.BID_TIMEOUT: { + case EVENTS.BID_TIMEOUT: { data.bidders = args; data.duration = bidRequestTimeout; break; } - case CONSTANTS.EVENTS.BID_REQUESTED: { + case EVENTS.BID_REQUESTED: { data = args; break; } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { data = args; delete data.ad; break; } - case CONSTANTS.EVENTS.BID_WON: { + case EVENTS.BID_WON: { data = args; delete data.ad; delete data.adUrl; break; } - case CONSTANTS.EVENTS.BIDDER_DONE: { + case EVENTS.BIDDER_DONE: { data = args; break; } - case CONSTANTS.EVENTS.SET_TARGETING: { + case EVENTS.SET_TARGETING: { data.targetings = args; break; } - case CONSTANTS.EVENTS.REQUEST_BIDS: { + case EVENTS.REQUEST_BIDS: { data = args; break; } - case CONSTANTS.EVENTS.ADD_AD_UNITS: { + case EVENTS.ADD_AD_UNITS: { data = args; break; } - case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + case EVENTS.AD_RENDER_FAILED: { data = args; break; } @@ -186,7 +186,7 @@ function sendEvent(event) { eventQueue.push(event); utils.logInfo(`HADRON_ANALYTICS_EVENT ${event.eventType} `, event); - if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (event.eventType === EVENTS.AUCTION_END) { flush(); } } diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index fbcbb9492c7..f046c860562 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -6,7 +6,7 @@ import { triggerPixel, } from '../src/utils.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -19,7 +19,7 @@ const TIME_TO_LIVE = 300 const TMAX = 500 let wurlMap = {} -events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler) +events.on(EVENTS.BID_WON, bidWonHandler) export const spec = { code: BIDDER_CODE, diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index d0f3198e03d..70da467ff7c 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -1,5 +1,5 @@ import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; @@ -7,14 +7,12 @@ import * as events from '../src/events.js'; import {getGlobal} from '../src/prebidGlobal.js'; const { - EVENTS: { - AUCTION_END, - TCF2_ENFORCEMENT, - BID_WON, - BID_VIEWABLE, - AD_RENDER_FAILED - } -} = CONSTANTS + AUCTION_END, + TCF2_ENFORCEMENT, + BID_WON, + BID_VIEWABLE, + AD_RENDER_FAILED +} = EVENTS const GVLID = 131; diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index ece556d0fd2..2686feab679 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -3,7 +3,7 @@ import { config } from '../src/config.js'; import { auctionManager } from '../src/auctionManager.js'; import { INSTREAM } from '../src/video.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json' +import { EVENTS, TARGETING_KEYS, BID_STATUS } from '../src/constants.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -11,9 +11,9 @@ import CONSTANTS from '../src/constants.json' * @typedef {import('../src/adapters/bidderFactory.js').AdUnit} AdUnit */ -const {CACHE_ID, UUID} = CONSTANTS.TARGETING_KEYS; -const {BID_WON, AUCTION_END} = CONSTANTS.EVENTS; -const {RENDERED} = CONSTANTS.BID_STATUS; +const { CACHE_ID, UUID } = TARGETING_KEYS; +const { BID_WON, AUCTION_END } = EVENTS; +const { RENDERED } = BID_STATUS; const INSTREAM_TRACKING_DEFAULT_CONFIG = { enabled: false, diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index a4f4eba271c..24c2c452402 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -6,7 +6,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { generateUUID, logInfo } from '../src/utils.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; const analyticsType = 'endpoint'; @@ -15,22 +15,20 @@ const ajax = ajaxBuilder(0); // Events needed const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_ADJUSTMENT, - BID_TIMEOUT, - BID_REQUESTED, - BID_RESPONSE, - NO_BID, - BID_WON, - BIDDER_DONE, - SET_TARGETING, - REQUEST_BIDS, - ADD_AD_UNITS, - AD_RENDER_FAILED, - }, -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_ADJUSTMENT, + BID_TIMEOUT, + BID_REQUESTED, + BID_RESPONSE, + NO_BID, + BID_WON, + BIDDER_DONE, + SET_TARGETING, + REQUEST_BIDS, + ADD_AD_UNITS, + AD_RENDER_FAILED, +} = EVENTS; const _VERSION = 1; const _pageViewId = generateUUID(); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index a29c1a39bff..248dbea0046 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -18,7 +18,7 @@ import { } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { getStorageManager } from '../src/storageManager.js'; import * as events from '../src/events.js'; import { find } from '../src/polyfill.js'; @@ -1739,8 +1739,8 @@ export const spec = { */ isBidRequestValid: function (bid) { if (!hasRegisteredHandler) { - events.on(CONSTANTS.EVENTS.AUCTION_DEBUG, localStorageHandler); - events.on(CONSTANTS.EVENTS.AD_RENDER_FAILED, localStorageHandler); + events.on(EVENTS.AUCTION_DEBUG, localStorageHandler); + events.on(EVENTS.AD_RENDER_FAILED, localStorageHandler); hasRegisteredHandler = true; } diff --git a/modules/kargoAnalyticsAdapter.js b/modules/kargoAnalyticsAdapter.js index 652e105167d..f8b088eefe8 100644 --- a/modules/kargoAnalyticsAdapter.js +++ b/modules/kargoAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; const EVENT_URL = 'https://krk.kargo.com/api/v1/event'; const KARGO_BIDDER_CODE = 'kargo'; @@ -23,11 +23,11 @@ var kargoAnalyticsAdapter = Object.assign( adapter({ analyticsType }), { track({ eventType, args }) { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { _logBidResponseData.auctionTimeout = args.timeout; break; } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { handleBidResponseData(args); break; } diff --git a/modules/konduitAnalyticsAdapter.js b/modules/konduitAnalyticsAdapter.js index a1a586b25db..5316d5b22a4 100644 --- a/modules/konduitAnalyticsAdapter.js +++ b/modules/konduitAnalyticsAdapter.js @@ -4,7 +4,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { targeting } from '../src/targeting.js'; import { config } from '../src/config.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; const TRACKER_HOST = 'tracker.konduit.me'; const KONDUIT_PREBID_MODULE_VERSION = '1.0.0'; @@ -12,13 +12,13 @@ const KONDUIT_PREBID_MODULE_VERSION = '1.0.0'; const analyticsType = 'endpoint'; const eventDataComposerMap = { - [CONSTANTS.EVENTS.AUCTION_INIT]: obtainAuctionInfo, - [CONSTANTS.EVENTS.AUCTION_END]: obtainAuctionInfo, - [CONSTANTS.EVENTS.BID_REQUESTED]: obtainBidRequestsInfo, - [CONSTANTS.EVENTS.BID_TIMEOUT]: obtainBidTimeoutInfo, - [CONSTANTS.EVENTS.BID_RESPONSE]: obtainBidResponseInfo, - [CONSTANTS.EVENTS.BID_WON]: obtainWinnerBidInfo, - [CONSTANTS.EVENTS.NO_BID]: obtainNoBidInfo, + [EVENTS.AUCTION_INIT]: obtainAuctionInfo, + [EVENTS.AUCTION_END]: obtainAuctionInfo, + [EVENTS.BID_REQUESTED]: obtainBidRequestsInfo, + [EVENTS.BID_TIMEOUT]: obtainBidTimeoutInfo, + [EVENTS.BID_RESPONSE]: obtainBidResponseInfo, + [EVENTS.BID_WON]: obtainWinnerBidInfo, + [EVENTS.NO_BID]: obtainNoBidInfo, }; // This function is copy from prebid core @@ -43,7 +43,7 @@ function buildUrl(obj) { const getWinnerBidFromAggregatedEvents = () => { return konduitAnalyticsAdapter.context.aggregatedEvents - .filter(evt => evt.eventType === CONSTANTS.EVENTS.BID_WON)[0]; + .filter(evt => evt.eventType === EVENTS.BID_WON)[0]; }; const isWinnerBidDetected = () => { @@ -57,7 +57,7 @@ const konduitAnalyticsAdapter = Object.assign( adapter({ analyticsType }), { track ({ eventType, args }) { - if (CONSTANTS.EVENTS.AUCTION_INIT === eventType) { + if (EVENTS.AUCTION_INIT === eventType) { konduitAnalyticsAdapter.context.aggregatedEvents.splice(0); } @@ -68,12 +68,12 @@ const konduitAnalyticsAdapter = Object.assign( }); } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { if (!isWinnerBidDetected() && isWinnerBidExist()) { - const bidWonData = eventDataComposerMap[CONSTANTS.EVENTS.BID_WON](targeting.getWinningBids()[0]); + const bidWonData = eventDataComposerMap[EVENTS.BID_WON](targeting.getWinningBids()[0]); konduitAnalyticsAdapter.context.aggregatedEvents.push({ - eventType: CONSTANTS.EVENTS.BID_WON, + eventType: EVENTS.BID_WON, data: bidWonData, }); } diff --git a/modules/liveIntentAnalyticsAdapter.js b/modules/liveIntentAnalyticsAdapter.js index 54402bcafc6..04b9e333e8a 100644 --- a/modules/liveIntentAnalyticsAdapter.js +++ b/modules/liveIntentAnalyticsAdapter.js @@ -1,7 +1,7 @@ import {ajax} from '../src/ajax.js'; import { generateUUID, logInfo, logWarn } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -11,7 +11,7 @@ const URL = 'https://wba.liadm.com/analytic-events'; const GVL_ID = 148; const ADAPTER_CODE = 'liveintent'; const DEFAULT_BID_WON_TIMEOUT = 2000; -const { EVENTS: { AUCTION_END } } = CONSTANTS; +const { AUCTION_END } = EVENTS; let bidWonTimeout; function handleAuctionEnd(args) { diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index f3ee81cae7a..2797664e954 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -1,7 +1,7 @@ import { timestamp, logInfo, getWindowTop } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, STATUS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { getGlobal } from '../src/prebidGlobal.js'; @@ -28,11 +28,11 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE logInfo('LIVEWRAPPED_EVENT:', [eventType, args]); switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: logInfo('LIVEWRAPPED_AUCTION_INIT:', args); cache.auctions[args.auctionId] = {bids: {}, bidAdUnits: {}}; break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: logInfo('LIVEWRAPPED_BID_REQUESTED:', args); cache.auctions[args.auctionId].timeStamp = args.start; @@ -73,11 +73,11 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE }); logInfo(livewrappedAnalyticsAdapter.requestEvents); break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: logInfo('LIVEWRAPPED_BID_RESPONSE:', args); let bidResponse = cache.auctions[args.auctionId].bids[args.requestId]; - bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.isBid = args.getStatusCode() === STATUS.GOOD; bidResponse.width = args.width; bidResponse.height = args.height; bidResponse.cpm = args.cpm; @@ -101,7 +101,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE }; } break; - case CONSTANTS.EVENTS.BIDDER_DONE: + case EVENTS.BIDDER_DONE: logInfo('LIVEWRAPPED_BIDDER_DONE:', args); args.bids.forEach(doneBid => { let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId || doneBid.requestId]; @@ -111,7 +111,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE bid.readyToSend = 1; }); break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: logInfo('LIVEWRAPPED_BID_WON:', args); let wonBid = cache.auctions[args.auctionId].bids[args.requestId]; wonBid.won = true; @@ -123,7 +123,7 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE livewrappedAnalyticsAdapter.sendEvents(); } break; - case CONSTANTS.EVENTS.AD_RENDER_FAILED: + case EVENTS.AD_RENDER_FAILED: logInfo('LIVEWRAPPED_AD_RENDER_FAILED:', args); let adRenderFailedBid = cache.auctions[args.bid.auctionId].bids[args.bid.requestId]; adRenderFailedBid.adRenderFailed = true; @@ -133,13 +133,13 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE livewrappedAnalyticsAdapter.sendEvents(); } break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: logInfo('LIVEWRAPPED_BID_TIMEOUT:', args); args.forEach(timeout => { cache.auctions[timeout.auctionId].bids[timeout.bidId].timeout = true; }); break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: logInfo('LIVEWRAPPED_AUCTION_END:', args); setTimeout(() => { livewrappedAnalyticsAdapter.sendEvents(); diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 5cc45e3adbf..6d45d05cd5c 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -19,7 +19,7 @@ import { } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, REJECTION_REASON } from '../src/constants.js'; import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -50,19 +50,17 @@ let cookieless; let prebidGlobal = getGlobal(); const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_TIMEOUT, - BID_WON, - BILLABLE_EVENT, - SEAT_NON_BID, - BID_REJECTED - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_TIMEOUT, + BID_WON, + BILLABLE_EVENT, + SEAT_NON_BID, + BID_REJECTED +} = EVENTS; // The saved state of rubicon specific setConfig controls export let rubiConf; @@ -921,7 +919,7 @@ magniteAdapter.track = ({ eventType, args }) => { handleBidResponse(args, 'success'); break; case BID_REJECTED: - const bidStatus = args.rejectionReason === CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET ? BID_REJECTED_IPF : 'rejected'; + const bidStatus = args.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET ? BID_REJECTED_IPF : 'rejected'; handleBidResponse(args, bidStatus); break; case SEAT_NON_BID: diff --git a/modules/malltvAnalyticsAdapter.js b/modules/malltvAnalyticsAdapter.js index af903795e49..b4fad0976fb 100644 --- a/modules/malltvAnalyticsAdapter.js +++ b/modules/malltvAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {ajax} from '../src/ajax.js' import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' -import CONSTANTS from '../src/constants.json' +import { EVENTS } from '../src/constants.js' import adapterManager from '../src/adapterManager.js' import {getGlobal} from '../src/prebidGlobal.js' import {logInfo, logError, deepClone} from '../src/utils.js' @@ -10,11 +10,9 @@ export const ANALYTICS_VERSION = '1.0.0' export const DEFAULT_SERVER = 'https://central.mall.tv/analytics' const { - EVENTS: { - AUCTION_END, - BID_TIMEOUT - } -} = CONSTANTS + AUCTION_END, + BID_TIMEOUT +} = EVENTS export const BIDDER_STATUS = { BID: 1, diff --git a/modules/mediafilterRtdProvider.js b/modules/mediafilterRtdProvider.js index 8a082ad4d59..fae5c9e769b 100644 --- a/modules/mediafilterRtdProvider.js +++ b/modules/mediafilterRtdProvider.js @@ -14,7 +14,7 @@ import { submodule } from '../src/hook.js'; import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; /** The event type for Media Filter. */ export const MEDIAFILTER_EVENT_TYPE = 'com.mediatrust.pbjs.'; @@ -65,7 +65,7 @@ export const MediaFilter = { generateEventHandler: function(configurationHash) { return (windowEvent) => { if (windowEvent.data.type === MEDIAFILTER_EVENT_TYPE.concat('.', configurationHash)) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { + events.emit(EVENTS.BILLABLE_EVENT, { 'billingId': generateUUID(), 'configurationHash': configurationHash, 'type': 'impression', diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index b902727a730..68927cc6b13 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -12,7 +12,7 @@ import { } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { BID_STATUS, EVENTS, TARGETING_KEYS } from '../src/constants.js'; import {ajax} from '../src/ajax.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; @@ -590,7 +590,7 @@ function bidResponseHandler(bid) { dfpbd = bid[priceGranularityKey] || cpm; } bidObj.dfpbd = dfpbd; - if (bid.status === CONSTANTS.BID_STATUS.BID_REJECTED) { + if (bid.status === BID_STATUS.BID_REJECTED) { bidObj.status = BID_FLOOR_REJECTED; } else { bidObj.status = BID_SUCCESS; @@ -660,12 +660,12 @@ function setTargetingHandler(params) { adunitObj.targeting = params[adunit]; auctionObj.setTargetingTime = Date.now(); let targetingObj = Object.keys(params[adunit]).reduce((result, key) => { - if (key.indexOf(CONSTANTS.TARGETING_KEYS.AD_ID) !== -1) { + if (key.indexOf(TARGETING_KEYS.AD_ID) !== -1) { result[key] = params[adunit][key] } return result; }, {}); - const winnerAdId = params[adunit][CONSTANTS.TARGETING_KEYS.AD_ID]; + const winnerAdId = params[adunit][TARGETING_KEYS.AD_ID]; let winningBid; let bidAdIds = Object.keys(targetingObj).map(k => targetingObj[k]); auctionObj.bidWrapper.bidObjs.filter((bid) => bidAdIds.indexOf(bid.adId) !== -1).map(function(bid) { @@ -845,35 +845,35 @@ let medianetAnalytics = Object.assign(adapter({URL, analyticsType}), { logInfo(eventType, args); } switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { auctionInitHandler(args); break; } - case CONSTANTS.EVENTS.BID_REQUESTED: { + case EVENTS.BID_REQUESTED: { bidRequestedHandler(args); break; } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { bidResponseHandler(args); break; } - case CONSTANTS.EVENTS.BID_TIMEOUT: { + case EVENTS.BID_TIMEOUT: { bidTimeoutHandler(args); break; } - case CONSTANTS.EVENTS.NO_BID: { + case EVENTS.NO_BID: { noBidResponseHandler(args); break; } - case CONSTANTS.EVENTS.AUCTION_END: { + case EVENTS.AUCTION_END: { auctionEndHandler(args); break; } - case CONSTANTS.EVENTS.SET_TARGETING : { + case EVENTS.SET_TARGETING: { setTargetingHandler(args); break; } - case CONSTANTS.EVENTS.BID_WON: { + case EVENTS.BID_WON: { bidWonHandler(args); break; } diff --git a/modules/multibid/index.js b/modules/multibid/index.js index 27b88d47cf7..d0ce2ae159e 100644 --- a/modules/multibid/index.js +++ b/modules/multibid/index.js @@ -9,7 +9,7 @@ import { logWarn, deepAccess, getUniqueIdentifierStr, deepSetValue, groupBy } from '../../src/utils.js'; import * as events from '../../src/events.js'; -import CONSTANTS from '../../src/constants.json'; +import { EVENTS } from '../../src/constants.js'; import {addBidderRequests} from '../../src/auction.js'; import {getHighestCpmBidsFromBidPool, sortByDealAndPriceBucketOrCpm} from '../../src/targeting.js'; import {PBS, registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; @@ -230,7 +230,7 @@ export const resetMultibidUnits = () => multibidUnits = {}; */ function init() { // TODO: does this reset logic make sense - what about simultaneous auctions? - events.on(CONSTANTS.EVENTS.AUCTION_INIT, resetMultibidUnits); + events.on(EVENTS.AUCTION_INIT, resetMultibidUnits); setupBeforeHookFnOnce(addBidderRequests, adjustBidderRequestsHook); getHook('addBidResponse').before(addBidResponseHook, 3); setupBeforeHookFnOnce(getHighestCpmBidsFromBidPool, targetBidPoolHook); diff --git a/modules/neuwoRtdProvider.js b/modules/neuwoRtdProvider.js index 7c594e2a1c3..a2c2249285b 100644 --- a/modules/neuwoRtdProvider.js +++ b/modules/neuwoRtdProvider.js @@ -3,7 +3,7 @@ import { getRefererInfo } from '../src/refererDetection.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; export const DATA_PROVIDER = 'neuwo.ai'; const SEGTAX_IAB = 6 // IAB - Content Taxonomy version 2 @@ -42,7 +42,7 @@ export function getBidRequestData(reqBidsConfigObj, callback, config, userConsen try { const jsonContent = JSON.parse(responseContent); if (jsonContent.marketing_categories) { - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) + events.emit(EVENTS.BILLABLE_EVENT, { type: 'request', billingId, vendor: neuwoRtdModule.name }) } injectTopics(jsonContent, reqBidsConfigObj, billingId) } catch (ex) { diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index de91b508125..65f530d9e58 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -14,7 +14,7 @@ import { } from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -139,7 +139,7 @@ export const spec = { auctionId, }); - this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_REQUESTED, bid); + this.getUrlPixelMetric(EVENTS.BID_REQUESTED, bid); }); return requests; @@ -183,7 +183,7 @@ export const spec = { bidResponses.push(bidResponse); - this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_RESPONSE, bid); + this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bid); }); }); @@ -263,7 +263,7 @@ export const spec = { onTimeout(bids) { for (const bid of bids) { - this.getUrlPixelMetric(CONSTANTS.EVENTS.BID_TIMEOUT, bid); + this.getUrlPixelMetric(EVENTS.BID_TIMEOUT, bid); }; }, }; diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index 3a272c3f796..5ae77f0cdc8 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -2,7 +2,7 @@ import {deepClone, logError, getParameterByName} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; @@ -17,16 +17,14 @@ const url = 'localhost:8383/event'; const GVLID = 816; const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME, moduleType: MODULE_TYPE_ANALYTICS}); const { - EVENTS: { - AUCTION_INIT, - BID_REQUESTED, - BID_TIMEOUT, - BID_RESPONSE, - BID_WON, - AUCTION_END, - AD_RENDER_SUCCEEDED - } -} = CONSTANTS; + AUCTION_INIT, + BID_REQUESTED, + BID_TIMEOUT, + BID_RESPONSE, + BID_WON, + AUCTION_END, + AD_RENDER_SUCCEEDED +} = EVENTS; function log (msg) { // eslint-disable-next-line no-console console.log(`%cNoBid Analytics ${VERSION}`, 'padding: 2px 8px 2px 8px; background-color:#f50057; color: white', msg); diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 9bc140f0536..8a6ef88a7fb 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { _each, deepClone, pick, deepSetValue, logError, logInfo } from '../src/ import { getOrigin } from '../libraries/getOrigin/index.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' -import CONSTANTS from '../src/constants.json' +import { EVENTS } from '../src/constants.js' import { ajax } from '../src/ajax.js' import { config } from '../src/config.js' @@ -33,7 +33,7 @@ const { BID_WON, BID_TIMEOUT, AD_RENDER_FAILED -} = CONSTANTS.EVENTS +} = EVENTS const SERVER_EVENTS = { AUCTION: 'auction', diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js index 25732d440ff..b3bc2d3479f 100644 --- a/modules/oxxionAnalyticsAdapter.js +++ b/modules/oxxionAnalyticsAdapter.js @@ -1,6 +1,6 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -8,14 +8,12 @@ const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; const { - EVENTS: { - AUCTION_END, - BID_WON, - BID_RESPONSE, - BID_REQUESTED, - BID_TIMEOUT, - } -} = CONSTANTS; + AUCTION_END, + BID_WON, + BID_RESPONSE, + BID_REQUESTED, + BID_TIMEOUT, +} = EVENTS; let saveEvents = {} let allEvents = {} diff --git a/modules/paapi.js b/modules/paapi.js index 720935bd3f5..5c76b2fb327 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -6,7 +6,7 @@ import {getHook, module} from '../src/hook.js'; import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import {EVENTS} from '../src/constants.js'; import {currencyCompare} from '../libraries/currencyUtils/currency.js'; import {maximum, minimum} from '../src/utils/reducers.js'; import {auctionManager} from '../src/auctionManager.js'; @@ -67,7 +67,7 @@ export function init(cfg, configNamespace) { getHook('addComponentAuction').before(addComponentAuctionHook); getHook('makeBidRequests').after(markForFledge); -events.on(CONSTANTS.EVENTS.AUCTION_END, onAuctionEnd); +events.on(EVENTS.AUCTION_END, onAuctionEnd); function getSlotSignals(bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; diff --git a/modules/pirIdSystem.js b/modules/pirIdSystem.js index b891e1d362a..233176028d3 100644 --- a/modules/pirIdSystem.js +++ b/modules/pirIdSystem.js @@ -44,7 +44,7 @@ export const pirIdSubmodule = { * performs action to obtain id and return a value * @function * @returns {(IdResponse|undefined)} - */ + */ getId() { const pirIdToken = readId(); diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 6e4aec8ad92..037119d44a2 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -16,7 +16,7 @@ import { triggerPixel, uniques, } from '../../src/utils.js'; -import CONSTANTS from '../../src/constants.json'; +import { EVENTS, REJECTION_REASON, S2S } from '../../src/constants.js'; import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; @@ -33,7 +33,7 @@ import {ACTIVITY_TRANSMIT_UFPD} from '../../src/activities/activities.js'; const getConfig = config.getConfig; -const TYPE = CONSTANTS.S2S.SRC; +const TYPE = S2S.SRC; let _syncCount = 0; let _s2sConfigs; @@ -467,10 +467,10 @@ export function PrebidServer() { processPBSRequest(s2sBidRequest, bidRequests, ajax, { onResponse: function (isValid, requestedBidders, response) { if (isValid) { - bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest)); + bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); } if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { - events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + events.emit(EVENTS.SEAT_NON_BID, { seatnonbid: response.ext.seatnonbid, auctionId: bidRequests[0].auctionId, requestedBidders, @@ -483,7 +483,7 @@ export function PrebidServer() { }, onError(msg, error) { logError(`Prebid server call failed: '${msg}'`, error); - bidRequests.forEach(bidderRequest => events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, {error, bidderRequest})); + bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_ERROR, { error, bidderRequest })); done(error.timedOut); }, onBid: function ({adUnit, bid}) { @@ -491,7 +491,7 @@ export function PrebidServer() { metrics.checkpoint('addBidResponse'); if ((bid.requestId == null || bid.requestBidder == null) && !s2sBidRequest.s2sConfig.allowUnknownBidderCodes) { logWarn(`PBS adapter received bid from unknown bidder (${bid.bidder}), but 's2sConfig.allowUnknownBidderCodes' is not set. Ignoring bid.`); - addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED); + addBidResponse.reject(adUnit, bid, REJECTION_REASON.BIDDER_DISALLOWED); } else { if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnit, bid))) { addBidResponse(adUnit, bid); @@ -499,7 +499,7 @@ export function PrebidServer() { addWurl(bid.auctionId, bid.adId, bid.pbsWurl); } } else { - addBidResponse.reject(adUnit, bid, CONSTANTS.REJECTION_REASON.INVALID); + addBidResponse.reject(adUnit, bid, REJECTION_REASON.INVALID); } } }, @@ -511,7 +511,7 @@ export function PrebidServer() { }; // Listen for bid won to call wurl - events.on(CONSTANTS.EVENTS.BID_WON, bidWonHandler); + events.on(EVENTS.BID_WON, bidWonHandler); return Object.assign(this, { callBids: baseAdapter.callBids, diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 1dd1532f423..e0f038767c2 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -11,7 +11,7 @@ import { timestamp } from '../../src/utils.js'; import {config} from '../../src/config.js'; -import CONSTANTS from '../../src/constants.json'; +import { STATUS, S2S } from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.js'; @@ -114,8 +114,8 @@ const PBS_CONVERTER = ortbConverter({ // because core has special treatment for PBS adapter responses, we need some additional processing bidResponse.requestTimestamp = context.requestTimestamp; return { - bid: Object.assign(createBid(CONSTANTS.STATUS.GOOD, { - src: CONSTANTS.S2S.SRC, + bid: Object.assign(createBid(STATUS.GOOD, { + src: S2S.SRC, bidId: bidRequest ? (bidRequest.bidId || bidRequest.bid_Id) : null, transactionId: context.adUnit.transactionId, adUnitId: context.adUnit.adUnitId, diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/prebidmanagerAnalyticsAdapter.js index b877918d16d..858e30068db 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/prebidmanagerAnalyticsAdapter.js @@ -3,7 +3,7 @@ import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** @@ -208,7 +208,7 @@ function handleEvent(eventType, eventArgs) { const pmEvent = {}; switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: { + case EVENTS.AUCTION_INIT: { pmEvent.auctionId = eventArgs.auctionId; pmEvent.timeout = eventArgs.timeout; pmEvent.eventType = eventArgs.eventType; @@ -218,7 +218,7 @@ function handleEvent(eventType, eventArgs) { _bidRequestTimeout = pmEvent.timeout; break; } - case CONSTANTS.EVENTS.AUCTION_END: { + case EVENTS.AUCTION_END: { pmEvent.auctionId = eventArgs.auctionId; pmEvent.end = eventArgs.end; pmEvent.start = eventArgs.start; @@ -228,15 +228,15 @@ function handleEvent(eventType, eventArgs) { pmEvent.end = Date.now(); break; } - case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + case EVENTS.BID_ADJUSTMENT: { break; } - case CONSTANTS.EVENTS.BID_TIMEOUT: { + case EVENTS.BID_TIMEOUT: { pmEvent.bidders = eventArgs && eventArgs.map ? eventArgs.map(trimBid) : eventArgs; pmEvent.duration = _bidRequestTimeout; break; } - case CONSTANTS.EVENTS.BID_REQUESTED: { + case EVENTS.BID_REQUESTED: { pmEvent.auctionId = eventArgs.auctionId; pmEvent.bidderCode = eventArgs.bidderCode; pmEvent.doneCbCallCount = eventArgs.doneCbCallCount; @@ -247,7 +247,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.timeout = eventArgs.timeout; break; } - case CONSTANTS.EVENTS.BID_RESPONSE: { + case EVENTS.BID_RESPONSE: { pmEvent.bidderCode = eventArgs.bidderCode; pmEvent.width = eventArgs.width; pmEvent.height = eventArgs.height; @@ -266,7 +266,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.adserverTargeting = eventArgs.adserverTargeting; break; } - case CONSTANTS.EVENTS.BID_WON: { + case EVENTS.BID_WON: { pmEvent.auctionId = eventArgs.auctionId; pmEvent.adId = eventArgs.adId; pmEvent.adserverTargeting = eventArgs.adserverTargeting; @@ -284,7 +284,7 @@ function handleEvent(eventType, eventArgs) { pmEvent.bidder = eventArgs.bidder; break; } - case CONSTANTS.EVENTS.BIDDER_DONE: { + case EVENTS.BIDDER_DONE: { pmEvent.auctionId = eventArgs.auctionId; pmEvent.auctionStart = eventArgs.auctionStart; pmEvent.bidderCode = eventArgs.bidderCode; @@ -297,16 +297,16 @@ function handleEvent(eventType, eventArgs) { pmEvent.src = eventArgs.src; break; } - case CONSTANTS.EVENTS.SET_TARGETING: { + case EVENTS.SET_TARGETING: { break; } - case CONSTANTS.EVENTS.REQUEST_BIDS: { + case EVENTS.REQUEST_BIDS: { break; } - case CONSTANTS.EVENTS.ADD_AD_UNITS: { + case EVENTS.ADD_AD_UNITS: { break; } - case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + case EVENTS.AD_RENDER_FAILED: { pmEvent.bid = eventArgs.bid; pmEvent.message = eventArgs.message; pmEvent.reason = eventArgs.reason; @@ -326,7 +326,7 @@ function sendEvent(event) { _eventQueue.push(event); logInfo(`${analyticsName} Event ${event.eventType}:`, event); - if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (event.eventType === EVENTS.AUCTION_END) { flush(); } } diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 70a0f9b9a14..5df8f938c3d 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -19,7 +19,7 @@ import {getGlobal} from '../src/prebidGlobal.js'; import {config} from '../src/config.js'; import {ajaxBuilder} from '../src/ajax.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, REJECTION_REASON } from '../src/constants.js'; import {getHook} from '../src/hook.js'; import {find} from '../src/polyfill.js'; import {getRefererInfo} from '../src/refererDetection.js'; @@ -31,6 +31,11 @@ import {adjustCpm} from '../src/utils/cpm.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; +export const FLOOR_SKIPPED_REASON = { + NOT_FOUND: 'not_found', + RANDOM: 'random' +}; + /** * @summary This Module is intended to provide users with the ability to dynamically set and enforce price floors on a per auction basis. */ @@ -412,13 +417,13 @@ export function createFloorsDataForAuction(adUnits, auctionId) { // if we still do not have a valid floor data then floors is not on for this auction, so skip if (Object.keys(deepAccess(resolvedFloorsData, 'data.values') || {}).length === 0) { resolvedFloorsData.skipped = true; - resolvedFloorsData.skippedReason = CONSTANTS.FLOOR_SKIPPED_REASON.NOT_FOUND + resolvedFloorsData.skippedReason = FLOOR_SKIPPED_REASON.NOT_FOUND } else { // determine the skip rate now const auctionSkipRate = getParameterByName('pbjs_skipRate') || (deepAccess(resolvedFloorsData, 'data.skipRate') ?? resolvedFloorsData.skipRate); const isSkipped = Math.random() * 100 < parseFloat(auctionSkipRate); resolvedFloorsData.skipped = isSkipped; - if (isSkipped) resolvedFloorsData.skippedReason = CONSTANTS.FLOOR_SKIPPED_REASON.RANDOM + if (isSkipped) resolvedFloorsData.skippedReason = FLOOR_SKIPPED_REASON.RANDOM } // copy FloorMin to floorData.data if (resolvedFloorsData.hasOwnProperty('floorMin')) resolvedFloorsData.data.floorMin = resolvedFloorsData.floorMin; @@ -701,7 +706,7 @@ export function handleSetFloorsConfig(config) { if (!addedFloorsHook) { // register hooks / listening events // when auction finishes remove it's associated floor data after 3 seconds so we stil have it for latent responses - events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => { + events.on(EVENTS.AUCTION_END, (args) => { setTimeout(() => delete _floorDataForAuction[args.auctionId], 3000); }); @@ -802,7 +807,7 @@ export const addBidResponseHook = timedBidResponseHook('priceFloors', function a // now do the compare! if (shouldFloorBid(floorData, floorInfo, bid)) { // bid fails floor -> throw it out - reject(CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET); + reject(REJECTION_REASON.FLOOR_NOT_MET); logWarn(`${MODULE_NAME}: ${bid.bidderCode}'s Bid Response for ${adUnitCode} was rejected due to floor not met (adjusted cpm: ${bid?.floorData?.cpmAfterAdjustments}, floor: ${floorInfo?.matchingFloor})`, bid); return; } diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index ced47086f7b..753b706e363 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -1,12 +1,22 @@ import {_each, isArray, isStr, logError, logWarn, pick, generateUUID} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { BID_STATUS, EVENTS, STATUS, REJECTION_REASON } from '../src/constants.js'; import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +const FLOOR_VALUES = { + NO_DATA: 'noData', + AD_UNIT: 'adUnit', + SET_CONFIG: 'setConfig', + FETCH: 'fetch', + SUCCESS: 'success', + ERROR: 'error', + TIMEOUT: 'timeout' +}; + /// /////////// CONSTANTS ////////////// const ADAPTER_CODE = 'pubmatic'; const VENDOR_OPENWRAP = 'openwrap'; @@ -109,7 +119,7 @@ function copyRequiredBidDetails(bid) { function setBidStatus(bid, args) { switch (args.getStatusCode()) { - case CONSTANTS.STATUS.GOOD: + case STATUS.GOOD: bid.status = SUCCESS; delete bid.error; // it's possible for this to be set by a previous timeout break; @@ -346,9 +356,9 @@ function getFloorFetchStatus(floorData) { return false; } const { location, fetchStatus } = floorData?.floorRequestData; - const isDataValid = location !== CONSTANTS.FLOOR_VALUES.NO_DATA; - const isFetchSuccessful = location === CONSTANTS.FLOOR_VALUES.FETCH && fetchStatus === CONSTANTS.FLOOR_VALUES.SUCCESS; - const isAdUnitOrSetConfig = location === CONSTANTS.FLOOR_VALUES.AD_UNIT || location === CONSTANTS.FLOOR_VALUES.SET_CONFIG; + const isDataValid = location !== FLOOR_VALUES.NO_DATA; + const isFetchSuccessful = location === FLOOR_VALUES.FETCH && fetchStatus === FLOOR_VALUES.SUCCESS; + const isAdUnitOrSetConfig = location === FLOOR_VALUES.AD_UNIT || location === FLOOR_VALUES.SET_CONFIG; return isDataValid && (isAdUnitOrSetConfig || isFetchSuccessful); } @@ -404,16 +414,16 @@ function executeBidsLoggerCall(e, highestCpmBids) { if (floorData?.floorRequestData) { const { location, fetchStatus, floorProvider } = floorData?.floorRequestData; slotObject.ffs = { - [CONSTANTS.FLOOR_VALUES.SUCCESS]: 1, - [CONSTANTS.FLOOR_VALUES.ERROR]: 2, - [CONSTANTS.FLOOR_VALUES.TIMEOUT]: 4, + [FLOOR_VALUES.SUCCESS]: 1, + [FLOOR_VALUES.ERROR]: 2, + [FLOOR_VALUES.TIMEOUT]: 4, undefined: 0 }[fetchStatus]; slotObject.fsrc = { - [CONSTANTS.FLOOR_VALUES.FETCH]: 2, - [CONSTANTS.FLOOR_VALUES.NO_DATA]: 2, - [CONSTANTS.FLOOR_VALUES.AD_UNIT]: 1, - [CONSTANTS.FLOOR_VALUES.SET_CONFIG]: 1 + [FLOOR_VALUES.FETCH]: 2, + [FLOOR_VALUES.NO_DATA]: 2, + [FLOOR_VALUES.AD_UNIT]: 1, + [FLOOR_VALUES.SET_CONFIG]: 1 }[location]; slotObject.fp = floorProvider; } @@ -578,9 +588,9 @@ function bidResponseHandler(args) { function bidRejectedHandler(args) { // If bid is rejected due to floors value did not met // make cpm as 0, status as bidRejected and forward the bid for logging - if (args.rejectionReason === CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET) { + if (args.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET) { args.cpm = 0; - args.status = CONSTANTS.BID_STATUS.BID_REJECTED; + args.status = BID_STATUS.BID_REJECTED; bidResponseHandler(args); } } @@ -674,28 +684,28 @@ let pubmaticAdapter = Object.assign({}, baseAdapter, { track({eventType, args}) { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: auctionInitHandler(args); break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: bidRequestedHandler(args); break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: bidResponseHandler(args); break; - case CONSTANTS.EVENTS.BID_REJECTED: + case EVENTS.BID_REJECTED: bidRejectedHandler(args) break; - case CONSTANTS.EVENTS.BIDDER_DONE: + case EVENTS.BIDDER_DONE: bidderDoneHandler(args); break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: bidWonHandler(args); break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: auctionEndHandler(args); break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: bidTimeoutHandler(args); break; } diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 846c59c1ae5..a428ba9fa6e 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { bidderSettings } from '../src/bidderSettings.js'; -import CONSTANTS from '../src/constants.json'; +import { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } from '../src/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -331,7 +331,6 @@ const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { 'displayurl': 'displayurl' }; -const { NATIVE_IMAGE_TYPES, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS, NATIVE_ASSET_TYPES } = CONSTANTS; const PREBID_NATIVE_DATA_KEY_VALUES = Object.values(PREBID_NATIVE_DATA_KEYS_TO_ORTB); // TODO remove this function when the support for 1.1 is removed diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js index 6aed462f2d5..00d8e3ccb6a 100644 --- a/modules/pubwiseAnalyticsAdapter.js +++ b/modules/pubwiseAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { getParameterByName, logInfo, generateUUID, debugTurnedOn } from '../src import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_CODE = 'pubwise'; @@ -176,13 +176,13 @@ function flushEvents() { function isIngestedEvent(eventType) { const ingested = [ - CONSTANTS.EVENTS.AUCTION_INIT, - CONSTANTS.EVENTS.BID_REQUESTED, - CONSTANTS.EVENTS.BID_RESPONSE, - CONSTANTS.EVENTS.BID_WON, - CONSTANTS.EVENTS.BID_TIMEOUT, - CONSTANTS.EVENTS.AD_RENDER_FAILED, - CONSTANTS.EVENTS.TCF2_ENFORCEMENT + EVENTS.AUCTION_INIT, + EVENTS.BID_REQUESTED, + EVENTS.BID_RESPONSE, + EVENTS.BID_WON, + EVENTS.BID_TIMEOUT, + EVENTS.AD_RENDER_FAILED, + EVENTS.TCF2_ENFORCEMENT ]; return ingested.indexOf(eventType) !== -1; } @@ -278,9 +278,9 @@ pubwiseAnalytics.handleEvent = function(eventType, data) { metaData = enrichWithCustomSegments(metaData); // add data on init to the metadata container - if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + if (eventType === EVENTS.AUCTION_INIT) { data = filterAuctionInit(data); - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + } else if (eventType === EVENTS.BID_RESPONSE) { data = filterBidResponse(data); } @@ -294,7 +294,7 @@ pubwiseAnalytics.handleEvent = function(eventType, data) { } // once the auction ends, or the event is a bid won send events - if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) { + if (eventType === EVENTS.AUCTION_END || eventType === EVENTS.BID_WON) { flushEvents(); } }; diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index e97e5505768..d4a7ec70a70 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { deepAccess, parseSizesInput, getWindowLocation, buildUrl } from '../src import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; @@ -38,9 +38,9 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( }), { track({ eventType, args }) { if (typeof args !== 'undefined') { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + if (eventType === EVENTS.BID_TIMEOUT) { args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + } else if (eventType === EVENTS.AUCTION_INIT) { events.auctionInit = args; events.floorDetail = {}; events.bids = []; @@ -49,15 +49,15 @@ var pubxaiAnalyticsAdapter = Object.assign(adapter( Object.assign(events.floorDetail, floorData); } auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + } else if (eventType === EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); - } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + } else if (eventType === EVENTS.BID_WON) { send({ winningBid: mapBidResponse(args, 'bidwon') }, 'bidwon'); } } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { send(events, 'auctionEnd'); } } diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index 7aa30334756..88b4339b38e 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -3,7 +3,7 @@ import { ajax } from '../src/ajax.js'; import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; let requestUrl; let bidderArray; @@ -128,7 +128,7 @@ export function loadScriptTag(config) { logMessage('received billable event: qx-impression') impressionIds.add(uid) billableEvent.transactionId = e.detail.uid; - events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, billableEvent); + events.emit(EVENTS.BILLABLE_EVENT, billableEvent); break; } default: diff --git a/modules/roxotAnalyticsAdapter.js b/modules/roxotAnalyticsAdapter.js index 2c3be3e1757..8e5371044a2 100644 --- a/modules/roxotAnalyticsAdapter.js +++ b/modules/roxotAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {deepClone, getParameterByName, logError, logInfo} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; import {ajaxBuilder} from '../src/ajax.js'; @@ -18,15 +18,13 @@ const DEFAULT_SERVER_CONFIG_URL = 'pa.rxthdr.com/v3'; const analyticsType = 'endpoint'; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BIDDER_DONE, - BID_WON - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BIDDER_DONE, + BID_WON +} = EVENTS; const AUCTION_STATUS = { 'RUNNING': 'running', diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index c5308c91e18..0c654fc28b0 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -163,7 +163,7 @@ import {config} from '../../src/config.js'; import {getHook, module} from '../../src/hook.js'; import {logError, logInfo, logWarn} from '../../src/utils.js'; import * as events from '../../src/events.js'; -import CONSTANTS from '../../src/constants.json'; +import { EVENTS, JSON_MAPPING } from '../../src/constants.js'; import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../../src/adapterManager.js'; import {find} from '../../src/polyfill.js'; import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; @@ -213,11 +213,11 @@ const setEventsListeners = (function () { return function setEventsListeners() { if (!registered) { Object.entries({ - [CONSTANTS.EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], - [CONSTANTS.EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], - [CONSTANTS.EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], - [CONSTANTS.EVENTS.BID_REQUESTED]: ['onBidRequestEvent'], - [CONSTANTS.EVENTS.BID_ACCEPTED]: ['onBidAcceptedEvent'] + [EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], + [EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], + [EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], + [EVENTS.BID_REQUESTED]: ['onBidRequestEvent'], + [EVENTS.BID_ACCEPTED]: ['onBidAcceptedEvent'] }).forEach(([ev, [handler, preprocess]]) => { events.on(ev, (args) => { preprocess && preprocess(args); @@ -380,7 +380,7 @@ export function getAdUnitTargeting(auction) { return } logInfo('RTD set ad unit targeting of', kv, 'for', adUnit); - adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] || {}, kv); + adUnit[JSON_MAPPING.ADSERVER_TARGETING] = Object.assign(adUnit[JSON_MAPPING.ADSERVER_TARGETING] || {}, kv); }); return auction.adUnits; } diff --git a/modules/scaleableAnalyticsAdapter.js b/modules/scaleableAnalyticsAdapter.js index 46f9d45d84d..054ccb7db55 100644 --- a/modules/scaleableAnalyticsAdapter.js +++ b/modules/scaleableAnalyticsAdapter.js @@ -1,7 +1,7 @@ /* COPYRIGHT SCALEABLE LLC 2019 */ import { ajax } from '../src/ajax.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { logMessage } from '../src/utils.js'; @@ -16,10 +16,10 @@ const entries = Object.entries || function(obj) { return resArray; }; -const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; -const BID_WON = CONSTANTS.EVENTS.BID_WON; -const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; +const BID_TIMEOUT = EVENTS.BID_TIMEOUT; +const AUCTION_INIT = EVENTS.AUCTION_INIT; +const BID_WON = EVENTS.BID_WON; +const AUCTION_END = EVENTS.AUCTION_END; const URL = 'https://auction.scaleable.ai/'; const ANALYTICS_TYPE = 'endpoint'; diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js index 18e1e20e3e3..a9d92b67e24 100644 --- a/modules/sigmoidAnalyticsAdapter.js +++ b/modules/sigmoidAnalyticsAdapter.js @@ -2,7 +2,7 @@ Updated : 2018-03-28 */ import {includes} from '../src/polyfill.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {getStorageManager} from '../src/storageManager.js'; import {generateUUID, logError, logInfo} from '../src/utils.js'; @@ -14,12 +14,12 @@ const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName const url = 'https://kinesis.us-east-1.amazonaws.com/'; const analyticsType = 'endpoint'; -const auctionInitConst = CONSTANTS.EVENTS.AUCTION_INIT; -const auctionEndConst = CONSTANTS.EVENTS.AUCTION_END; -const bidWonConst = CONSTANTS.EVENTS.BID_WON; -const bidRequestConst = CONSTANTS.EVENTS.BID_REQUESTED; -const bidAdjustmentConst = CONSTANTS.EVENTS.BID_ADJUSTMENT; -const bidResponseConst = CONSTANTS.EVENTS.BID_RESPONSE; +const auctionInitConst = EVENTS.AUCTION_INIT; +const auctionEndConst = EVENTS.AUCTION_END; +const bidWonConst = EVENTS.BID_WON; +const bidRequestConst = EVENTS.BID_REQUESTED; +const bidAdjustmentConst = EVENTS.BID_ADJUSTMENT; +const bidResponseConst = EVENTS.BID_RESPONSE; let initOptions = { publisherIds: [], utmTagData: [], adUnits: [] }; let bidWon = {options: {}, events: []}; diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index ac0422842d5..64d8765dc04 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -3,7 +3,7 @@ import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import CONSTANTS from '../src/constants.json'; +import { NATIVE_IMAGE_TYPES } from '../src/constants.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; import {fill} from '../libraries/appnexusUtils/anUtils.js'; import {chunk} from '../libraries/chunk/chunk.js'; @@ -16,7 +16,6 @@ import {chunk} from '../libraries/chunk/chunk.js'; * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ -const { NATIVE_IMAGE_TYPES } = CONSTANTS; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.8' diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js index 04a855b5be6..8242df7e0c5 100644 --- a/modules/sonobiAnalyticsAdapter.js +++ b/modules/sonobiAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { deepClone, logInfo, logError } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {ajaxBuilder} from '../src/ajax.js'; @@ -10,17 +10,15 @@ export const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; const analyticsType = 'endpoint'; const QUEUE_TIMEOUT_DEFAULT = 200; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BIDDER_DONE, - BID_WON, - BID_RESPONSE, - BID_TIMEOUT - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BIDDER_DONE, + BID_WON, + BID_RESPONSE, + BID_TIMEOUT +} = EVENTS; let initOptions = {}; let auctionCache = {}; diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js index a72c4b1a5a5..a89b365e074 100644 --- a/modules/sovrnAnalyticsAdapter.js +++ b/modules/sovrnAnalyticsAdapter.js @@ -1,7 +1,7 @@ import {logError, timestamp} from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adaptermanager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {ajaxBuilder} from '../src/ajax.js'; import {config} from '../src/config.js'; import {find, includes} from '../src/polyfill.js'; @@ -10,14 +10,12 @@ import {getRefererInfo} from '../src/refererDetection.js'; const ajax = ajaxBuilder(0) const { - EVENTS: { - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BID_RESPONSE, - BID_WON - } -} = CONSTANTS + AUCTION_END, + BID_REQUESTED, + BID_ADJUSTMENT, + BID_RESPONSE, + BID_WON +} = EVENTS; let pbaUrl = 'https://pba.aws.lijit.com/analytics' let currentAuctions = {}; diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index c1aaa727af5..ac5e86db19d 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -1,6 +1,6 @@ import { logInfo, logError, parseUrl, _each } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { ajax } from '../src/ajax.js'; @@ -55,25 +55,25 @@ let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { } let handler = null; switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: if (analyticsAdapter.context.queue) { analyticsAdapter.context.queue.init(); } handler = trackAuctionInit; break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: handler = trackBidRequest; break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: handler = trackBidResponse; break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: handler = trackBidWon; break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: handler = trackBidTimeout; break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: handler = trackAuctionEnd; break; } @@ -81,11 +81,11 @@ let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { let events = handler(args); if (analyticsAdapter.context.queue) { analyticsAdapter.context.queue.push(events); - if (eventType === CONSTANTS.EVENTS.BID_WON) { + if (eventType === EVENTS.BID_WON) { analyticsAdapter.context.queue.updateWithWins(events); } } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { sendAll(); } } diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index c17948d73d0..089f8d917d6 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -2,7 +2,7 @@ import { parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; @@ -24,23 +24,23 @@ var terceptAnalyticsAdapter = Object.assign(adapter( }), { track({ eventType, args }) { if (typeof args !== 'undefined') { - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT) { + if (eventType === EVENTS.BID_TIMEOUT) { args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) { + } else if (eventType === EVENTS.AUCTION_INIT) { events.auctionInit = args; auctionTimestamp = args.timestamp; - } else if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + } else if (eventType === EVENTS.BID_REQUESTED) { mapBidRequests(args).forEach(item => { events.bids.push(item) }); - } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) { + } else if (eventType === EVENTS.BID_RESPONSE) { mapBidResponse(args, 'response'); - } else if (eventType === CONSTANTS.EVENTS.BID_WON) { + } else if (eventType === EVENTS.BID_WON) { send({ bidWon: mapBidResponse(args, 'win') }, 'won'); } } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + if (eventType === EVENTS.AUCTION_END) { send(events, 'auctionEnd'); } } diff --git a/modules/ucfunnelAnalyticsAdapter.js b/modules/ucfunnelAnalyticsAdapter.js index 77fffddbaae..3b4053d3626 100644 --- a/modules/ucfunnelAnalyticsAdapter.js +++ b/modules/ucfunnelAnalyticsAdapter.js @@ -1,6 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {logError, logInfo, deepClone} from '../src/utils.js'; @@ -12,12 +12,10 @@ export const ANALYTICS_VERSION = '1.0.0'; const ANALYTICS_SERVER = 'https://hbwa.aralego.com'; const { - EVENTS: { - AUCTION_END, - BID_WON, - BID_TIMEOUT - } -} = CONSTANTS; + AUCTION_END, + BID_WON, + BID_TIMEOUT +} = EVENTS; export const BIDDER_STATUS = { BID: 'bid', diff --git a/modules/userId/index.js b/modules/userId/index.js index 0e1df12e1db..90d377a816e 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -131,7 +131,7 @@ import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; -import CONSTANTS from '../../src/constants.json'; +import { EVENTS } from '../../src/constants.js'; import {module, ready as hooksReady} from '../../src/hook.js'; import {buildEidPermissions, createEidsArray, EID_CONFIG} from './eids.js'; import { @@ -538,8 +538,8 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { if (auctionDelay > 0) { startCallbacks.resolve(); } else { - events.on(CONSTANTS.EVENTS.AUCTION_END, function auctionEndHandler() { - events.off(CONSTANTS.EVENTS.AUCTION_END, auctionEndHandler); + events.on(EVENTS.AUCTION_END, function auctionEndHandler() { + events.off(EVENTS.AUCTION_END, auctionEndHandler); delay(syncDelay).then(startCallbacks.resolve); }); } diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js index 28f5c90d326..c84d98a6d5f 100644 --- a/modules/videoModule/index.js +++ b/modules/videoModule/index.js @@ -3,7 +3,7 @@ import { find } from '../../src/polyfill.js'; import * as events from '../../src/events.js'; import {mergeDeep, logWarn, logError} from '../../src/utils.js'; import { getGlobal } from '../../src/prebidGlobal.js'; -import CONSTANTS from '../../src/constants.json'; +import { EVENTS } from '../../src/constants.js'; import { videoEvents, AUCTION_AD_LOAD_ATTEMPT, @@ -71,7 +71,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent requestBids.before(beforeBidsRequested, 40); - pbEvents.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { + pbEvents.on(EVENTS.BID_ADJUSTMENT, function (bid) { videoImpressionVerifier.trackBid(bid); }); @@ -107,7 +107,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent const bidsBackHandler = bidderRequest.bidsBackHandler; if (!bidsBackHandler || typeof bidsBackHandler !== 'function') { - pbEvents.on(CONSTANTS.EVENTS.AUCTION_END, auctionEnd); + pbEvents.on(EVENTS.AUCTION_END, auctionEnd); } return nextFn.call(this, bidderRequest); @@ -192,7 +192,7 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent renderWinningBid(adUnit); } }); - pbEvents.off(CONSTANTS.EVENTS.AUCTION_END, auctionEnd); + pbEvents.off(EVENTS.AUCTION_END, auctionEnd); } function getAdServerConfig(adUnitVideoConfig) { diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js index ba000db6162..5150d5d7dca 100644 --- a/modules/yandexAnalyticsAdapter.js +++ b/modules/yandexAnalyticsAdapter.js @@ -1,7 +1,7 @@ import buildAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { logError, logInfo } from '../src/utils.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import * as events from '../src/events.js'; const timeoutIds = {}; @@ -32,7 +32,7 @@ const { BIDDER_DONE, AUCTION_END, BID_TIMEOUT, -} = CONSTANTS.EVENTS; +} = EVENTS; export const EVENTS_TO_TRACK = [ BID_REQUESTED, diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index 0663ecb3f76..23fa0e0eec9 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -1,7 +1,7 @@ import { isArray, deepClone } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { targeting } from '../src/targeting.js'; import { auctionManager } from '../src/auctionManager.js'; @@ -14,9 +14,9 @@ const requestedBidders = {}; const requestedBids = {}; const referrers = {}; const ignoredEvents = {}; -ignoredEvents[CONSTANTS.EVENTS.BID_ADJUSTMENT] = true; -ignoredEvents[CONSTANTS.EVENTS.BIDDER_DONE] = true; -ignoredEvents[CONSTANTS.EVENTS.AUCTION_END] = true; +ignoredEvents[EVENTS.BID_ADJUSTMENT] = true; +ignoredEvents[EVENTS.BIDDER_DONE] = true; +ignoredEvents[EVENTS.AUCTION_END] = true; let currentAuctionId = ''; let url = defaultUrl; @@ -69,7 +69,7 @@ function addAdUnitName(params, map) { const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { getUrl() { return url; }, track({eventType, args = {}}) { - if (eventType === CONSTANTS.EVENTS.BID_REQUESTED) { + if (eventType === EVENTS.BID_REQUESTED) { const reqBidderId = `${args.bidderCode}_${args.auctionId}`; requestedBidders[reqBidderId] = deepClone(args); requestedBidders[reqBidderId].bids = []; @@ -77,7 +77,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { requestedBids[`${bid.bidId}_${bid.auctionId}`] = bid; }); } - if (eventType === CONSTANTS.EVENTS.BID_TIMEOUT && isArray(args)) { + if (eventType === EVENTS.BID_TIMEOUT && isArray(args)) { const eventsStorage = yieldoneAnalytics.eventsStorage; const reqBidders = {}; args.forEach((bid) => { @@ -118,7 +118,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { } if ( - eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON + eventType === EVENTS.AUCTION_END || eventType === EVENTS.BID_WON ) { params.adServerTargeting = targeting.getAllTargeting( auctionManager.getAdUnitCodes(), diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 820e6365a9f..25e4dc73b74 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -2,7 +2,7 @@ import {buildUrl, generateUUID, getWindowLocation, logError, logInfo, parseSizes import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS, STATUS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; @@ -100,13 +100,13 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin track({ eventType, args }) { if (typeof args !== 'undefined') { switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: + case EVENTS.AUCTION_INIT: logInfo(localStoragePrefix + 'AUCTION_INIT:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { events.auctions[args.auctionId] = { bids: {} }; } break; - case CONSTANTS.EVENTS.BID_REQUESTED: + case EVENTS.BID_REQUESTED: logInfo(localStoragePrefix + 'BID_REQUESTED:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { @@ -135,14 +135,14 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin }); } break; - case CONSTANTS.EVENTS.BID_RESPONSE: + case EVENTS.BID_RESPONSE: logInfo(localStoragePrefix + 'BID_RESPONSE:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { events.auctions[args.auctionId] = { bids: {} }; } else if (Object.keys(events.auctions[args.auctionId]['bids']).length) { let bidResponse = events.auctions[args.auctionId]['bids'][args.requestId]; - bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD; + bidResponse.isBid = args.getStatusCode() === STATUS.GOOD; bidResponse.cpm = args.cpm; bidResponse.currency = args.currency; bidResponse.netRevenue = args.netRevenue; @@ -165,7 +165,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } } break; - case CONSTANTS.EVENTS.NO_BID: + case EVENTS.NO_BID: logInfo(localStoragePrefix + 'NO_BID:', JSON.stringify(args)); if (typeof args.auctionId !== 'undefined' && args.auctionId.length) { if (typeof events.auctions[args.auctionId] === 'undefined') { @@ -176,7 +176,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } } break; - case CONSTANTS.EVENTS.BID_WON: + case EVENTS.BID_WON: logInfo(localStoragePrefix + 'BID_WON:', JSON.stringify(args)); if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { updateSessionId(); @@ -192,7 +192,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin } } break; - case CONSTANTS.EVENTS.BID_TIMEOUT: + case EVENTS.BID_TIMEOUT: logInfo(localStoragePrefix + 'BID_TIMEOUT:', JSON.stringify(args)); if (args.length) { args.forEach(timeout => { @@ -208,7 +208,7 @@ var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoin }); } break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: logInfo(localStoragePrefix + 'AUCTION_END:', JSON.stringify(args)); if (typeof initOptions.enableSession !== 'undefined' && initOptions.enableSession) { updateSessionId(); diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index 1eb4cab93b0..f0933d2f62f 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -1,7 +1,7 @@ import {logInfo, logError} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; -import CONSTANTS from '../src/constants.json'; +import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -48,7 +48,7 @@ function getZetaParams(event) { /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { - let eventType = CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED + let eventType = EVENTS.AD_RENDER_SUCCEEDED logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); const event = { @@ -88,7 +88,7 @@ function adRenderSucceededHandler(args) { } function auctionEndHandler(args) { - let eventType = CONSTANTS.EVENTS.AUCTION_END; + let eventType = EVENTS.AUCTION_END; logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); const event = { @@ -177,10 +177,10 @@ let zetaAdapter = Object.assign({}, baseAdapter, { track({ eventType, args }) { switch (eventType) { - case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + case EVENTS.AD_RENDER_SUCCEEDED: adRenderSucceededHandler(args); break; - case CONSTANTS.EVENTS.AUCTION_END: + case EVENTS.AUCTION_END: auctionEndHandler(args); break; } diff --git a/src/adRendering.js b/src/adRendering.js index a6d509bea77..7d306adc9cc 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -1,6 +1,6 @@ import {createIframe, deepAccess, inIframe, insertElement, logError, logWarn, replaceMacros} from './utils.js'; import * as events from './events.js'; -import CONSTANTS from './constants.json'; +import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS, MESSAGES } from './constants.js'; import {config} from './config.js'; import {executeRenderer, isRendererRequired} from './Renderer.js'; import {VIDEO} from './mediaTypes.js'; @@ -9,14 +9,14 @@ import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; -const {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON} = CONSTANTS.EVENTS; -const {EXCEPTION} = CONSTANTS.AD_RENDER_FAILED_REASON; +const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; +const { EXCEPTION } = AD_RENDER_FAILED_REASON; /** * Emit the AD_RENDER_FAILED event. * * @param {Object} data - * @param data.reason one of the values in CONSTANTS.AD_RENDER_FAILED_REASON + * @param data.reason one of the values in AD_RENDER_FAILED_REASON * @param data.message failure description * @param [data.bid] bid response object that failed to render * @param [data.id] adId that failed to render @@ -52,7 +52,7 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { export function handleCreativeEvent(data, bidResponse) { switch (data.event) { - case CONSTANTS.EVENTS.AD_RENDER_FAILED: + case EVENTS.AD_RENDER_FAILED: emitAdRenderFail({ bid: bidResponse, id: bidResponse.adId, @@ -60,7 +60,7 @@ export function handleCreativeEvent(data, bidResponse) { message: data.info.message }); break; - case CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED: + case EVENTS.AD_RENDER_SUCCEEDED: emitAdRenderSucceeded({ doc: null, bid: bidResponse, @@ -83,11 +83,11 @@ export function handleNativeMessage(data, bidResponse, {resizeFn, fireTrackers = } const HANDLERS = { - [CONSTANTS.MESSAGES.EVENT]: handleCreativeEvent + [MESSAGES.EVENT]: handleCreativeEvent } if (FEATURES.NATIVE) { - HANDLERS[CONSTANTS.MESSAGES.NATIVE] = handleNativeMessage; + HANDLERS[MESSAGES.NATIVE] = handleNativeMessage; } function creativeMessageHandler(deps) { @@ -115,7 +115,7 @@ export const getRenderingData = hook('sync', function (bidResponse, options) { export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options}) { if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { emitAdRenderFail({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, + reason: AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, message: 'Cannot render video ad', bid: bidResponse, id: bidResponse.adId @@ -145,13 +145,13 @@ doRender.before(function (next, args) { export function handleRender({renderFn, resizeFn, adId, options, bidResponse, doc}) { if (bidResponse == null) { emitAdRenderFail({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, message: `Cannot find ad '${adId}'`, id: adId }); return; } - if (bidResponse.status === CONSTANTS.BID_STATUS.RENDERED) { + if (bidResponse.status === BID_STATUS.RENDERED) { logWarn(`Ad id ${adId} has been rendered before`); events.emit(STALE_RENDER, bidResponse); if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { @@ -162,7 +162,7 @@ export function handleRender({renderFn, resizeFn, adId, options, bidResponse, do doRender({renderFn, resizeFn, bidResponse, options, doc}); } catch (e) { emitAdRenderFail({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, + reason: AD_RENDER_FAILED_REASON.EXCEPTION, message: e.message, id: adId, bid: bidResponse @@ -198,7 +198,7 @@ export function renderAdDirect(doc, adId, options) { .then( () => emitAdRenderSucceeded({doc, bid, adId: bid.adId}), (e) => { - fail(e?.reason || CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) + fail(e?.reason || AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) e?.stack && logError(e); } ); @@ -209,12 +209,12 @@ export function renderAdDirect(doc, adId, options) { } try { if (!adId || !doc) { - fail(CONSTANTS.AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); + fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { bid = auctionManager.findBidByAdId(adId); if ((doc === document && !inIframe())) { - fail(CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); + fail(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); } else { handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); } diff --git a/src/adapterManager.js b/src/adapterManager.js index 72695be0946..cbabfa4380e 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -31,7 +31,7 @@ import {adunitCounter} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; -import CONSTANTS from './constants.json'; +import { EVENTS, S2S } from './constants.js'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from './activities/modules.js'; @@ -242,7 +242,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a * emit and pass adunits for external modification * @see {@link https://github.com/prebid/Prebid.js/issues/4149|Issue} */ - events.emit(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, adUnits); + events.emit(EVENTS.BEFORE_REQUEST_BIDS, adUnits); if (FEATURES.NATIVE) { decorateAdUnitsWithNativeParams(adUnits); } @@ -300,10 +300,10 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a auctionId, bidderRequestId, uniquePbsTid, - bids: hookedGetBids({bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), src: CONSTANTS.S2S.SRC, metrics}), + bids: hookedGetBids({ bidderCode, auctionId, bidderRequestId, 'adUnits': deepClone(adUnitsS2SCopy), src: S2S.SRC, metrics }), auctionStart: auctionStart, timeout: s2sConfig.timeout, - src: CONSTANTS.S2S.SRC, + src: S2S.SRC, refererInfo, metrics, }, s2sParams); @@ -375,7 +375,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request } let [clientBidderRequests, serverBidderRequests] = bidRequests.reduce((partitions, bidRequest) => { - partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC)].push(bidRequest); + partitions[Number(typeof bidRequest.src !== 'undefined' && bidRequest.src === S2S.SRC)].push(bidRequest); return partitions; }, [[], []]); @@ -428,7 +428,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request // fire BID_REQUESTED event for each s2s bidRequest uniqueServerRequests.forEach(bidRequest => { // add the new sourceTid - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, {...bidRequest, tid: bidRequest.auctionId}); + events.emit(EVENTS.BID_REQUESTED, { ...bidRequest, tid: bidRequest.auctionId }); }); // make bid requests @@ -454,7 +454,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request const adapter = _bidderRegistry[bidderRequest.bidderCode]; config.runWithBidder(bidderRequest.bidderCode, () => { logMessage(`CALLING BIDDER`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); + events.emit(EVENTS.BID_REQUESTED, bidderRequest); }); let ajax = ajaxBuilder(requestBidsTimeout, requestCallbacks ? { request: requestCallbacks.request.bind(null, bidderRequest.bidderCode), @@ -630,7 +630,7 @@ function invokeBidderMethod(bidder, method, spec, fn, ...params) { } function tryCallBidderMethod(bidder, method, param) { - if (param?.src !== CONSTANTS.S2S.SRC) { + if (param?.src !== S2S.SRC) { const target = getBidderMethod(bidder, method); if (target != null) { invokeBidderMethod(bidder, method, ...target, param); diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 337ae47f338..1d10c3161e5 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -5,7 +5,7 @@ import {createBid} from '../bidfactory.js'; import {userSync} from '../userSync.js'; import {nativeBidIsValid} from '../native.js'; import {isValidVideoBid} from '../video.js'; -import CONSTANTS from '../constants.json'; +import { EVENTS, STATUS, REJECTION_REASON } from '../constants.js'; import * as events from '../events.js'; import {includes} from '../polyfill.js'; import { @@ -256,7 +256,7 @@ export function newBidder(spec) { if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid))) { addBidResponse(adUnitCode, bid); } else { - addBidResponse.reject(adUnitCode, bid, CONSTANTS.REJECTION_REASON.INVALID) + addBidResponse.reject(adUnitCode, bid, REJECTION_REASON.INVALID) } } @@ -266,7 +266,7 @@ export function newBidder(spec) { function afterAllResponses() { done(); config.runWithBidder(spec.code, () => { - events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); + events.emit(EVENTS.BIDDER_DONE, bidderRequest); registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent); }); } @@ -288,7 +288,7 @@ export function newBidder(spec) { }); processBidderRequests(spec, validBidRequests.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest), ajax, configEnabledCallback, { - onRequest: requestObject => events.emit(CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject), + onRequest: requestObject => events.emit(EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject), onResponse: (resp) => { onTimelyResponse(spec.code); responses.push(resp) @@ -307,7 +307,7 @@ export function newBidder(spec) { onTimelyResponse(spec.code); } adapterManager.callBidderError(spec.code, error, bidderRequest) - events.emit(CONSTANTS.EVENTS.BIDDER_ERROR, { error, bidderRequest }); + events.emit(EVENTS.BIDDER_ERROR, { error, bidderRequest }); logError(`Server call for ${spec.code} failed: ${errorMessage} ${error.status}. Continuing without bids.`); }, onBid: (bid) => { @@ -316,18 +316,18 @@ export function newBidder(spec) { bid.adapterCode = bidRequest.bidder; if (isInvalidAlternateBidder(bid.bidderCode, bidRequest.bidder)) { logWarn(`${bid.bidderCode} is not a registered partner or known bidder of ${bidRequest.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`); - addBidResponse.reject(bidRequest.adUnitCode, bid, CONSTANTS.REJECTION_REASON.BIDDER_DISALLOWED) + addBidResponse.reject(bidRequest.adUnitCode, bid, REJECTION_REASON.BIDDER_DISALLOWED) return; } // creating a copy of original values as cpm and currency are modified later bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); - const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid, pick(bidRequest, TIDS)); + const prebidBid = Object.assign(createBid(STATUS.GOOD, bidRequest), bid, pick(bidRequest, TIDS)); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bid.requestId}. Ignoring.`); - addBidResponse.reject(null, bid, CONSTANTS.REJECTION_REASON.INVALID_REQUEST_ID); + addBidResponse.reject(null, bid, REJECTION_REASON.INVALID_REQUEST_ID); } }, onCompletion: afterAllResponses, diff --git a/src/auction.js b/src/auction.js index 2d7d350bb7a..26845936797 100644 --- a/src/auction.js +++ b/src/auction.js @@ -94,7 +94,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import CONSTANTS from './constants.json'; +import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, S2S, TARGETING_KEYS } from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -107,7 +107,7 @@ export const AUCTION_IN_PROGRESS = 'inProgress'; export const AUCTION_COMPLETED = 'completed'; // register event for bid adjustment -events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { +events.on(EVENTS.BID_ADJUSTMENT, function (bid) { adjustBids(bid); }); @@ -196,7 +196,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a if (!timedOut) { clearTimeout(_timeoutTimer); } else { - events.emit(CONSTANTS.EVENTS.AUCTION_TIMEOUT, getProperties()); + events.emit(EVENTS.AUCTION_TIMEOUT, getProperties()); } if (_auctionEnd === undefined) { let timedOutRequests = []; @@ -204,7 +204,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a logMessage(`Auction ${_auctionId} timedOut`); timedOutRequests = _bidderRequests.filter(rq => !_timelyRequests.has(rq.bidderRequestId)).flatMap(br => br.bids) if (timedOutRequests.length) { - events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, timedOutRequests); + events.emit(EVENTS.BID_TIMEOUT, timedOutRequests); } } @@ -215,7 +215,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a metrics.timeBetween('callBids', 'auctionEnd', 'requestBids.callBids'); done.resolve(); - events.emit(CONSTANTS.EVENTS.AUCTION_END, getProperties()); + events.emit(EVENTS.AUCTION_END, getProperties()); bidsBackCallback(_adUnits, function () { try { if (_callback != null) { @@ -293,7 +293,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a _auctionStatus = AUCTION_IN_PROGRESS; - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, getProperties()); + events.emit(EVENTS.AUCTION_INIT, getProperties()); let callbacks = auctionCallbacks(auctionDone, this); adapterManager.callBids(_adUnits, bidRequests, callbacks.addBidResponse, callbacks.adapterDone, { @@ -335,7 +335,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a call.bidRequests.some(bidRequest => { let requests = 1; - let source = (typeof bidRequest.src !== 'undefined' && bidRequest.src === CONSTANTS.S2S.SRC) ? 's2s' + let source = (typeof bidRequest.src !== 'undefined' && bidRequest.src === S2S.SRC) ? 's2s' : bidRequest.bidderCode; // if we have no previous info on this source just let them through if (sourceInfo[source]) { @@ -381,7 +381,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); } - events.on(CONSTANTS.EVENTS.SEAT_NON_BID, (event) => { + events.on(EVENTS.SEAT_NON_BID, (event) => { if (event.auctionId === _auctionId) { addNonBids(event.seatnonbid) } @@ -465,7 +465,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM function acceptBidResponse(adUnitCode, bid) { handleBidResponse(adUnitCode, bid, (done) => { let bidResponse = getPreparedBidForAuction(bid); - events.emit(CONSTANTS.EVENTS.BID_ACCEPTED, bidResponse); + events.emit(EVENTS.BID_ACCEPTED, bidResponse); if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { tryAddVideoBid(auctionInstance, bidResponse, done); } else { @@ -482,7 +482,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM return handleBidResponse(adUnitCode, bid, (done) => { bid.rejectionReason = reason; logWarn(`Bid from ${bid.bidder || 'unknown bidder'} was rejected: ${reason}`, bid) - events.emit(CONSTANTS.EVENTS.BID_REJECTED, bid); + events.emit(EVENTS.BID_REJECTED, bid); auctionInstance.addBidRejected(bid); done(); }) @@ -507,7 +507,7 @@ export function auctionCallbacks(auctionDone, auctionInstance, {index = auctionM bidderRequest.bids.forEach(bid => { if (!bidResponseMap[bid.bidId]) { auctionInstance.addNoBid(bid); - events.emit(CONSTANTS.EVENTS.NO_BID, bid); + events.emit(EVENTS.NO_BID, bid); } }); @@ -546,7 +546,7 @@ export function addBidToAuction(auctionInstance, bidResponse) { useMetrics(bidResponse.metrics).timeSince('addBidResponse', 'addBidResponse.total'); auctionInstance.addBidReceived(bidResponse); - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); } // Video bids may fail if the cache is down, or there's trouble on the network. @@ -672,7 +672,7 @@ function getPreparedBidForAuction(bid, {index = auctionManager.index} = {}) { // // CAREFUL: Publishers rely on certain bid properties to be available (like cpm), // but others to not be set yet (like priceStrings). See #1372 and #1389. - events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bid); + events.emit(EVENTS.BID_ADJUSTMENT, bid); // a publisher-defined renderer can be used to render bids const bidRenderer = index.getBidRequest(bid)?.renderer || index.getAdUnit(bid).renderer; @@ -767,17 +767,17 @@ export const getPriceGranularity = (bid, {index = auctionManager.index} = {}) => export const getPriceByGranularity = (granularity) => { return (bid) => { const bidGranularity = granularity || getPriceGranularity(bid); - if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + if (bidGranularity === GRANULARITY_OPTIONS.AUTO) { return bid.pbAg; - } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + } else if (bidGranularity === GRANULARITY_OPTIONS.DENSE) { return bid.pbDg; - } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + } else if (bidGranularity === GRANULARITY_OPTIONS.LOW) { return bid.pbLg; - } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + } else if (bidGranularity === GRANULARITY_OPTIONS.MEDIUM) { return bid.pbMg; - } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + } else if (bidGranularity === GRANULARITY_OPTIONS.HIGH) { return bid.pbHg; - } else if (bidGranularity === CONSTANTS.GRANULARITY_OPTIONS.CUSTOM) { + } else if (bidGranularity === GRANULARITY_OPTIONS.CUSTOM) { return bid.pbCg; } } @@ -838,7 +838,6 @@ function createKeyVal(key, value) { } function defaultAdserverTargeting() { - const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; return [ createKeyVal(TARGETING_KEYS.BIDDER, 'bidderCode'), createKeyVal(TARGETING_KEYS.AD_ID, 'adId'), @@ -860,15 +859,14 @@ function defaultAdserverTargeting() { * @returns {*} */ export function getStandardBidderSettings(mediaType, bidderCode) { - const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; const standardSettings = Object.assign({}, bidderSettings.settingsFor(null)); - if (!standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { - standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); + if (!standardSettings[JSON_MAPPING.ADSERVER_TARGETING]) { + standardSettings[JSON_MAPPING.ADSERVER_TARGETING] = defaultAdserverTargeting(); } if (FEATURES.VIDEO && mediaType === 'video') { - const adserverTargeting = standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING].slice(); - standardSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING] = adserverTargeting; + const adserverTargeting = standardSettings[JSON_MAPPING.ADSERVER_TARGETING].slice(); + standardSettings[JSON_MAPPING.ADSERVER_TARGETING] = adserverTargeting; // Adding hb_uuid + hb_cache_id [TARGETING_KEYS.UUID, TARGETING_KEYS.CACHE_ID].forEach(targetingKeyVal => { @@ -906,7 +904,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, {index = aucti setKeys(keyValues, standardSettings, custBidObj, bidRequest); // 2) set keys from specific bidder setting override if they exist - if (bidderCode && bidderSettings.getOwn(bidderCode, CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING)) { + if (bidderCode && bidderSettings.getOwn(bidderCode, JSON_MAPPING.ADSERVER_TARGETING)) { setKeys(keyValues, bidderSettings.ownSettingsFor(bidderCode), custBidObj, bidRequest); custBidObj.sendStandardTargeting = bidderSettings.get(bidderCode, 'sendStandardTargeting'); } @@ -920,7 +918,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, {index = aucti } function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { - var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + var targeting = bidderSettings[JSON_MAPPING.ADSERVER_TARGETING]; custBidObj.size = custBidObj.getSize(); (targeting || []).forEach(function (kvPair) { @@ -941,7 +939,7 @@ function setKeys(keyValues, bidderSettings, custBidObj, bidReq) { if ( ((typeof bidderSettings.suppressEmptyKeys !== 'undefined' && bidderSettings.suppressEmptyKeys === true) || - key === CONSTANTS.TARGETING_KEYS.DEAL || key === CONSTANTS.TARGETING_KEYS.ACAT || key === CONSTANTS.TARGETING_KEYS.DSP || key === CONSTANTS.TARGETING_KEYS.CRID) && // hb_deal & hb_acat are suppressed automatically if not set + key === TARGETING_KEYS.DEAL || key === TARGETING_KEYS.ACAT || key === TARGETING_KEYS.DSP || key === TARGETING_KEYS.CRID) && // hb_deal & hb_acat are suppressed automatically if not set ( isEmptyStr(value) || value === null || diff --git a/src/auctionManager.js b/src/auctionManager.js index 2d6e0ffbfd9..a1ab1a859da 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -23,7 +23,7 @@ import { uniques, logWarn } from './utils.js'; import { newAuction, getStandardBidderSettings, AUCTION_COMPLETED } from './auction.js'; import {AuctionIndex} from './auctionIndex.js'; -import CONSTANTS from './constants.json'; +import { BID_STATUS, JSON_MAPPING } from './constants.js'; import {useMetrics} from './utils/perfMetrics.js'; import {ttlCollection} from './utils/ttlCollection.js'; import {getTTL, onTTLBufferChange} from './bidTTL.js'; @@ -77,7 +77,7 @@ export function newAuctionManager() { metrics.timeBetween('requestBids', 'bidWon', 'render.e2e'); const auction = getAuction(bid.auctionId); if (auction) { - bid.status = CONSTANTS.BID_STATUS.RENDERED; + bid.status = BID_STATUS.RENDERED; auction.addWinningBid(bid); } else { logWarn(`Auction not found when adding winning bid`); @@ -134,14 +134,14 @@ export function newAuctionManager() { }; auctionManager.getStandardBidderAdServerTargeting = function() { - return getStandardBidderSettings()[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + return getStandardBidderSettings()[JSON_MAPPING.ADSERVER_TARGETING]; }; auctionManager.setStatusForBids = function(adId, status) { let bid = auctionManager.findBidByAdId(adId); if (bid) bid.status = status; - if (bid && status === CONSTANTS.BID_STATUS.BID_TARGETING_SET) { + if (bid && status === BID_STATUS.BID_TARGETING_SET) { const auction = getAuction(bid.auctionId); if (auction) auction.setBidTargeting(bid); } diff --git a/src/bidderSettings.js b/src/bidderSettings.js index b39bf480511..4d97ed2b95e 100644 --- a/src/bidderSettings.js +++ b/src/bidderSettings.js @@ -1,6 +1,6 @@ import {deepAccess, mergeDeep} from './utils.js'; import {getGlobal} from './prebidGlobal.js'; -import CONSTANTS from './constants.json'; +import { JSON_MAPPING } from './constants.js'; export class ScopedSettings { constructor(getSettings, defaultScope) { @@ -65,4 +65,4 @@ export class ScopedSettings { } } -export const bidderSettings = new ScopedSettings(() => getGlobal().bidderSettings || {}, CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD); +export const bidderSettings = new ScopedSettings(() => getGlobal().bidderSettings || {}, JSON_MAPPING.BD_SETTING_STANDARD); diff --git a/src/config.js b/src/config.js index e3bb5f146ed..f4dd0de9612 100644 --- a/src/config.js +++ b/src/config.js @@ -27,9 +27,9 @@ import { logWarn, mergeDeep } from './utils.js'; -import CONSTANTS from './constants.json'; +import {DEBUG_MODE} from './constants.js'; -const DEFAULT_DEBUG = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; +const DEFAULT_DEBUG = getParameterByName(DEBUG_MODE).toUpperCase() === 'TRUE'; const DEFAULT_BIDDER_TIMEOUT = 3000; const DEFAULT_ENABLE_SEND_ALL_BIDS = true; const DEFAULT_DISABLE_AJAX_TIMEOUT = false; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000000..b40b7ddb9b0 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,190 @@ +export const JSON_MAPPING = { + PL_CODE: 'code', + PL_SIZE: 'sizes', + PL_BIDS: 'bids', + BD_BIDDER: 'bidder', + BD_ID: 'paramsd', + BD_PL_ID: 'placementId', + ADSERVER_TARGETING: 'adserverTargeting', + BD_SETTING_STANDARD: 'standard' +}; + +export const DEBUG_MODE = 'pbjs_debug'; + +export const STATUS = { + GOOD: 1 +}; + +export const EVENTS = { + AUCTION_INIT: 'auctionInit', + AUCTION_TIMEOUT: 'auctionTimeout', + AUCTION_END: 'auctionEnd', + BID_ADJUSTMENT: 'bidAdjustment', + BID_TIMEOUT: 'bidTimeout', + BID_REQUESTED: 'bidRequested', + BID_RESPONSE: 'bidResponse', + BID_REJECTED: 'bidRejected', + NO_BID: 'noBid', + SEAT_NON_BID: 'seatNonBid', + BID_WON: 'bidWon', + BIDDER_DONE: 'bidderDone', + BIDDER_ERROR: 'bidderError', + SET_TARGETING: 'setTargeting', + BEFORE_REQUEST_BIDS: 'beforeRequestBids', + BEFORE_BIDDER_HTTP: 'beforeBidderHttp', + REQUEST_BIDS: 'requestBids', + ADD_AD_UNITS: 'addAdUnits', + AD_RENDER_FAILED: 'adRenderFailed', + AD_RENDER_SUCCEEDED: 'adRenderSucceeded', + TCF2_ENFORCEMENT: 'tcf2Enforcement', + AUCTION_DEBUG: 'auctionDebug', + BID_VIEWABLE: 'bidViewable', + STALE_RENDER: 'staleRender', + BILLABLE_EVENT: 'billableEvent', + BID_ACCEPTED: 'bidAccepted' +}; + +export const AD_RENDER_FAILED_REASON = { + PREVENT_WRITING_ON_MAIN_DOCUMENT: 'preventWritingOnMainDocument', + NO_AD: 'noAd', + EXCEPTION: 'exception', + CANNOT_FIND_AD: 'cannotFindAd', + MISSING_DOC_OR_ADID: 'missingDocOrAdid' +}; + +export const EVENT_ID_PATHS = { + bidWon: 'adUnitCode' +}; + +export const GRANULARITY_OPTIONS = { + LOW: 'low', + MEDIUM: 'medium', + HIGH: 'high', + AUTO: 'auto', + DENSE: 'dense', + CUSTOM: 'custom' +}; + +export const TARGETING_KEYS = { + BIDDER: 'hb_bidder', + AD_ID: 'hb_adid', + PRICE_BUCKET: 'hb_pb', + SIZE: 'hb_size', + DEAL: 'hb_deal', + SOURCE: 'hb_source', + FORMAT: 'hb_format', + UUID: 'hb_uuid', + CACHE_ID: 'hb_cache_id', + CACHE_HOST: 'hb_cache_host', + ADOMAIN: 'hb_adomain', + ACAT: 'hb_acat', + CRID: 'hb_crid', + DSP: 'hb_dsp' +}; + +export const DEFAULT_TARGETING_KEYS = { + BIDDER: 'hb_bidder', + AD_ID: 'hb_adid', + PRICE_BUCKET: 'hb_pb', + SIZE: 'hb_size', + DEAL: 'hb_deal', + FORMAT: 'hb_format', + UUID: 'hb_uuid', + CACHE_HOST: 'hb_cache_host' +}; + +export const NATIVE_KEYS = { + title: 'hb_native_title', + body: 'hb_native_body', + body2: 'hb_native_body2', + privacyLink: 'hb_native_privacy', + privacyIcon: 'hb_native_privicon', + sponsoredBy: 'hb_native_brand', + image: 'hb_native_image', + icon: 'hb_native_icon', + clickUrl: 'hb_native_linkurl', + displayUrl: 'hb_native_displayurl', + cta: 'hb_native_cta', + rating: 'hb_native_rating', + address: 'hb_native_address', + downloads: 'hb_native_downloads', + likes: 'hb_native_likes', + phone: 'hb_native_phone', + price: 'hb_native_price', + salePrice: 'hb_native_saleprice', + rendererUrl: 'hb_renderer_url', + adTemplate: 'hb_adTemplate' +}; + +export const S2S = { + SRC: 's2s', + DEFAULT_ENDPOINT: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', + SYNCED_BIDDERS_KEY: 'pbjsSyncs' +}; + +export const BID_STATUS = { + BID_TARGETING_SET: 'targetingSet', + RENDERED: 'rendered', + BID_REJECTED: 'bidRejected' +}; + +export const REJECTION_REASON = { + INVALID: 'Bid has missing or invalid properties', + INVALID_REQUEST_ID: 'Invalid request ID', + BIDDER_DISALLOWED: 'Bidder code is not allowed by allowedAlternateBidderCodes / allowUnknownBidderCodes', + FLOOR_NOT_MET: 'Bid does not meet price floor', + CANNOT_CONVERT_CURRENCY: 'Unable to convert currency', + DSA_REQUIRED: 'Bid does not provide required DSA transparency info', + DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method' +}; + +export const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { + body: 'desc', + body2: 'desc2', + sponsoredBy: 'sponsored', + cta: 'ctatext', + rating: 'rating', + address: 'address', + downloads: 'downloads', + likes: 'likes', + phone: 'phone', + price: 'price', + salePrice: 'saleprice', + displayUrl: 'displayurl' +}; + +export const NATIVE_ASSET_TYPES = { + sponsored: 1, + desc: 2, + rating: 3, + likes: 4, + downloads: 5, + price: 6, + saleprice: 7, + phone: 8, + address: 9, + desc2: 10, + displayurl: 11, + ctatext: 12 +}; + +export const NATIVE_IMAGE_TYPES = { + ICON: 1, + MAIN: 3 +}; + +export const NATIVE_KEYS_THAT_ARE_NOT_ASSETS = [ + 'privacyIcon', + 'clickUrl', + 'sendTargetingKeys', + 'adTemplate', + 'rendererUrl', + 'type' +]; + +export const MESSAGES = { + REQUEST: 'Prebid Request', + RESPONSE: 'Prebid Response', + NATIVE: 'Prebid Native', + EVENT: 'Prebid Event' +}; diff --git a/src/constants.json b/src/constants.json deleted file mode 100644 index ceac779a508..00000000000 --- a/src/constants.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "JSON_MAPPING": { - "PL_CODE": "code", - "PL_SIZE": "sizes", - "PL_BIDS": "bids", - "BD_BIDDER": "bidder", - "BD_ID": "paramsd", - "BD_PL_ID": "placementId", - "ADSERVER_TARGETING": "adserverTargeting", - "BD_SETTING_STANDARD": "standard" - }, - "FLOOR_SKIPPED_REASON": { - "NOT_FOUND": "not_found", - "RANDOM": "random" - }, - "DEBUG_MODE": "pbjs_debug", - "STATUS": { - "GOOD": 1 - }, - "CB": { - "TYPE": { - "ALL_BIDS_BACK": "allRequestedBidsBack", - "AD_UNIT_BIDS_BACK": "adUnitBidsBack", - "BID_WON": "bidWon", - "REQUEST_BIDS": "requestBids" - } - }, - "EVENTS": { - "AUCTION_INIT": "auctionInit", - "AUCTION_TIMEOUT": "auctionTimeout", - "AUCTION_END": "auctionEnd", - "BID_ADJUSTMENT": "bidAdjustment", - "BID_TIMEOUT": "bidTimeout", - "BID_REQUESTED": "bidRequested", - "BID_RESPONSE": "bidResponse", - "BID_REJECTED": "bidRejected", - "NO_BID": "noBid", - "SEAT_NON_BID": "seatNonBid", - "BID_WON": "bidWon", - "BIDDER_DONE": "bidderDone", - "BIDDER_ERROR": "bidderError", - "SET_TARGETING": "setTargeting", - "BEFORE_REQUEST_BIDS": "beforeRequestBids", - "BEFORE_BIDDER_HTTP": "beforeBidderHttp", - "REQUEST_BIDS": "requestBids", - "ADD_AD_UNITS": "addAdUnits", - "AD_RENDER_FAILED": "adRenderFailed", - "AD_RENDER_SUCCEEDED": "adRenderSucceeded", - "TCF2_ENFORCEMENT": "tcf2Enforcement", - "AUCTION_DEBUG": "auctionDebug", - "BID_VIEWABLE": "bidViewable", - "STALE_RENDER": "staleRender", - "BILLABLE_EVENT": "billableEvent", - "BID_ACCEPTED": "bidAccepted" - }, - "AD_RENDER_FAILED_REASON": { - "PREVENT_WRITING_ON_MAIN_DOCUMENT": "preventWritingOnMainDocument", - "NO_AD": "noAd", - "EXCEPTION": "exception", - "CANNOT_FIND_AD": "cannotFindAd", - "MISSING_DOC_OR_ADID": "missingDocOrAdid" - }, - "EVENT_ID_PATHS": { - "bidWon": "adUnitCode" - }, - "GRANULARITY_OPTIONS": { - "LOW": "low", - "MEDIUM": "medium", - "HIGH": "high", - "AUTO": "auto", - "DENSE": "dense", - "CUSTOM": "custom" - }, - "TARGETING_KEYS": { - "BIDDER": "hb_bidder", - "AD_ID": "hb_adid", - "PRICE_BUCKET": "hb_pb", - "SIZE": "hb_size", - "DEAL": "hb_deal", - "SOURCE": "hb_source", - "FORMAT": "hb_format", - "UUID": "hb_uuid", - "CACHE_ID": "hb_cache_id", - "CACHE_HOST": "hb_cache_host", - "ADOMAIN": "hb_adomain", - "ACAT": "hb_acat", - "CRID": "hb_crid", - "DSP": "hb_dsp" - }, - "DEFAULT_TARGETING_KEYS": { - "BIDDER": "hb_bidder", - "AD_ID": "hb_adid", - "PRICE_BUCKET": "hb_pb", - "SIZE": "hb_size", - "DEAL": "hb_deal", - "FORMAT": "hb_format", - "UUID": "hb_uuid", - "CACHE_HOST": "hb_cache_host" - }, - "NATIVE_KEYS": { - "title": "hb_native_title", - "body": "hb_native_body", - "body2": "hb_native_body2", - "privacyLink": "hb_native_privacy", - "privacyIcon": "hb_native_privicon", - "sponsoredBy": "hb_native_brand", - "image": "hb_native_image", - "icon": "hb_native_icon", - "clickUrl": "hb_native_linkurl", - "displayUrl": "hb_native_displayurl", - "cta": "hb_native_cta", - "rating": "hb_native_rating", - "address": "hb_native_address", - "downloads": "hb_native_downloads", - "likes": "hb_native_likes", - "phone": "hb_native_phone", - "price": "hb_native_price", - "salePrice": "hb_native_saleprice", - "rendererUrl": "hb_renderer_url", - "adTemplate": "hb_adTemplate" - }, - "S2S": { - "SRC": "s2s", - "DEFAULT_ENDPOINT": "https://prebid.adnxs.com/pbs/v1/openrtb2/auction", - "SYNCED_BIDDERS_KEY": "pbjsSyncs" - }, - "BID_STATUS": { - "BID_TARGETING_SET": "targetingSet", - "RENDERED": "rendered", - "BID_REJECTED": "bidRejected" - }, - "REJECTION_REASON": { - "INVALID": "Bid has missing or invalid properties", - "INVALID_REQUEST_ID": "Invalid request ID", - "BIDDER_DISALLOWED": "Bidder code is not allowed by allowedAlternateBidderCodes / allowUnknownBidderCodes", - "FLOOR_NOT_MET": "Bid does not meet price floor", - "CANNOT_CONVERT_CURRENCY": "Unable to convert currency", - "DSA_REQUIRED": "Bid does not provide required DSA transparency info", - "DSA_MISMATCH": "Bid indicates inappropriate DSA rendering method" - }, - "PREBID_NATIVE_DATA_KEYS_TO_ORTB": { - "body": "desc", - "body2": "desc2", - "sponsoredBy": "sponsored", - "cta": "ctatext", - "rating": "rating", - "address": "address", - "downloads": "downloads", - "likes": "likes", - "phone": "phone", - "price": "price", - "salePrice": "saleprice", - "displayUrl": "displayurl" - }, - "NATIVE_ASSET_TYPES": { - "sponsored": 1, - "desc": 2, - "rating": 3, - "likes": 4, - "downloads": 5, - "price": 6, - "saleprice": 7, - "phone": 8, - "address": 9, - "desc2": 10, - "displayurl": 11, - "ctatext": 12 - }, - "NATIVE_IMAGE_TYPES": { - "ICON": 1, - "MAIN": 3 - }, - "NATIVE_KEYS_THAT_ARE_NOT_ASSETS": [ - "privacyIcon", - "clickUrl", - "sendTargetingKeys", - "adTemplate", - "rendererUrl", - "type" - ], - "FLOOR_VALUES": { - "NO_DATA": "noData", - "AD_UNIT": "adUnit", - "SET_CONFIG": "setConfig", - "FETCH": "fetch", - "SUCCESS": "success", - "ERROR": "error", - "TIMEOUT": "timeout" - }, - "MESSAGES": { - "REQUEST": "Prebid Request", - "RESPONSE": "Prebid Response", - "NATIVE": "Prebid Native", - "EVENT": "Prebid Event" - } -} diff --git a/src/events.js b/src/events.js index d98991180bf..38e7f633d16 100644 --- a/src/events.js +++ b/src/events.js @@ -2,7 +2,7 @@ * events.js */ import * as utils from './utils.js' -import CONSTANTS from './constants.json'; +import { EVENTS, EVENT_ID_PATHS } from './constants.js'; import {ttlCollection} from './utils/ttlCollection.js'; import {config} from './config.js'; const TTL_CONFIG = 'eventHistoryTTL'; @@ -28,9 +28,9 @@ let slice = Array.prototype.slice; let push = Array.prototype.push; // define entire events -let allEvents = Object.values(CONSTANTS.EVENTS); +let allEvents = Object.values(EVENTS); -const idPaths = CONSTANTS.EVENT_ID_PATHS; +const idPaths = EVENT_ID_PATHS; const _public = (function () { let _handlers = {}; diff --git a/src/native.js b/src/native.js index 1b6e13c77fc..f001200000d 100644 --- a/src/native.js +++ b/src/native.js @@ -13,7 +13,7 @@ import { } from './utils.js'; import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; -import CONSTANTS from './constants.json'; +import {NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS} from './constants.js'; import {NATIVE} from './mediaTypes.js'; /** @@ -23,8 +23,8 @@ import {NATIVE} from './mediaTypes.js'; export const nativeAdapters = []; -export const NATIVE_TARGETING_KEYS = Object.keys(CONSTANTS.NATIVE_KEYS).map( - key => CONSTANTS.NATIVE_KEYS[key] +export const NATIVE_TARGETING_KEYS = Object.keys(NATIVE_KEYS).map( + key => NATIVE_KEYS[key] ); export const IMAGE = { @@ -84,8 +84,6 @@ const SUPPORTED_TYPES = { image: IMAGE }; -const { NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS } = CONSTANTS; - // inverse native maps useful for converting to legacy const PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE = inverse(PREBID_NATIVE_DATA_KEYS_TO_ORTB); const NATIVE_ASSET_TYPES_INVERSE = inverse(NATIVE_ASSET_TYPES); @@ -425,7 +423,7 @@ export function getNativeRenderingData(bid, adUnit, keys) { const data = { ...getDefinedParams(bid.native, ['rendererUrl', 'adTemplate']), assets: getNativeAssets(bid.native, keys), - nativeKeys: CONSTANTS.NATIVE_KEYS + nativeKeys: NATIVE_KEYS }; if (bid.native.ortb) { data.ortb = bid.native.ortb; @@ -443,7 +441,7 @@ function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {} }; } -const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(CONSTANTS.NATIVE_KEYS).map(([k, v]) => [v, k])); +const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(NATIVE_KEYS).map(([k, v]) => [v, k])); /** * Constructs a message object containing asset values for each of the @@ -476,7 +474,7 @@ function getNativeKeys(adUnit) { } return { - ...CONSTANTS.NATIVE_KEYS, + ...NATIVE_KEYS, ...extraNativeKeys } } diff --git a/src/prebid.js b/src/prebid.js index 750a4bdee1a..e96c0b36c9f 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -34,7 +34,7 @@ import {adunitCounter} from './adUnits.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; import {default as adapterManager, getS2SBidderSet} from './adapterManager.js'; -import CONSTANTS from './constants.json'; +import { BID_STATUS, EVENTS, NATIVE_KEYS } from './constants.js'; import * as events from './events.js'; import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; @@ -48,7 +48,7 @@ const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; /* private variables */ -const { ADD_AD_UNITS, REQUEST_BIDS, SET_TARGETING } = CONSTANTS.EVENTS; +const { ADD_AD_UNITS, REQUEST_BIDS, SET_TARGETING } = EVENTS; const eventValidators = { bidWon: checkDefinedPlacement @@ -143,7 +143,7 @@ function validateNativeMediaType(adUnit) { const native = validatedAdUnit.mediaTypes.native; // if native assets are specified in OpenRTB format, remove legacy assets and print a warn. if (native.ortb) { - const legacyNativeKeys = Object.keys(CONSTANTS.NATIVE_KEYS).filter(key => CONSTANTS.NATIVE_KEYS[key].includes('hb_native_')); + const legacyNativeKeys = Object.keys(NATIVE_KEYS).filter(key => NATIVE_KEYS[key].includes('hb_native_')); const nativeKeys = Object.keys(native); const intersection = nativeKeys.filter(nativeKey => legacyNativeKeys.includes(nativeKey)); if (intersection.length > 0) { @@ -173,7 +173,7 @@ function validateAdUnitPos(adUnit, mediaType) { let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`; logWarn(warning); - events.emit(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: warning}); + events.emit(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: warning }); delete adUnit.mediaTypes[mediaType].pos; } @@ -416,7 +416,7 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { Object.keys(targetingSet).forEach((adUnitCode) => { Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => { if (targetingKey === 'hb_adid') { - auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], CONSTANTS.BID_STATUS.BID_TARGETING_SET); + auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET); } }); }); @@ -857,7 +857,7 @@ pbjsInstance.getAllWinningBids = function () { */ pbjsInstance.getAllPrebidWinningBids = function () { return auctionManager.getBidsReceived() - .filter(bid => bid.status === CONSTANTS.BID_STATUS.BID_TARGETING_SET); + .filter(bid => bid.status === BID_STATUS.BID_TARGETING_SET); }; /** diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 1880f56f474..96ace0792e4 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -5,16 +5,16 @@ import * as events from './events.js'; import {getAllAssetsMessage, getAssetMessage} from './native.js'; -import CONSTANTS from './constants.json'; +import { BID_STATUS, EVENTS, MESSAGES } from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; import {handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; -const {REQUEST, RESPONSE, NATIVE, EVENT} = CONSTANTS.MESSAGES; +const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; -const BID_WON = CONSTANTS.EVENTS.BID_WON; +const BID_WON = EVENTS.BID_WON; const HANDLER_MAP = { [REQUEST]: handleRenderRequest, @@ -99,7 +99,7 @@ function handleNativeRequest(reply, data, adObject) { return; } - if (adObject.status !== CONSTANTS.BID_STATUS.RENDERED) { + if (adObject.status !== BID_STATUS.RENDERED) { auctionManager.addWinningBid(adObject); events.emit(BID_WON, adObject); } @@ -121,7 +121,7 @@ function handleEventRequest(reply, data, adObject) { logError(`Cannot find ad '${data.adId}' for x-origin event request`); return; } - if (adObject.status !== CONSTANTS.BID_STATUS.RENDERED) { + if (adObject.status !== BID_STATUS.RENDERED) { logWarn(`Received x-origin event request without corresponding render request for ad '${adObject.adId}'`); return; } diff --git a/src/targeting.js b/src/targeting.js index ddbc3cebaf3..acb3ddb09ff 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -21,7 +21,7 @@ import {ADPOD} from './mediaTypes.js'; import {hook} from './hook.js'; import {bidderSettings} from './bidderSettings.js'; import {find, includes} from './polyfill.js'; -import CONSTANTS from './constants.json'; +import { BID_STATUS, JSON_MAPPING, DEFAULT_TARGETING_KEYS, TARGETING_KEYS, NATIVE_KEYS, STATUS } from './constants.js'; import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; import {getTTL} from './bidTTL.js'; @@ -33,19 +33,19 @@ const CFG_ALLOW_TARGETING_KEYS = `targetingControls.allowTargetingKeys`; const CFG_ADD_TARGETING_KEYS = `targetingControls.addTargetingKeys`; const TARGETING_KEY_CONFIGURATION_ERROR_MSG = `Only one of "${CFG_ALLOW_TARGETING_KEYS}" or "${CFG_ADD_TARGETING_KEYS}" can be set`; -export const TARGETING_KEYS = Object.keys(CONSTANTS.TARGETING_KEYS).map( - key => CONSTANTS.TARGETING_KEYS[key] +export const TARGETING_KEYS_ARR = Object.keys(TARGETING_KEYS).map( + key => TARGETING_KEYS[key] ); // return unexpired bids const isBidNotExpired = (bid) => (bid.responseTimestamp + getTTL(bid) * 1000) > timestamp(); // return bids whose status is not set. Winning bids can only have a status of `rendered`. -const isUnusedBid = (bid) => bid && ((bid.status && !includes([CONSTANTS.BID_STATUS.RENDERED], bid.status)) || !bid.status); +const isUnusedBid = (bid) => bid && ((bid.status && !includes([BID_STATUS.RENDERED], bid.status)) || !bid.status); export let filters = { isActualBid(bid) { - return bid.getStatusCode() === CONSTANTS.STATUS.GOOD + return bid.getStatusCode() === STATUS.GOOD }, isBidNotExpired, isUnusedBid @@ -195,7 +195,7 @@ export function newTargeting(auctionManager) { */ function getDealBids(adUnitCodes, bidsReceived) { if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); // we only want the top bid from bidders who have multiple entries per ad unit code const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm); @@ -221,7 +221,7 @@ export function newTargeting(auctionManager) { * @return {targetingArray} filtered targeting */ function getAllowedTargetingKeyValues(targeting, allowedKeys) { - const defaultKeyring = Object.assign({}, CONSTANTS.TARGETING_KEYS, CONSTANTS.NATIVE_KEYS); + const defaultKeyring = Object.assign({}, TARGETING_KEYS, NATIVE_KEYS); const defaultKeys = Object.keys(defaultKeyring); const keyDispositions = {}; logInfo(`allowTargetingKeys - allowed keys [ ${allowedKeys.map(k => defaultKeyring[k]).join(', ')} ]`); @@ -282,7 +282,7 @@ export function newTargeting(auctionManager) { }); }); - const defaultKeys = Object.keys(Object.assign({}, CONSTANTS.DEFAULT_TARGETING_KEYS, CONSTANTS.NATIVE_KEYS)); + const defaultKeys = Object.keys(Object.assign({}, DEFAULT_TARGETING_KEYS, NATIVE_KEYS)); let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); const addedKeys = config.getConfig(CFG_ADD_TARGETING_KEYS); @@ -547,7 +547,7 @@ export function newTargeting(auctionManager) { .reduce((acc, key) => { const targetingValue = [winner.adserverTargeting[key]]; const targeting = { [key.substring(0, MAX_DFP_KEYLENGTH)]: targetingValue }; - if (key === CONSTANTS.TARGETING_KEYS.DEAL) { + if (key === TARGETING_KEYS.DEAL) { const bidderCodeTargetingKey = `${key}_${winner.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH); const bidderCodeTargeting = { [bidderCodeTargetingKey]: targetingValue }; return [...acc, targeting, bidderCodeTargeting]; @@ -563,7 +563,7 @@ export function newTargeting(auctionManager) { function getStandardKeys() { return auctionManager.getStandardBidderAdServerTargeting() // in case using a custom standard key set .map(targeting => targeting.key) - .concat(TARGETING_KEYS).filter(uniques); // standard keys defined in the library. + .concat(TARGETING_KEYS_ARR).filter(uniques); // standard keys defined in the library. } /** @@ -648,13 +648,13 @@ export function newTargeting(auctionManager) { * @return {targetingArray} all non-winning bids targeting */ function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS.slice(); + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys - ? allowSendAllBidsTargetingKeys.map((key) => CONSTANTS.TARGETING_KEYS[key]) + ? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key]) : standardKeys; // populate targeting keys for the remaining bids @@ -680,7 +680,7 @@ export function newTargeting(auctionManager) { function getAdUnitTargeting(adUnitCodes) { function getTargetingObj(adUnit) { - return deepAccess(adUnit, CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING); + return deepAccess(adUnit, JSON_MAPPING.ADSERVER_TARGETING); } function getTargetingValues(adUnit) { diff --git a/src/utils.js b/src/utils.js index c7ce5f22f9a..71cc78090a6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,7 +1,7 @@ import {config} from './config.js'; import clone from 'just-clone'; import {includes} from './polyfill.js'; -import CONSTANTS from './constants.json'; +import { EVENTS, S2S } from './constants.js'; import {GreedyPromise} from './utils/promise.js'; import {getGlobal} from './prebidGlobal.js'; @@ -202,7 +202,7 @@ export function logWarn() { // eslint-disable-next-line no-console console.warn.apply(console, decorateLog(arguments, 'WARNING:')); } - emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'WARNING', arguments: arguments}); + emitEvent(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: arguments }); } export function logError() { @@ -210,7 +210,7 @@ export function logError() { // eslint-disable-next-line no-console console.error.apply(console, decorateLog(arguments, 'ERROR:')); } - emitEvent(CONSTANTS.EVENTS.AUCTION_DEBUG, {type: 'ERROR', arguments: arguments}); + emitEvent(EVENTS.AUCTION_DEBUG, { type: 'ERROR', arguments: arguments }); } export function prefixLog(prefix) { @@ -444,7 +444,7 @@ export function triggerPixel(url, done, timeout) { } export function callBurl({ source, burl }) { - if (source === CONSTANTS.S2S.SRC && burl) { + if (source === S2S.SRC && burl) { internal.triggerPixel(burl); } } diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index 7317ea039d1..fb6cfe036e5 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -1,17 +1,17 @@ // jscs:disable -import CONSTANTS from 'src/constants.json'; +import { TARGETING_KEYS, STATUS } from 'src/constants.js'; import {createBid} from '../../src/bidfactory.js'; const utils = require('src/utils.js'); function convertTargetingsFromOldToNew(targetings) { var mapOfOldToNew = { - 'hb_bidder': CONSTANTS.TARGETING_KEYS.BIDDER, - 'hb_adid': CONSTANTS.TARGETING_KEYS.AD_ID, - 'hb_pb': CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, - 'hb_size': CONSTANTS.TARGETING_KEYS.SIZE, - 'hb_deal': CONSTANTS.TARGETING_KEYS.DEAL, - 'hb_source': CONSTANTS.TARGETING_KEYS.SOURCE, - 'hb_format': CONSTANTS.TARGETING_KEYS.FORMAT + 'hb_bidder': TARGETING_KEYS.BIDDER, + 'hb_adid': TARGETING_KEYS.AD_ID, + 'hb_pb': TARGETING_KEYS.PRICE_BUCKET, + 'hb_size': TARGETING_KEYS.SIZE, + 'hb_deal': TARGETING_KEYS.DEAL, + 'hb_source': TARGETING_KEYS.SOURCE, + 'hb_format': TARGETING_KEYS.FORMAT }; var newTargetings = {}; utils._each(targetings, function(value, currentKey) { @@ -1018,19 +1018,19 @@ export function getAdServerTargeting() { export function getTargetingKeys() { return [ [ - CONSTANTS.TARGETING_KEYS.BIDDER, + TARGETING_KEYS.BIDDER, 'appnexus' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID, + TARGETING_KEYS.AD_ID, '233bcbee889d46d' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE, + TARGETING_KEYS.SIZE, '300x250' ], [ @@ -1045,19 +1045,19 @@ export function getTargetingKeys() { export function getTargetingKeysBidLandscape() { return [ [ - CONSTANTS.TARGETING_KEYS.BIDDER, + TARGETING_KEYS.BIDDER, 'appnexus' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', + TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE, + TARGETING_KEYS.SIZE, '300x250' ], [ @@ -1065,111 +1065,111 @@ export function getTargetingKeysBidLandscape() { ['0x0', '300x250', '300x600'] ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift', + TARGETING_KEYS.BIDDER + '_triplelift', 'triplelift' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift', + TARGETING_KEYS.AD_ID + '_triplelift', '222bb26f9e8bd' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift', + TARGETING_KEYS.PRICE_BUCKET + '_triplelift', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift', + TARGETING_KEYS.SIZE + '_triplelift', '0x0' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus', + TARGETING_KEYS.BIDDER + '_appnexus', 'appnexus' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', + TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus', + TARGETING_KEYS.SIZE + '_appnexus', '300x250' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc', + TARGETING_KEYS.BIDDER + '_pagescienc', 'pagescience' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience', + TARGETING_KEYS.AD_ID + '_pagescience', '25bedd4813632d7' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience', + TARGETING_KEYS.PRICE_BUCKET + '_pagescience', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience', + TARGETING_KEYS.SIZE + '_pagescience', '300x250' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom', + TARGETING_KEYS.BIDDER + '_brightcom', 'brightcom' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom', + TARGETING_KEYS.AD_ID + '_brightcom', '26e0795ab963896' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom', + TARGETING_KEYS.PRICE_BUCKET + '_brightcom', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom', + TARGETING_KEYS.SIZE + '_brightcom', '300x250' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime', + TARGETING_KEYS.BIDDER + '_brealtime', 'brealtime' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime', + TARGETING_KEYS.AD_ID + '_brealtime', '275bd666f5a5a5d' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime', + TARGETING_KEYS.PRICE_BUCKET + '_brealtime', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime', + TARGETING_KEYS.SIZE + '_brealtime', '300x250' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic', + TARGETING_KEYS.BIDDER + '_pubmatic', 'pubmatic' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic', + TARGETING_KEYS.AD_ID + '_pubmatic', '28f4039c636b6a7' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic', + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic', + TARGETING_KEYS.SIZE + '_pubmatic', '300x250' ], [ - CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon', + TARGETING_KEYS.BIDDER + '_rubicon', 'rubicon' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon', + TARGETING_KEYS.AD_ID + '_rubicon', '29019e2ab586a5a' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon', + TARGETING_KEYS.PRICE_BUCKET + '_rubicon', '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon', + TARGETING_KEYS.SIZE + '_rubicon', '300x600' ] ]; @@ -1262,7 +1262,7 @@ export function createBidReceived({bidder, cpm, auctionId, responseTimestamp, ad if (typeof status !== 'undefined') { bid.status = status; } - return Object.assign(createBid(CONSTANTS.STATUS.GOOD), bid); + return Object.assign(createBid(STATUS.GOOD), bid); } export function getServerTestingsAds() { diff --git a/test/helpers/analytics.js b/test/helpers/analytics.js index b376118dc6f..d36bcf44f64 100644 --- a/test/helpers/analytics.js +++ b/test/helpers/analytics.js @@ -1,12 +1,12 @@ import * as pbEvents from 'src/events.js'; -import constants from '../../src/constants.json'; +import { EVENTS } from '../../src/constants.js'; export function fireEvents(events = [ - constants.EVENTS.AUCTION_INIT, - constants.EVENTS.AUCTION_END, - constants.EVENTS.BID_REQUESTED, - constants.EVENTS.BID_RESPONSE, - constants.EVENTS.BID_WON + EVENTS.AUCTION_INIT, + EVENTS.AUCTION_END, + EVENTS.BID_REQUESTED, + EVENTS.BID_RESPONSE, + EVENTS.BID_WON ]) { return events.map((ev, i) => { ev = Array.isArray(ev) ? ev : [ev, {i: i}]; diff --git a/test/spec/AnalyticsAdapter_spec.js b/test/spec/AnalyticsAdapter_spec.js index 62c00e04403..e853fb72fa8 100644 --- a/test/spec/AnalyticsAdapter_spec.js +++ b/test/spec/AnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import {server} from 'test/mocks/xhr.js'; import {disableAjaxForAnalytics, enableAjaxForAnalytics} from '../mocks/analyticsStub.js'; import {clearEvents} from 'src/events.js'; @@ -10,8 +10,8 @@ import { setDebounceDelay } from '../../libraries/analyticsAdapter/AnalyticsAdapter.js'; -const BID_WON = CONSTANTS.EVENTS.BID_WON; -const NO_BID = CONSTANTS.EVENTS.NO_BID; +const BID_WON = EVENTS.BID_WON; +const NO_BID = EVENTS.NO_BID; const AnalyticsAdapter = require('libraries/analyticsAdapter/AnalyticsAdapter.js').default; const config = { diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 06d3d538596..65c6256acdc 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -7,7 +7,7 @@ import { getPriceByGranularity, addBidResponse, resetAuctionState, responsesReady } from 'src/auction.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS, TARGETING_KEYS, S2S } from 'src/constants.js'; import * as auctionModule from 'src/auction.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; import { createBid } from 'src/bidfactory.js'; @@ -110,8 +110,8 @@ function mockBidRequest(bid, opts) { 'auctionId': opts && opts.auctionId, 'bidderRequestId': requestId, src: bid?._ctx?.src, - adUnitsS2SCopy: bid?._ctx?.src === CONSTANTS.S2S.SRC ? bid?._ctx?.adUnits : undefined, - uniquePbsTid: bid?._ctx?.src === CONSTANTS.S2S.SRC ? bid?._ctx?.uniquePbsTid : undefined, + adUnitsS2SCopy: bid?._ctx?.src === S2S.SRC ? bid?._ctx?.adUnits : undefined, + uniquePbsTid: bid?._ctx?.src === S2S.SRC ? bid?._ctx?.uniquePbsTid : undefined, 'bids': [ { 'bidder': bidderCode || bid.bidderCode, @@ -215,20 +215,20 @@ describe('auctionmanager.js', function () { /* return the expected response for a given bid, filter by keys if given */ function getDefaultExpected(bid, keys) { var expected = {}; - expected[ CONSTANTS.TARGETING_KEYS.BIDDER ] = bid.bidderCode; - expected[ CONSTANTS.TARGETING_KEYS.AD_ID ] = bid.adId; - expected[ CONSTANTS.TARGETING_KEYS.PRICE_BUCKET ] = bid.pbMg; - expected[ CONSTANTS.TARGETING_KEYS.SIZE ] = bid.getSize(); - expected[ CONSTANTS.TARGETING_KEYS.SOURCE ] = bid.source; - expected[ CONSTANTS.TARGETING_KEYS.FORMAT ] = bid.mediaType; - expected[ CONSTANTS.TARGETING_KEYS.ADOMAIN ] = bid.meta.advertiserDomains[0]; - expected[ CONSTANTS.TARGETING_KEYS.ACAT ] = bid.meta.primaryCatId; - expected[ CONSTANTS.TARGETING_KEYS.DSP ] = bid.meta.networkId; - expected[ CONSTANTS.TARGETING_KEYS.CRID ] = bid.creativeId; + expected[TARGETING_KEYS.BIDDER] = bid.bidderCode; + expected[TARGETING_KEYS.AD_ID] = bid.adId; + expected[TARGETING_KEYS.PRICE_BUCKET] = bid.pbMg; + expected[TARGETING_KEYS.SIZE] = bid.getSize(); + expected[TARGETING_KEYS.SOURCE] = bid.source; + expected[TARGETING_KEYS.FORMAT] = bid.mediaType; + expected[TARGETING_KEYS.ADOMAIN] = bid.meta.advertiserDomains[0]; + expected[TARGETING_KEYS.ACAT] = bid.meta.primaryCatId; + expected[TARGETING_KEYS.DSP] = bid.meta.networkId; + expected[TARGETING_KEYS.CRID] = bid.creativeId; if (bid.mediaType === 'video') { - expected[ CONSTANTS.TARGETING_KEYS.UUID ] = bid.videoCacheKey; - expected[ CONSTANTS.TARGETING_KEYS.CACHE_ID ] = bid.videoCacheKey; - expected[ CONSTANTS.TARGETING_KEYS.CACHE_HOST ] = 'prebid.adnxs.com'; + expected[TARGETING_KEYS.UUID] = bid.videoCacheKey; + expected[TARGETING_KEYS.CACHE_ID] = bid.videoCacheKey; + expected[TARGETING_KEYS.CACHE_HOST] = 'prebid.adnxs.com'; } if (!keys) { return expected; @@ -288,59 +288,59 @@ describe('auctionmanager.js', function () { standard: { adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return bidResponse.pbHg; } }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, + key: TARGETING_KEYS.SIZE, val: function (bidResponse) { return bidResponse.size; } }, { - key: CONSTANTS.TARGETING_KEYS.SOURCE, + key: TARGETING_KEYS.SOURCE, val: function (bidResponse) { return bidResponse.source; } }, { - key: CONSTANTS.TARGETING_KEYS.FORMAT, + key: TARGETING_KEYS.FORMAT, val: function (bidResponse) { return bidResponse.mediaType; } }, { - key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + key: TARGETING_KEYS.ADOMAIN, val: function (bidResponse) { return bidResponse.meta.advertiserDomains[0]; } }, { - key: CONSTANTS.TARGETING_KEYS.CRID, + key: TARGETING_KEYS.CRID, val: function (bidResponse) { return bidResponse.creativeId; } }, { - key: CONSTANTS.TARGETING_KEYS.DSP, + key: TARGETING_KEYS.DSP, val: function (bidResponse) { return bidResponse.meta.networkId; } }, { - key: CONSTANTS.TARGETING_KEYS.ACAT, + key: TARGETING_KEYS.ACAT, val: function (bidResponse) { return bidResponse.meta.primaryCatId; } @@ -351,7 +351,7 @@ describe('auctionmanager.js', function () { }; var expected = getDefaultExpected(bid); - expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; + expected[TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); @@ -373,70 +373,70 @@ describe('auctionmanager.js', function () { standard: { adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { return bidResponse.pbMg; } }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, + key: TARGETING_KEYS.SIZE, val: function (bidResponse) { return bidResponse.size; } }, { - key: CONSTANTS.TARGETING_KEYS.SOURCE, + key: TARGETING_KEYS.SOURCE, val: function (bidResponse) { return bidResponse.source; } }, { - key: CONSTANTS.TARGETING_KEYS.FORMAT, + key: TARGETING_KEYS.FORMAT, val: function (bidResponse) { return bidResponse.mediaType; } }, { - key: CONSTANTS.TARGETING_KEYS.UUID, + key: TARGETING_KEYS.UUID, val: function (bidResponse) { return bidResponse.videoCacheKey; } }, { - key: CONSTANTS.TARGETING_KEYS.CACHE_ID, + key: TARGETING_KEYS.CACHE_ID, val: function (bidResponse) { return bidResponse.videoCacheKey; } }, { - key: CONSTANTS.TARGETING_KEYS.ADOMAIN, + key: TARGETING_KEYS.ADOMAIN, val: function (bidResponse) { return bidResponse.meta.advertiserDomains[0]; } }, { - key: CONSTANTS.TARGETING_KEYS.CRID, + key: TARGETING_KEYS.CRID, val: function (bidResponse) { return bidResponse.creativeId; } }, { - key: CONSTANTS.TARGETING_KEYS.DSP, + key: TARGETING_KEYS.DSP, val: function (bidResponse) { return bidResponse.meta.networkId; } }, { - key: CONSTANTS.TARGETING_KEYS.ACAT, + key: TARGETING_KEYS.ACAT, val: function (bidResponse) { return bidResponse.meta.primaryCatId; } @@ -459,23 +459,23 @@ describe('auctionmanager.js', function () { appnexus: { adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return bidResponse.pbHg; } }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, + key: TARGETING_KEYS.SIZE, val: function (bidResponse) { return bidResponse.size; } @@ -486,7 +486,7 @@ describe('auctionmanager.js', function () { }; var expected = getDefaultExpected(bid); - expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; + expected[TARGETING_KEYS.PRICE_BUCKET] = bid.pbHg; var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); @@ -498,23 +498,23 @@ describe('auctionmanager.js', function () { nonExistentBidder: { adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return bidResponse.pbHg; } }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, + key: TARGETING_KEYS.SIZE, val: function (bidResponse) { return bidResponse.size; } @@ -561,17 +561,17 @@ describe('auctionmanager.js', function () { }, adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return 10.00; @@ -581,8 +581,8 @@ describe('auctionmanager.js', function () { } }; - var expected = getDefaultExpected(bid, [CONSTANTS.TARGETING_KEYS.BIDDER, CONSTANTS.TARGETING_KEYS.AD_ID]); - expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 10.0; + var expected = getDefaultExpected(bid, [TARGETING_KEYS.BIDDER, TARGETING_KEYS.AD_ID]); + expected[TARGETING_KEYS.PRICE_BUCKET] = 10.0; var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); @@ -618,17 +618,17 @@ describe('auctionmanager.js', function () { }, adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return 15.00; @@ -639,24 +639,24 @@ describe('auctionmanager.js', function () { standard: { adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { // change default here return 10.00; }, }, { - key: CONSTANTS.TARGETING_KEYS.SIZE, + key: TARGETING_KEYS.SIZE, val: function (bidResponse) { return bidResponse.size; } @@ -665,8 +665,8 @@ describe('auctionmanager.js', function () { } }; - var expected = getDefaultExpected(bid, [CONSTANTS.TARGETING_KEYS.BIDDER, CONSTANTS.TARGETING_KEYS.AD_ID, CONSTANTS.TARGETING_KEYS.SIZE]); - expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 15.0; + var expected = getDefaultExpected(bid, [TARGETING_KEYS.BIDDER, TARGETING_KEYS.AD_ID, TARGETING_KEYS.SIZE]); + expected[TARGETING_KEYS.PRICE_BUCKET] = 15.0; var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); @@ -679,17 +679,17 @@ describe('auctionmanager.js', function () { sendStandardTargeting: false, adserverTargeting: [ { - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function (bidResponse) { return bidResponse.bidderCode; } }, { - key: CONSTANTS.TARGETING_KEYS.AD_ID, + key: TARGETING_KEYS.AD_ID, val: function (bidResponse) { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function (bidResponse) { return bidResponse.pbHg; } @@ -698,7 +698,7 @@ describe('auctionmanager.js', function () { } }; var expected = getDefaultExpected(bid); - expected[CONSTANTS.TARGETING_KEYS.PRICE_BUCKET] = 5.57; + expected[TARGETING_KEYS.PRICE_BUCKET] = 5.57; var response = getKeyValueTargetingPairs(bid.bidderCode, bid); assert.deepEqual(response, expected); @@ -830,7 +830,7 @@ describe('auctionmanager.js', function () { } const auction = auctionManager.createAuction({adUnits, ortb2Fragments}); expect(auction.getNonBids()[0]).to.equal(undefined); - events.emit(CONSTANTS.EVENTS.SEAT_NON_BID, { + events.emit(EVENTS.SEAT_NON_BID, { auctionId: auction.getAuctionId(), seatnonbid: ['test'] }); @@ -990,7 +990,7 @@ describe('auctionmanager.js', function () { auction.callBids(); let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.adserverTargeting[CONSTANTS.TARGETING_KEYS.DEAL], 'test deal', 'dealId placed in adserverTargeting'); + assert.equal(registeredBid.adserverTargeting[TARGETING_KEYS.DEAL], 'test deal', 'dealId placed in adserverTargeting'); }); it('should pass through default adserverTargeting sent from adapter', function () { @@ -999,7 +999,7 @@ describe('auctionmanager.js', function () { auction.callBids(); let registeredBid = auction.getBidsReceived().pop(); - assert.equal(registeredBid.adserverTargeting[CONSTANTS.TARGETING_KEYS.BIDDER], BIDDER_CODE); + assert.equal(registeredBid.adserverTargeting[TARGETING_KEYS.BIDDER], BIDDER_CODE); assert.equal(registeredBid.adserverTargeting.extra, 'stuff'); }); it('should add the bidResponse to the collection before calling BID_RESPONSE', function () { @@ -1008,9 +1008,9 @@ describe('auctionmanager.js', function () { const storedBid = auction.getBidsReceived().pop(); hasBid = storedBid === bid; } - events.on(CONSTANTS.EVENTS.BID_RESPONSE, eventHandler); + events.on(EVENTS.BID_RESPONSE, eventHandler); auction.callBids(); - events.off(CONSTANTS.EVENTS.BID_RESPONSE, eventHandler); + events.off(EVENTS.BID_RESPONSE, eventHandler); assert.ok(hasBid, 'Bid not available'); }); @@ -1231,10 +1231,10 @@ describe('auctionmanager.js', function () { let handler; beforeEach(() => { handler = sinon.spy(); - events.on(CONSTANTS.EVENTS.AUCTION_TIMEOUT, handler); + events.on(EVENTS.AUCTION_TIMEOUT, handler); }) afterEach(() => { - events.off(CONSTANTS.EVENTS.AUCTION_TIMEOUT, handler); + events.off(EVENTS.AUCTION_TIMEOUT, handler); }); Object.entries({ @@ -1257,14 +1257,14 @@ describe('auctionmanager.js', function () { it('should emit BID_TIMEOUT and AUCTION_END for timed out bids', function () { const pm = runAuction().then(() => { - const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0]; + const bidTimeoutCall = eventsEmitSpy.withArgs(EVENTS.BID_TIMEOUT).getCalls()[0]; const timedOutBids = bidTimeoutCall.args[1]; assert.equal(timedOutBids.length, 1); assert.equal(timedOutBids[0].bidder, BIDDER_CODE1); // Check that additional properties are available assert.equal(timedOutBids[0].params[0].placementId, 'id'); - const auctionEndCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.AUCTION_END).getCalls()[0]; + const auctionEndCall = eventsEmitSpy.withArgs(EVENTS.AUCTION_END).getCalls()[0]; const auctionProps = auctionEndCall.args[1]; assert.equal(auctionProps.adUnits, adUnits); assert.equal(auctionProps.timeout, 20); @@ -1276,7 +1276,7 @@ describe('auctionmanager.js', function () { it('should NOT emit BID_TIMEOUT when all bidders responded in time', function () { const pm = runAuction().then(() => { - assert.ok(eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).notCalled, 'did not emit event BID_TIMEOUT'); + assert.ok(eventsEmitSpy.withArgs(EVENTS.BID_TIMEOUT).notCalled, 'did not emit event BID_TIMEOUT'); }); respondToRequest(0); respondToRequest(1); @@ -1285,7 +1285,7 @@ describe('auctionmanager.js', function () { it('should NOT emit BID_TIMEOUT for bidders which responded in time but with an empty bid', function () { const pm = runAuction().then(() => { - const bidTimeoutCall = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0]; + const bidTimeoutCall = eventsEmitSpy.withArgs(EVENTS.BID_TIMEOUT).getCalls()[0]; const timedOutBids = bidTimeoutCall.args[1]; assert.equal(timedOutBids.length, 1); assert.equal(timedOutBids[0].bidder, BIDDER_CODE1); @@ -1314,8 +1314,8 @@ describe('auctionmanager.js', function () { adUnits[0].bids.push({bidder: 'mock-s2s-1'}, {bidder: 'mock-s2s-2'}) const s2sAdUnits = deepClone(adUnits); bids.unshift( - mockBid({bidderCode: 'mock-s2s-1', src: CONSTANTS.S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '1'}), - mockBid({bidderCode: 'mock-s2s-2', src: CONSTANTS.S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '2'}) + mockBid({ bidderCode: 'mock-s2s-1', src: S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '1' }), + mockBid({ bidderCode: 'mock-s2s-2', src: S2S.SRC, adUnits: s2sAdUnits, uniquePbsTid: '2' }) ); Object.assign(s2sAdUnits[0], { mediaTypes: { @@ -1336,7 +1336,7 @@ describe('auctionmanager.js', function () { }) const pm = runAuction().then(() => { - const toBids = eventsEmitSpy.withArgs(CONSTANTS.EVENTS.BID_TIMEOUT).getCalls()[0].args[1] + const toBids = eventsEmitSpy.withArgs(EVENTS.BID_TIMEOUT).getCalls()[0].args[1] expect(toBids.map(bid => bid.bidder)).to.eql([ 'mock-s2s-2', BIDDER_CODE, @@ -1906,12 +1906,12 @@ describe('auctionmanager.js', function () { before(() => { addBidResponse.before(rejectHook, 999); - events.on(CONSTANTS.EVENTS.BID_REJECTED, onBidRejected); + events.on(EVENTS.BID_REJECTED, onBidRejected); }); after(() => { addBidResponse.getHooks({hook: rejectHook}).remove(); - events.off(CONSTANTS.EVENTS.BID_REJECTED, onBidRejected); + events.off(EVENTS.BID_REJECTED, onBidRejected); }); beforeEach(() => { diff --git a/test/spec/modules/33acrossAnalyticsAdapter_spec.js b/test/spec/modules/33acrossAnalyticsAdapter_spec.js index 9e0d928cd97..5089f12a461 100644 --- a/test/spec/modules/33acrossAnalyticsAdapter_spec.js +++ b/test/spec/modules/33acrossAnalyticsAdapter_spec.js @@ -4,10 +4,9 @@ import { log } from 'modules/33acrossAnalyticsAdapter.js'; import * as mockGpt from 'test/spec/integration/faker/googletag.js'; import * as events from 'src/events.js'; import * as faker from 'faker'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import { gdprDataHandler, gppDataHandler, uspDataHandler } from '../../../src/adapterManager'; import { DEFAULT_ENDPOINT, POST_GAM_TIMEOUT, locals } from '../../../modules/33acrossAnalyticsAdapter'; -const { EVENTS, BID_STATUS } = CONSTANTS; describe('33acrossAnalyticsAdapter:', function () { let sandbox; diff --git a/test/spec/modules/adWMGAnalyticsAdapter_spec.js b/test/spec/modules/adWMGAnalyticsAdapter_spec.js index 1e0da1bb3c8..92e1fcbe4db 100644 --- a/test/spec/modules/adWMGAnalyticsAdapter_spec.js +++ b/test/spec/modules/adWMGAnalyticsAdapter_spec.js @@ -2,9 +2,9 @@ import adWMGAnalyticsAdapter from 'modules/adWMGAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; import {expectEvents} from '../../helpers/analytics.js'; +import {EVENTS} from 'src/constants.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); describe('adWMG Analytics', function () { let timestamp = new Date() - 256; @@ -142,31 +142,31 @@ describe('adWMG Analytics', function () { }); expectEvents([ - [constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}], - [constants.EVENTS.BID_REQUESTED, {}], - [constants.EVENTS.BID_RESPONSE, bidResponse], - [constants.EVENTS.NO_BID, {}], - [constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs], - [constants.EVENTS.AUCTION_END, {}], - [constants.EVENTS.BID_WON, wonRequest], + [EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}], + [EVENTS.BID_REQUESTED, {}], + [EVENTS.BID_RESPONSE, bidResponse], + [EVENTS.NO_BID, {}], + [EVENTS.BID_TIMEOUT, bidTimeoutArgs], + [EVENTS.AUCTION_END, {}], + [EVENTS.BID_WON, wonRequest], ]).to.beTrackedBy(adWMGAnalyticsAdapter.track); }); it('should be two xhr requests', function () { - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.AUCTION_END, {}); + events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.equal(2); }); it('second request should be bidWon', function () { - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.AUCTION_END, {}); + events.emit(EVENTS.BID_WON, wonRequest); expect(JSON.parse(server.requests[1].requestBody).events[0].status).to.equal(expectedBidWonData.events[0].status); }); it('check bidWon data', function () { - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.AUCTION_END, {}); + events.emit(EVENTS.BID_WON, wonRequest); let realBidWonData = JSON.parse(server.requests[1].requestBody); expect(realBidWonData.publisher_id).to.equal(expectedBidWonData.publisher_id); expect(realBidWonData.site).to.equal(expectedBidWonData.site); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index 64740f32b06..c14393e267b 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -3,10 +3,10 @@ import { expect } from 'chai'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; +import { EVENTS } from 'src/constants.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); describe('adagio analytics adapter - adagio.js', () => { let sandbox; @@ -86,9 +86,9 @@ describe('adagio analytics adapter - adagio.js', () => { }; const testEvents = { - [constants.EVENTS.BID_REQUESTED]: bidRequest, - [constants.EVENTS.BID_RESPONSE]: bidResponse, - [constants.EVENTS.AUCTION_END]: {} + [EVENTS.BID_REQUESTED]: bidRequest, + [EVENTS.BID_RESPONSE]: bidResponse, + [EVENTS.AUCTION_END]: {} }; // Step 1-3: Send events @@ -162,13 +162,13 @@ describe('adagio analytics adapter - adagio.js', () => { }; // Step 1: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); // Step 2: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // Step 3: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); utils.getWindowTop.restore(); @@ -661,12 +661,12 @@ describe('adagio analytics adapter', () => { } }); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); - events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); expect(server.requests.length).to.equal(3, 'requests count'); { @@ -733,13 +733,13 @@ describe('adagio analytics adapter', () => { } }); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another_nobid); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.bidcached); - events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED.bidcached); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another_nobid); + events.emit(EVENTS.BID_WON, MOCK.BID_WON.bidcached); + events.emit(EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED.bidcached); expect(server.requests.length).to.equal(5, 'requests count'); { @@ -831,12 +831,12 @@ describe('adagio analytics adapter', () => { it('send an "empty" cpm when adserver currency != USD and convertCurrency() is undefined', () => { sandbox.stub(prebidGlobal, 'getGlobal').returns({}); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END.another); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON.another); - events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); expect(server.requests.length).to.equal(3, 'requests count'); diff --git a/test/spec/modules/adkernelAdnAnalytics_spec.js b/test/spec/modules/adkernelAdnAnalytics_spec.js index 7af96c9321c..fc6cba5176b 100644 --- a/test/spec/modules/adkernelAdnAnalytics_spec.js +++ b/test/spec/modules/adkernelAdnAnalytics_spec.js @@ -1,7 +1,7 @@ import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/adkernelAdnAnalyticsAdapter'; import {expect} from 'chai'; import adapterManager from 'src/adapterManager'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; const events = require('../../../src/events'); @@ -230,21 +230,21 @@ describe('', function () { }); it('should handle auction init event', function () { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, bidderRequests: [REQUEST], timeout: 3000}); + events.emit(EVENTS.AUCTION_INIT, {config: {}, bidderRequests: [REQUEST], timeout: 3000}); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); expect(ev[0]).to.be.eql({event: 'auctionInit'}); }); it('should handle bid request event', function () { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + events.emit(EVENTS.BID_REQUESTED, REQUEST); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(2); expect(ev[1]).to.be.eql({event: 'bidRequested', adapter: 'adapter', tagid: 'container-1'}); }); it('should handle bid response event', function () { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); + events.emit(EVENTS.BID_RESPONSE, RESPONSE); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(3); expect(ev[2]).to.be.eql({ @@ -258,7 +258,7 @@ describe('', function () { it('should handle auction end event', function () { timer.tick(447); - events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + events.emit(EVENTS.AUCTION_END, RESPONSE); let ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(0); expect(ajaxStub.calledOnce).to.be.equal(true); @@ -267,7 +267,7 @@ describe('', function () { }); it('should handle winning bid', function () { - events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + events.emit(EVENTS.BID_WON, RESPONSE); timer.tick(4500); expect(ajaxStub.calledTwice).to.be.equal(true); let ev = JSON.parse(ajaxStub.secondCall.args[0]).hb_ev; diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 8acd02c7f26..450dd83f86d 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -3,7 +3,7 @@ import analyticsAdapter, { command as analyticsCommand, COMMAND } from 'modules/ import { AUCTION_COMPLETED } from 'src/auction.js'; import { expect } from 'chai'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import * as utils from 'src/utils.js'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; @@ -143,10 +143,10 @@ describe('Adloox Analytics Adapter', function () { return arg.tagName === 'LINK' && arg.getAttribute('rel') === 'preload' && arg.getAttribute('as') === 'script' && href_uri.href === uri.href; }; - events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); + events.emit(EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.calledWith(sinon.match(isLinkPreloadAsScript))).to.true; - events.emit(CONSTANTS.EVENTS.AUCTION_END, auctionDetails); + events.emit(EVENTS.AUCTION_END, auctionDetails); expect(insertElementStub.callCount).to.equal(1); done(); @@ -167,7 +167,7 @@ describe('Adloox Analytics Adapter', function () { const querySelectorStub = sandbox.stub(document, 'querySelector'); querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); - events.emit(CONSTANTS.EVENTS.BID_WON, bid); + events.emit(EVENTS.BID_WON, bid); const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; @@ -196,7 +196,7 @@ describe('Adloox Analytics Adapter', function () { const querySelectorStub = sandbox.stub(document, 'querySelector'); querySelectorStub.withArgs(`#${bid.adUnitCode}`).returns(slot); - events.emit(CONSTANTS.EVENTS.BID_WON, bidIgnore); + events.emit(EVENTS.BID_WON, bidIgnore); expect(parent.querySelector('script')).is.null; @@ -238,7 +238,7 @@ describe('Adloox Analytics Adapter', function () { it('should inject tracking event', function (done) { const data = { - eventType: CONSTANTS.EVENTS.BID_WON, + eventType: EVENTS.BID_WON, args: bid }; diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js index d872d6f8e08..703e6ed8992 100644 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ b/test/spec/modules/adomikAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; import { expect } from 'chai'; +import {EVENTS} from 'src/constants.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); describe('Adomik Prebid Analytic', function () { let sendEventStub; @@ -70,7 +70,7 @@ describe('Adomik Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); + events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); expect(adomikAnalytics.currentContext).to.deep.equal({ uid: '123456', @@ -81,7 +81,7 @@ describe('Adomik Prebid Analytic', function () { }); // Step 3: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid] }); + events.emit(EVENTS.BID_REQUESTED, { bids: [bid] }); expect(adomikAnalytics.bucketEvents.length).to.equal(1); expect(adomikAnalytics.bucketEvents[0]).to.deep.equal({ @@ -93,7 +93,7 @@ describe('Adomik Prebid Analytic', function () { }); // Step 4: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bid); + events.emit(EVENTS.BID_RESPONSE, bid); expect(adomikAnalytics.bucketEvents.length).to.equal(2); expect(adomikAnalytics.bucketEvents[1]).to.deep.equal({ @@ -114,17 +114,17 @@ describe('Adomik Prebid Analytic', function () { }); // Step 5: Send bid won event - events.emit(constants.EVENTS.BID_WON, bid); + events.emit(EVENTS.BID_WON, bid); expect(adomikAnalytics.bucketEvents.length).to.equal(2); // Step 6: Send bid timeout event - events.emit(constants.EVENTS.BID_TIMEOUT, {}); + events.emit(EVENTS.BID_TIMEOUT, {}); expect(adomikAnalytics.currentContext.timeouted).to.equal(true); // Step 7: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); setTimeout(function() { sinon.assert.callCount(sendEventStub, 1); diff --git a/test/spec/modules/adxcgAnalyticsAdapter_spec.js b/test/spec/modules/adxcgAnalyticsAdapter_spec.js index a796e7e966d..40e1347bce3 100644 --- a/test/spec/modules/adxcgAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxcgAnalyticsAdapter_spec.js @@ -2,9 +2,9 @@ import adxcgAnalyticsAdapter from 'modules/adxcgAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('adxcg analytics adapter', function () { beforeEach(function () { @@ -171,21 +171,21 @@ describe('adxcg analytics adapter', function () { it('builds and sends auction data', function () { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { timestamp: auctionTimestamp }); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); expect(server.requests.length).to.equal(1); @@ -196,7 +196,7 @@ describe('adxcg analytics adapter', function () { expect(realAfterBid.bidTimeout).to.deep.equal(['bidderOne', 'bidderTwo']); // Step 6: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.equal(2); let winEventData = JSON.parse(server.requests[1].requestBody); diff --git a/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js index fd698e9e1fd..fe453a1c208 100644 --- a/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js +++ b/test/spec/modules/adxpremiumAnalyticsAdapter_spec.js @@ -3,9 +3,9 @@ import { testSend } from 'modules/adxpremiumAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('AdxPremium analytics adapter', function () { beforeEach(function () { @@ -378,19 +378,19 @@ describe('AdxPremium analytics adapter', function () { it('builds and sends auction data', function () { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); + events.emit(EVENTS.AUCTION_INIT, auctionInit); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); testSend(); @@ -404,7 +404,7 @@ describe('AdxPremium analytics adapter', function () { expect(realAfterBid).to.deep.equal(expectedAfterTimeout); // Step 6: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, wonRequest); expect(server.requests.length).to.equal(3); let winEventData = JSON.parse(server.requests[1].requestBody); diff --git a/test/spec/modules/agmaAnalyticsAdapter_spec.js b/test/spec/modules/agmaAnalyticsAdapter_spec.js index df18e7bcf45..227acacde12 100644 --- a/test/spec/modules/agmaAnalyticsAdapter_spec.js +++ b/test/spec/modules/agmaAnalyticsAdapter_spec.js @@ -7,7 +7,7 @@ import agmaAnalyticsAdapter, { import { gdprDataHandler } from '../../../src/adapterManager.js'; import { expect } from 'chai'; import * as events from '../../../src/events.js'; -import constants from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { generateUUID } from '../../../src/utils.js'; import { server } from '../../mocks/xhr.js'; import { config } from 'src/config.js'; @@ -281,22 +281,22 @@ describe('AGMA Analytics Adapter', () => { auctionId: generateUUID(), }; - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { auctionId: generateUUID('1'), auction, }); clock.tick(200); - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { auctionId: generateUUID('2'), auction, }); - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { auctionId: generateUUID('3'), auction, }); - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { auctionId: generateUUID('4'), auction, }); @@ -307,7 +307,7 @@ describe('AGMA Analytics Adapter', () => { const requestBody = JSON.parse(request.requestBody); expect(request.url).to.equal(INGEST_URL); expect(requestBody).to.have.all.keys(extendedKey); - expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(requestBody.triggerEvent).to.equal(EVENTS.AUCTION_INIT); expect(server.requests).to.have.length(1); }); @@ -327,14 +327,14 @@ describe('AGMA Analytics Adapter', () => { auctionId: generateUUID(), }; - events.emit(constants.EVENTS.AUCTION_INIT, auction); + events.emit(EVENTS.AUCTION_INIT, auction); clock.tick(1100); const [request] = server.requests; const requestBody = JSON.parse(request.requestBody); expect(request.url).to.equal(INGEST_URL); expect(requestBody).to.have.all.keys(extendedKey); - expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(requestBody.triggerEvent).to.equal(EVENTS.AUCTION_INIT); expect(requestBody.deviceWidth).to.equal(screen.width); expect(requestBody.deviceHeight).to.equal(screen.height); expect(server.requests).to.have.length(1); @@ -351,13 +351,13 @@ describe('AGMA Analytics Adapter', () => { auctionId: generateUUID(), }; - events.emit(constants.EVENTS.AUCTION_INIT, auction); + events.emit(EVENTS.AUCTION_INIT, auction); clock.tick(1000); const [request] = server.requests; const requestBody = JSON.parse(request.requestBody); expect(request.url).to.equal(INGEST_URL); - expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_INIT); + expect(requestBody.triggerEvent).to.equal(EVENTS.AUCTION_INIT); expect(server.requests).to.have.length(1); expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); }); @@ -369,22 +369,22 @@ describe('AGMA Analytics Adapter', () => { provider: 'agma', options: { code: 'test', - triggerEvent: constants.EVENTS.AUCTION_END + triggerEvent: EVENTS.AUCTION_END }, }); const auction = { auctionId: generateUUID(), }; - events.emit(constants.EVENTS.AUCTION_INIT, auction); - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_INIT, auction); + events.emit(EVENTS.AUCTION_END, auction); clock.tick(1000); const [request] = server.requests; const requestBody = JSON.parse(request.requestBody); expect(request.url).to.equal(INGEST_URL); expect(requestBody.auctionIds).to.have.length(1); - expect(requestBody.triggerEvent).to.equal(constants.EVENTS.AUCTION_END); + expect(requestBody.triggerEvent).to.equal(EVENTS.AUCTION_END); expect(server.requests).to.have.length(1); expect(agmaAnalyticsAdapter.auctionIds).to.have.length(0); }); diff --git a/test/spec/modules/appierAnalyticsAdapter_spec.js b/test/spec/modules/appierAnalyticsAdapter_spec.js index cd026f64d49..e380672b73c 100644 --- a/test/spec/modules/appierAnalyticsAdapter_spec.js +++ b/test/spec/modules/appierAnalyticsAdapter_spec.js @@ -4,7 +4,7 @@ import { } from 'modules/appierAnalyticsAdapter.js'; import {expect} from 'chai'; const events = require('src/events'); -const constants = require('src/constants.json'); +const constants = require('src/constants.js'); const affiliateId = 'WhctHaViHtI'; const configId = 'd9cc9a9be9b240eda17cf1c9a8a4b29c'; diff --git a/test/spec/modules/asteriobidAnalyticsAdapter_spec.js b/test/spec/modules/asteriobidAnalyticsAdapter_spec.js index 9be6c1dedac..7c336d2a885 100644 --- a/test/spec/modules/asteriobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/asteriobidAnalyticsAdapter_spec.js @@ -3,9 +3,9 @@ import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('AsterioBid Analytics Adapter', function () { let bidWonEvent = { @@ -66,7 +66,7 @@ describe('AsterioBid Analytics Adapter', function () { } }); - events.emit(constants.EVENTS.BID_WON, bidWonEvent); + events.emit(EVENTS.BID_WON, bidWonEvent); asteriobidAnalytics.flush(); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/atsAnalyticsAdapter_spec.js b/test/spec/modules/atsAnalyticsAdapter_spec.js index 2316f96ec8e..3440cb1efbb 100644 --- a/test/spec/modules/atsAnalyticsAdapter_spec.js +++ b/test/spec/modules/atsAnalyticsAdapter_spec.js @@ -5,10 +5,10 @@ import {server} from '../../mocks/xhr.js'; import {parseBrowser} from '../../../modules/atsAnalyticsAdapter.js'; import {getCoreStorageManager, getStorageManager} from '../../../src/storageManager.js'; import {analyticsUrl} from '../../../modules/atsAnalyticsAdapter.js'; -let utils = require('src/utils'); +import {EVENTS} from 'src/constants.js'; +let utils = require('src/utils'); let events = require('src/events'); -let constants = require('src/constants.json'); const storage = getCoreStorageManager(); let sandbox; @@ -160,22 +160,22 @@ describe('ats analytics adapter', function () { }); // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { timestamp: auctionTimestamp }); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); // Step 6: Send bid won event - events.emit(constants.EVENTS.BID_WON, wonRequest); + events.emit(EVENTS.BID_WON, wonRequest); sandbox.stub($$PREBID_GLOBAL$$, 'getAllWinningBids').callsFake((key) => { return [wonRequest] diff --git a/test/spec/modules/automatadAnalyticsAdapter_spec.js b/test/spec/modules/automatadAnalyticsAdapter_spec.js index a7dd28a8dc0..8b566280cc6 100644 --- a/test/spec/modules/automatadAnalyticsAdapter_spec.js +++ b/test/spec/modules/automatadAnalyticsAdapter_spec.js @@ -3,7 +3,7 @@ import * as utils from 'src/utils.js'; import spec, {self as exports} from 'modules/automatadAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import { expect } from 'chai'; const obj = { @@ -28,7 +28,7 @@ const { BID_TIMEOUT, BID_WON, NO_BID -} = CONSTANTS.EVENTS +} = EVENTS const CONFIG_WITH_DEBUG = { provider: 'atmtdAnalyticsAdapter', diff --git a/test/spec/modules/bidViewabilityIO_spec.js b/test/spec/modules/bidViewabilityIO_spec.js index 5b4944082bc..e18d3bdca58 100644 --- a/test/spec/modules/bidViewabilityIO_spec.js +++ b/test/spec/modules/bidViewabilityIO_spec.js @@ -3,7 +3,7 @@ import * as events from 'src/events.js'; import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { expect } from 'chai'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; describe('#bidViewabilityIO', function() { const makeElement = (id) => { @@ -97,7 +97,7 @@ describe('#bidViewabilityIO', function() { expect(mockObserver.unobserve.calledOnce).to.be.true; expect(emitSpy.calledOnce).to.be.true; // expect(emitSpy.firstCall.args).to.be.false; - expect(emitSpy.firstCall.args[0]).to.eq(CONSTANTS.EVENTS.BID_VIEWABLE); + expect(emitSpy.firstCall.args[0]).to.eq(EVENTS.BID_VIEWABLE); }); }) diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 2d2e51abbe1..1df9aecf73a 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -5,7 +5,7 @@ import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import {expect, spy} from 'chai'; import * as prebidGlobal from 'src/prebidGlobal.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import parse from 'url-parse'; @@ -292,9 +292,9 @@ describe('#bidViewability', function() { let call = callBidViewableBidderSpy.getCall(0); expect(call.args[0]).to.equal(PBJS_WINNING_BID.bidder); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); - // CONSTANTS.EVENTS.BID_VIEWABLE is triggered + // EVENTS.BID_VIEWABLE is triggered call = eventsEmitSpy.getCall(0); - expect(call.args[0]).to.equal(CONSTANTS.EVENTS.BID_VIEWABLE); + expect(call.args[0]).to.equal(EVENTS.BID_VIEWABLE); expect(call.args[1]).to.deep.equal(PBJS_WINNING_BID); }); @@ -303,7 +303,7 @@ describe('#bidViewability', function() { expect(triggerPixelSpy.callCount).to.equal(0); // adapterManager.callBidViewableBidder is NOT called expect(callBidViewableBidderSpy.callCount).to.equal(0); - // CONSTANTS.EVENTS.BID_VIEWABLE is NOT triggered + // EVENTS.BID_VIEWABLE is NOT triggered expect(eventsEmitSpy.callCount).to.equal(0); }); diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js index be1ffb06c76..d934a6c611b 100644 --- a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -2,9 +2,10 @@ import bidwatchAnalytics from 'modules/bidwatchAnalyticsAdapter.js'; import {dereferenceWithoutRenderer} from 'modules/bidwatchAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); describe('BidWatch Analytics', function () { let timestamp = new Date() - 256; @@ -301,10 +302,10 @@ describe('BidWatch Analytics', function () { } }); - events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); - events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); + events.emit(EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); expect(server.requests.length).to.equal(1); let message = JSON.parse(server.requests[0].requestBody); expect(message).to.have.property('auctionEnd').exist; @@ -331,7 +332,7 @@ describe('BidWatch Analytics', function () { domain: 'test' } }); - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests.length).to.equal(1); let message = JSON.parse(server.requests[0].requestBody); expect(message).not.to.have.property('ad'); diff --git a/test/spec/modules/byDataAnalyticsAdapter_spec.js b/test/spec/modules/byDataAnalyticsAdapter_spec.js index c680c687a71..b98b5cb7039 100644 --- a/test/spec/modules/byDataAnalyticsAdapter_spec.js +++ b/test/spec/modules/byDataAnalyticsAdapter_spec.js @@ -1,8 +1,9 @@ import ascAdapter from 'modules/byDataAnalyticsAdapter'; import { expect } from 'chai'; +import {EVENTS} from 'src/constants.js'; + let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); let auctionId = 'b70ef967-5c5b-4602-831e-f2cf16e59af2'; const initOptions = { clientId: 'asc00000', @@ -176,9 +177,9 @@ describe('byData Analytics Adapter ', () => { }); }); it('sends and formatted auction data ', function () { - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeoutArgs); - events.emit(constants.EVENTS.NO_BID, noBidArgs); - events.emit(constants.EVENTS.BID_WON, bidWonArgs) + events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgs); + events.emit(EVENTS.NO_BID, noBidArgs); + events.emit(EVENTS.BID_WON, bidWonArgs) var userToken = ascAdapter.getVisitorData(userData); var newAuData = ascAdapter.dataProcess(auctionEndArgs); var newBwData = ascAdapter.getBidWonData(bidWonArgs); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js index 1d21fbd8457..3145108c373 100644 --- a/test/spec/modules/cleanioRtdProvider_spec.js +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -2,7 +2,7 @@ import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { __TEST__ } from '../../../modules/cleanioRtdProvider.js'; @@ -193,16 +193,16 @@ describe('clean.io RTD module', function () { const eventCounter = { registerCleanioBillingEvent: function() {} }; sinon.spy(eventCounter, 'registerCleanioBillingEvent'); - events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (evt) => { + events.on(EVENTS.BILLABLE_EVENT, (evt) => { if (evt.vendor === 'clean.io') { eventCounter.registerCleanioBillingEvent() } }); - events.emit(CONSTANTS.EVENTS.BID_WON, {}); - events.emit(CONSTANTS.EVENTS.BID_WON, {}); - events.emit(CONSTANTS.EVENTS.BID_WON, {}); - events.emit(CONSTANTS.EVENTS.BID_WON, {}); + events.emit(EVENTS.BID_WON, {}); + events.emit(EVENTS.BID_WON, {}); + events.emit(EVENTS.BID_WON, {}); + events.emit(EVENTS.BID_WON, {}); sinon.assert.callCount(eventCounter.registerCleanioBillingEvent, 4); }); diff --git a/test/spec/modules/concertAnalyticsAdapter_spec.js b/test/spec/modules/concertAnalyticsAdapter_spec.js index 1df73ae04fe..7cb6db1b1a0 100644 --- a/test/spec/modules/concertAnalyticsAdapter_spec.js +++ b/test/spec/modules/concertAnalyticsAdapter_spec.js @@ -1,10 +1,11 @@ import concertAnalytics from 'modules/concertAnalyticsAdapter.js'; import { expect } from 'chai'; import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; + const sinon = require('sinon'); let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); describe('ConcertAnalyticsAdapter', function() { let sandbox; @@ -147,10 +148,10 @@ describe('ConcertAnalyticsAdapter', function() { } function fireBidEvents(events) { - events.emit(constants.EVENTS.AUCTION_INIT, {timestamp, auctionId, timeout, adUnits}); - events.emit(constants.EVENTS.BID_REQUESTED, {bidder: 'concert'}); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.AUCTION_INIT, { timestamp, auctionId, timeout, adUnits }); + events.emit(EVENTS.BID_REQUESTED, { bidder: 'concert' }); + events.emit(EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.AUCTION_END, {}); + events.emit(EVENTS.BID_WON, bidWon); } }); diff --git a/test/spec/modules/confiantRtdProvider_spec.js b/test/spec/modules/confiantRtdProvider_spec.js index 8f9fcd0ba98..f9b86046f3a 100644 --- a/test/spec/modules/confiantRtdProvider_spec.js +++ b/test/spec/modules/confiantRtdProvider_spec.js @@ -1,7 +1,7 @@ import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import confiantModule from '../../../modules/confiantRtdProvider.js'; @@ -48,7 +48,7 @@ describe('Confiant RTD module', function () { let billableEventsCounter = 0; const propertyId = 'fff'; - events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + events.on(EVENTS.BILLABLE_EVENT, (e) => { if (e.vendor === 'confiant') { billableEventsCounter++; expect(e.type).to.equal('impression'); diff --git a/test/spec/modules/conversantAnalyticsAdapter_spec.js b/test/spec/modules/conversantAnalyticsAdapter_spec.js index f425535ce73..75cb63b02f6 100644 --- a/test/spec/modules/conversantAnalyticsAdapter_spec.js +++ b/test/spec/modules/conversantAnalyticsAdapter_spec.js @@ -1,13 +1,13 @@ import sinon from 'sinon'; -import {expect} from 'chai'; -import {default as conversantAnalytics, CNVR_CONSTANTS, cnvrHelper} from 'modules/conversantAnalyticsAdapter'; +import { expect } from 'chai'; +import { default as conversantAnalytics, CNVR_CONSTANTS, cnvrHelper } from 'modules/conversantAnalyticsAdapter'; import * as utils from 'src/utils.js'; import * as prebidGlobal from 'src/prebidGlobal'; -import {server} from '../../mocks/xhr.js'; +import { server } from '../../mocks/xhr.js'; -import constants from 'src/constants.json' +import {EVENTS} from 'src/constants.js' -let events = require('src/events'); +const events = require('src/events'); describe('Conversant analytics adapter tests', function() { let sandbox; // sinon sandbox to make restoring all stubbed objects easier @@ -39,7 +39,7 @@ describe('Conversant analytics adapter tests', function() { requests = server.requests; sandbox = sinon.sandbox.create(); sandbox.stub(events, 'getEvents').returns([]); // need to stub this otherwise unwanted events seem to get fired during testing - let getGlobalStub = { + const getGlobalStub = { version: PREBID_VERSION, getUserIds: function() { // userIdTargeting.js init() gets called on AUCTION_END so we need to mock this function. return {}; @@ -98,7 +98,7 @@ describe('Conversant analytics adapter tests', function() { it('should NOT sample when sampling set to 0', function() { sandbox.stub(utils, 'logError'); const NEVER_SAMPLE_CONFIG = utils.deepClone(VALID_ALWAYS_SAMPLE_CONFIG); - NEVER_SAMPLE_CONFIG['options'].cnvr_sampling = 0; + NEVER_SAMPLE_CONFIG.options.cnvr_sampling = 0; conversantAnalytics.disableAnalytics(); conversantAnalytics.enableAnalytics(NEVER_SAMPLE_CONFIG); expect(utils.logError.called).to.equal(false); @@ -110,17 +110,17 @@ describe('Conversant analytics adapter tests', function() { it('should cleanup up cache objects', function() { conversantAnalytics.enableAnalytics(VALID_CONFIGURATION); - cnvrHelper.adIdLookup['keep'] = {timeReceived: DATESTAMP + 1}; - cnvrHelper.adIdLookup['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE}; + cnvrHelper.adIdLookup.keep = { timeReceived: DATESTAMP + 1 }; + cnvrHelper.adIdLookup.delete = { timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE }; - cnvrHelper.timeoutCache['keep'] = {timeReceived: DATESTAMP + 1}; - cnvrHelper.timeoutCache['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE}; + cnvrHelper.timeoutCache.keep = { timeReceived: DATESTAMP + 1 }; + cnvrHelper.timeoutCache.delete = { timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE }; - cnvrHelper.auctionIdTimestampCache['keep'] = {timeReceived: DATESTAMP + 1}; - cnvrHelper.auctionIdTimestampCache['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE}; + cnvrHelper.auctionIdTimestampCache.keep = { timeReceived: DATESTAMP + 1 }; + cnvrHelper.auctionIdTimestampCache.delete = { timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE }; - cnvrHelper.bidderErrorCache['keep'] = {timeReceived: DATESTAMP + 1, errors: []}; - cnvrHelper.bidderErrorCache['delete'] = {timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE, errors: []}; + cnvrHelper.bidderErrorCache.keep = { timeReceived: DATESTAMP + 1, errors: [] }; + cnvrHelper.bidderErrorCache.delete = { timeReceived: DATESTAMP - CNVR_CONSTANTS.MAX_MILLISECONDS_IN_CACHE, errors: [] }; expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(2); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(2); @@ -145,12 +145,12 @@ describe('Conversant analytics adapter tests', function() { it('createBid() should return correct object', function() { const EVENT_CODE = 1; const TIME = 2; - let bid = cnvrHelper.createBid(EVENT_CODE, 2); - expect(bid).to.deep.equal({'eventCodes': [EVENT_CODE], 'timeToRespond': TIME}); + const bid = cnvrHelper.createBid(EVENT_CODE, 2); + expect(bid).to.deep.equal({ eventCodes: [EVENT_CODE], timeToRespond: TIME }); }); it('createAdUnit() should return correct object', function() { - let adUnit = cnvrHelper.createAdUnit(); + const adUnit = cnvrHelper.createAdUnit(); expect(adUnit).to.deep.equal({ sizes: [], mediaTypes: [], @@ -160,13 +160,13 @@ describe('Conversant analytics adapter tests', function() { it('createAdSize() should return correct object', function() { let adSize = cnvrHelper.createAdSize(1, 2); - expect(adSize).to.deep.equal({w: 1, h: 2}); + expect(adSize).to.deep.equal({ w: 1, h: 2 }); adSize = cnvrHelper.createAdSize(); - expect(adSize).to.deep.equal({w: -1, h: -1}); + expect(adSize).to.deep.equal({ w: -1, h: -1 }); adSize = cnvrHelper.createAdSize('foo', 'bar'); - expect(adSize).to.deep.equal({w: -1, h: -1}); + expect(adSize).to.deep.equal({ w: -1, h: -1 }); }); it('getLookupKey() should return correct object', function() { @@ -183,7 +183,7 @@ describe('Conversant analytics adapter tests', function() { const myDate = Date.now(); conversantAnalytics.enableAnalytics(VALID_ALWAYS_SAMPLE_CONFIG); - let payload = cnvrHelper.createPayload(REQUEST_TYPE, AUCTION_ID, myDate); + const payload = cnvrHelper.createPayload(REQUEST_TYPE, AUCTION_ID, myDate); expect(payload).to.deep.equal({ bidderErrors: [], cnvrSampleRate: 1, @@ -200,7 +200,7 @@ describe('Conversant analytics adapter tests', function() { }); it('keyExistsAndIsObject() should return correct data', function() { - let data = { + const data = { a: [], b: 1, c: 'foo', @@ -216,21 +216,21 @@ describe('Conversant analytics adapter tests', function() { }); it('deduplicateArray() should return correct data', function () { - let arrayOfObjects = [{w: 1, h: 2}, {w: 2, h: 3}, {w: 1, h: 2}]; - let array = [3, 2, 1, 1, 2, 3]; + const arrayOfObjects = [{ w: 1, h: 2 }, { w: 2, h: 3 }, { w: 1, h: 2 }]; + const array = [3, 2, 1, 1, 2, 3]; let empty; - let notArray = 3; - let emptyArray = []; + const notArray = 3; + const emptyArray = []; expect(JSON.stringify(cnvrHelper.deduplicateArray(array))).to.equal(JSON.stringify([3, 2, 1])); - expect(JSON.stringify(cnvrHelper.deduplicateArray(arrayOfObjects))).to.equal(JSON.stringify([{w: 1, h: 2}, {w: 2, h: 3}])); + expect(JSON.stringify(cnvrHelper.deduplicateArray(arrayOfObjects))).to.equal(JSON.stringify([{ w: 1, h: 2 }, { w: 2, h: 3 }])); expect(JSON.stringify(cnvrHelper.deduplicateArray(emptyArray))).to.equal(JSON.stringify([])); expect(cnvrHelper.deduplicateArray(empty)).to.be.undefined; expect(cnvrHelper.deduplicateArray(notArray)).to.equal(notArray); }); it('getSampleRate() should return correct data', function () { - let obj = { + const obj = { sampling: 1, cnvr_sampling: 0.5, too_big: 1.2, @@ -249,7 +249,7 @@ describe('Conversant analytics adapter tests', function() { }); it('getPageUrl() should return correct data', function() { - let url = cnvrHelper.getPageUrl(); + const url = cnvrHelper.getPageUrl(); expect(url.length).to.be.above(1); }); @@ -313,20 +313,20 @@ describe('Conversant analytics adapter tests', function() { describe('Bid Timeout Event Tests', function() { const BID_TIMEOUT_PAYLOAD = [{ - 'bidId': '80882409358b8a8', - 'bidder': 'conversant', - 'adUnitCode': 'MedRect', - 'auctionId': 'afbd6e0b-e45b-46ab-87bf-c0bac0cb8881' + bidId: '80882409358b8a8', + bidder: 'conversant', + adUnitCode: 'MedRect', + auctionId: 'afbd6e0b-e45b-46ab-87bf-c0bac0cb8881' }, { - 'bidId': '9da4c107a6f24c8', - 'bidder': 'conversant', - 'adUnitCode': 'Leaderboard', - 'auctionId': 'afbd6e0b-e45b-46ab-87bf-c0bac0cb8881' + bidId: '9da4c107a6f24c8', + bidder: 'conversant', + adUnitCode: 'Leaderboard', + auctionId: 'afbd6e0b-e45b-46ab-87bf-c0bac0cb8881' }]; it('should put both items in timeout cache', function() { expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(0); - events.emit(constants.EVENTS.BID_TIMEOUT, BID_TIMEOUT_PAYLOAD); + events.emit(EVENTS.BID_TIMEOUT, BID_TIMEOUT_PAYLOAD); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(2); BID_TIMEOUT_PAYLOAD.forEach(timeoutBid => { @@ -358,7 +358,7 @@ describe('Conversant analytics adapter tests', function() { }; expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); - events.emit(constants.EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD); + events.emit(EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); // object should be removed expect(requests).to.have.lengthOf(1); const data = JSON.parse(requests[0].requestBody); @@ -366,8 +366,8 @@ describe('Conversant analytics adapter tests', function() { expect(data.auction.auctionId).to.equal('auctionId'); expect(data.auction.preBidVersion).to.equal(PREBID_VERSION); expect(data.auction.sid).to.equal(SITE_ID); - expect(data.adUnits['adUnitCode'].bids['bidderCode'][0].eventCodes.includes(CNVR_CONSTANTS.RENDER_FAILED)).to.be.true; - expect(data.adUnits['adUnitCode'].bids['bidderCode'][0].message).to.have.lengthOf.above(0); + expect(data.adUnits.adUnitCode.bids.bidderCode[0].eventCodes.includes(CNVR_CONSTANTS.RENDER_FAILED)).to.be.true; + expect(data.adUnits.adUnitCode.bids.bidderCode[0].message).to.have.lengthOf.above(0); }); it('should not send data if no adId', function() { @@ -379,14 +379,14 @@ describe('Conversant analytics adapter tests', function() { }; expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); - events.emit(constants.EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD_NO_ADID); + events.emit(EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD_NO_ADID); expect(requests).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); // same object in cache as before... no change expect(cnvrHelper.adIdLookup[RENDER_FAILED_PAYLOAD.adId]).to.not.be.undefined; expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); const data = JSON.parse(requests[0].requestBody); - expect(data.event).to.be.equal(constants.EVENTS.AD_RENDER_FAILED); + expect(data.event).to.be.equal(EVENTS.AD_RENDER_FAILED); expect(data.siteId).to.be.equal(SITE_ID); expect(data.message).to.not.be.undefined; expect(data.prebidVersion).to.not.be.undefined; @@ -402,13 +402,13 @@ describe('Conversant analytics adapter tests', function() { }; expect(requests).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); - events.emit(constants.EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD); + events.emit(EVENTS.AD_RENDER_FAILED, RENDER_FAILED_PAYLOAD); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); // object should be removed but no call made to send data expect(requests).to.have.lengthOf(1); expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); const data = JSON.parse(requests[0].requestBody); - expect(data.event).to.be.equal(constants.EVENTS.AD_RENDER_FAILED); + expect(data.event).to.be.equal(EVENTS.AD_RENDER_FAILED); expect(data.siteId).to.be.equal(SITE_ID); expect(data.message).to.not.be.undefined; expect(data.prebidVersion).to.not.be.undefined; @@ -492,14 +492,14 @@ describe('Conversant analytics adapter tests', function() { it('should not send data or put a record in adIdLookup when bad data provided', function() { expect(requests).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); - events.emit(constants.EVENTS.BID_WON, BAD_BID_WON_ARGS); + events.emit(EVENTS.BID_WON, BAD_BID_WON_ARGS); expect(requests).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); // check for error event expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); const data = JSON.parse(requests[0].requestBody); - expect(data.event).to.be.equal(constants.EVENTS.BID_WON); + expect(data.event).to.be.equal(EVENTS.BID_WON); expect(data.siteId).to.be.equal(SITE_ID); expect(data.message).to.not.be.undefined; expect(data.prebidVersion).to.not.be.undefined; @@ -509,11 +509,11 @@ describe('Conversant analytics adapter tests', function() { it('should send data and put a record in adIdLookup', function() { const myAuctionStart = Date.now(); - cnvrHelper.auctionIdTimestampCache[GOOD_BID_WON_ARGS.auctionId] = {timeReceived: myAuctionStart}; + cnvrHelper.auctionIdTimestampCache[GOOD_BID_WON_ARGS.auctionId] = { timeReceived: myAuctionStart }; expect(requests).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(0); - events.emit(constants.EVENTS.BID_WON, GOOD_BID_WON_ARGS); + events.emit(EVENTS.BID_WON, GOOD_BID_WON_ARGS); // Check that adIdLookup was set correctly expect(Object.keys(cnvrHelper.adIdLookup)).to.have.lengthOf(1); @@ -914,16 +914,16 @@ describe('Conversant analytics adapter tests', function() { it('should not do anything when auction id doesnt exist', function() { sandbox.stub(utils, 'logError'); - let BAD_ARGS = JSON.parse(JSON.stringify(AUCTION_END_PAYLOAD)); + const BAD_ARGS = JSON.parse(JSON.stringify(AUCTION_END_PAYLOAD)); delete BAD_ARGS.auctionId; expect(requests).to.have.lengthOf(0); - events.emit(constants.EVENTS.AUCTION_END, BAD_ARGS); + events.emit(EVENTS.AUCTION_END, BAD_ARGS); expect(requests).to.have.lengthOf(1); // check for error event expect(requests[0].url).to.contain('cvx/event/prebidanalyticerrors'); const data = JSON.parse(requests[0].requestBody); - expect(data.event).to.be.equal(constants.EVENTS.AUCTION_END); + expect(data.event).to.be.equal(EVENTS.AUCTION_END); expect(data.siteId).to.be.equal(SITE_ID); expect(data.message).to.not.be.undefined; expect(data.prebidVersion).to.not.be.undefined; @@ -961,7 +961,7 @@ describe('Conversant analytics adapter tests', function() { expect(Object.keys(cnvrHelper.auctionIdTimestampCache)).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(1); - events.emit(constants.EVENTS.AUCTION_END, AUCTION_END_PAYLOAD); + events.emit(EVENTS.AUCTION_END, AUCTION_END_PAYLOAD); expect(utils.logError.called).to.equal(false); expect(requests).to.have.lengthOf(1); expect(Object.keys(cnvrHelper.timeoutCache)).to.have.lengthOf(0); @@ -993,7 +993,7 @@ describe('Conversant analytics adapter tests', function() { expect(data.adUnits[AD_UNIT_CODE_NATIVE].sizes).to.have.lengthOf(0); expect(Object.keys(data.adUnits[AD_UNIT_CODE].bids)).to.have.lengthOf(2); - const cnvrBidsArray = data.adUnits[AD_UNIT_CODE].bids['conversant']; + const cnvrBidsArray = data.adUnits[AD_UNIT_CODE].bids.conversant; // testing multiple bids from same bidder expect(cnvrBidsArray).to.have.lengthOf(2); expect(cnvrBidsArray[0].eventCodes.includes(CNVR_CONSTANTS.BID)).to.be.true; @@ -1014,7 +1014,7 @@ describe('Conversant analytics adapter tests', function() { expect(cnvrBidsArray[1].adSize.h).to.equal(100); expect(cnvrBidsArray[1].mediaType).to.equal('banner'); - const apnBidsArray = data.adUnits[AD_UNIT_CODE].bids['appnexus']; + const apnBidsArray = data.adUnits[AD_UNIT_CODE].bids.appnexus; expect(apnBidsArray).to.have.lengthOf(2); let apnBid = apnBidsArray[0]; expect(apnBid.originalCpm).to.be.undefined; @@ -1034,7 +1034,7 @@ describe('Conversant analytics adapter tests', function() { expect(apnBid.mediaType).to.be.undefined; expect(Object.keys(data.adUnits[AD_UNIT_CODE_NATIVE].bids)).to.have.lengthOf(1); - const apnNativeBidsArray = data.adUnits[AD_UNIT_CODE_NATIVE].bids['appnexus']; + const apnNativeBidsArray = data.adUnits[AD_UNIT_CODE_NATIVE].bids.appnexus; expect(apnNativeBidsArray).to.have.lengthOf(1); const apnNativeBid = apnNativeBidsArray[0]; expect(apnNativeBid.eventCodes.includes(CNVR_CONSTANTS.BID)).to.be.true; @@ -1075,7 +1075,7 @@ describe('Conversant analytics adapter tests', function() { bidderCode: 'myBidderCode', bidderRequestId: '15246a574e859f', bids: [{}], - gdprConsent: {consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true}, + gdprConsent: { consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', vendorData: {}, gdprApplies: true }, refererInfo: { canonicalUrl: null, page: 'http://mypage.org?pbjs_debug=true', @@ -1089,12 +1089,12 @@ describe('Conversant analytics adapter tests', function() { }; it('should record error when bidder_error called', function() { - let warnStub = sandbox.stub(utils, 'logWarn'); + const warnStub = sandbox.stub(utils, 'logWarn'); expect(requests).to.have.lengthOf(0); expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(0); expect(warnStub.calledOnce).to.be.false; - events.emit(constants.EVENTS.BIDDER_ERROR, {'error': XHR_ERROR_MOCK, 'bidderRequest': MOCK_BID_REQUEST}); + events.emit(EVENTS.BIDDER_ERROR, { error: XHR_ERROR_MOCK, bidderRequest: MOCK_BID_REQUEST }); expect(Object.keys(cnvrHelper.bidderErrorCache)).to.have.lengthOf(1); expect(warnStub.calledOnce).to.be.true; @@ -1105,7 +1105,7 @@ describe('Conversant analytics adapter tests', function() { expect(errorObj.errors[0].bidderCode).to.equal(MOCK_BID_REQUEST.bidderCode); expect(errorObj.errors[0].url).to.not.be.undefined; - events.emit(constants.EVENTS.BIDDER_ERROR, {'error': XHR_ERROR_MOCK, 'bidderRequest': MOCK_BID_REQUEST}); + events.emit(EVENTS.BIDDER_ERROR, { error: XHR_ERROR_MOCK, bidderRequest: MOCK_BID_REQUEST }); errorObj = cnvrHelper.bidderErrorCache[MOCK_BID_REQUEST.auctionId]; expect(errorObj.errors).to.have.lengthOf(2); }); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index fa44b7daa7a..e96867f4e84 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -13,7 +13,7 @@ import { responseReady } from 'modules/currency.js'; import {createBid} from '../../../src/bidfactory.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS, STATUS, REJECTION_REASON } from '../../../src/constants.js'; import {server} from '../../mocks/xhr.js'; import * as events from 'src/events.js'; @@ -28,7 +28,7 @@ describe('currency', function () { let fn = sinon.spy(); function makeBid(bidProps) { - return Object.assign(createBid(CONSTANTS.STATUS.GOOD), bidProps); + return Object.assign(createBid(STATUS.GOOD), bidProps); } beforeEach(function () { @@ -335,7 +335,7 @@ describe('currency', function () { addBidResponseHook(addBidResponse, 'au', bid, reject); fakeCurrencyFileServer.respond(); sinon.assert.notCalled(addBidResponse); - sinon.assert.calledWith(reject, CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + sinon.assert.calledWith(reject, REJECTION_REASON.CANNOT_CONVERT_CURRENCY); }); it('attempts to load rates again on the next auction', () => { @@ -344,7 +344,7 @@ describe('currency', function () { }); fakeCurrencyFileServer.respond(); fakeCurrencyFileServer.respondWith(JSON.stringify(getCurrencyRates())); - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {}); + events.emit(EVENTS.AUCTION_INIT, {}); addBidResponseHook(addBidResponse, 'au', bid, reject); fakeCurrencyFileServer.respond(); sinon.assert.calledWith(addBidResponse, 'au', bid, reject); @@ -462,12 +462,12 @@ describe('currency', function () { const addBidResponse = sinon.spy(); addBidResponseHook(addBidResponse, 'au', bid, reject); addBidResponseHook(addBidResponse, 'au', noConversionBid, reject); - events.emit(CONSTANTS.EVENTS.AUCTION_TIMEOUT, {auctionId: 'aid'}); + events.emit(EVENTS.AUCTION_TIMEOUT, { auctionId: 'aid' }); fakeCurrencyFileServer.respond(); sinon.assert.calledOnce(addBidResponse); sinon.assert.calledWith(addBidResponse, 'au', noConversionBid, reject); sinon.assert.calledOnce(reject); - sinon.assert.calledWith(reject, CONSTANTS.REJECTION_REASON.CANNOT_CONVERT_CURRENCY); + sinon.assert.calledWith(reject, REJECTION_REASON.CANNOT_CONVERT_CURRENCY); }) it('should return 1 when currency support is enabled and same currency code is requested as is set to adServerCurrency', function () { diff --git a/test/spec/modules/dsaControl_spec.js b/test/spec/modules/dsaControl_spec.js index 0d7c52b5efd..45392d58c04 100644 --- a/test/spec/modules/dsaControl_spec.js +++ b/test/spec/modules/dsaControl_spec.js @@ -1,5 +1,5 @@ import {addBidResponseHook, setMetaDsa, reset} from '../../../modules/dsaControl.js'; -import CONSTANTS from 'src/constants.json'; +import { REJECTION_REASON } from 'src/constants.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {AuctionIndex} from '../../../src/auctionIndex.js'; @@ -51,7 +51,7 @@ describe('DSA transparency', () => { }); it('should reject bids that have no meta.dsa', () => { - expectRejection(CONSTANTS.REJECTION_REASON.DSA_REQUIRED); + expectRejection(REJECTION_REASON.DSA_REQUIRED); }); it('should accept bids that do', () => { @@ -66,7 +66,7 @@ describe('DSA transparency', () => { it('should reject bids with adrender = 0 (advertiser will not render)', () => { bid.meta = {dsa: {adrender: 0}}; - expectRejection(CONSTANTS.REJECTION_REASON.DSA_MISMATCH); + expectRejection(REJECTION_REASON.DSA_MISMATCH); }); it('should accept bids with adrender = 1 (advertiser will render)', () => { @@ -81,7 +81,7 @@ describe('DSA transparency', () => { it('should reject bids with adrender = 1 (advertiser will render)', () => { bid.meta = {dsa: {adrender: 1}}; - expectRejection(CONSTANTS.REJECTION_REASON.DSA_MISMATCH); + expectRejection(REJECTION_REASON.DSA_MISMATCH); }); it('should accept bids with adrender = 0 (advertiser will not render)', () => { diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js index 419181de983..dddc248b409 100644 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ b/test/spec/modules/eplanningAnalyticsAdapter_spec.js @@ -3,9 +3,10 @@ import {includes} from 'src/polyfill.js'; import { expect } from 'chai'; import { parseUrl } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); describe('eplanning analytics adapter', function () { beforeEach(function () { @@ -82,22 +83,22 @@ describe('eplanning analytics adapter', function () { // Emit the events with the "real" arguments // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { auctionId: pauctionId, timestamp: auctionTimestamp }); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); // Step 5: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, { + events.emit(EVENTS.BID_WON, { adId: 'adIdData', ad: 'adContent', auctionId: pauctionId, @@ -106,7 +107,7 @@ describe('eplanning analytics adapter', function () { }); // Step 6: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {auctionId: pauctionId}); + events.emit(EVENTS.AUCTION_END, { auctionId: pauctionId }); // Step 7: Find the request data sent (filtering other hosts) let requests = server.requests.filter(req => { @@ -127,22 +128,28 @@ describe('eplanning analytics adapter', function () { // Step 9 verify that we only receive the parameters we need let expectedEventValues = [ // AUCTION INIT - {ec: constants.EVENTS.AUCTION_INIT, + { + ec: EVENTS.AUCTION_INIT, p: {auctionId: pauctionId, time: auctionTimestamp}}, // BID REQ - {ec: constants.EVENTS.BID_REQUESTED, + { + ec: EVENTS.BID_REQUESTED, p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, // BID RESP - {ec: constants.EVENTS.BID_RESPONSE, + { + ec: EVENTS.BID_RESPONSE, p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, // BID T.O. - {ec: constants.EVENTS.BID_TIMEOUT, + { + ec: EVENTS.BID_TIMEOUT, p: [{auctionId: pauctionId, bidder: pbidderCode}]}, // BID WON - {ec: constants.EVENTS.BID_WON, + { + ec: EVENTS.BID_WON, p: {auctionId: pauctionId, size: '300x250'}}, // AUCTION END - {ec: constants.EVENTS.AUCTION_END, + { + ec: EVENTS.AUCTION_END, p: {auctionId: pauctionId}} ]; diff --git a/test/spec/modules/fintezaAnalyticsAdapter_spec.js b/test/spec/modules/fintezaAnalyticsAdapter_spec.js index cddffc63554..1e4c5cbcdd3 100644 --- a/test/spec/modules/fintezaAnalyticsAdapter_spec.js +++ b/test/spec/modules/fintezaAnalyticsAdapter_spec.js @@ -3,10 +3,10 @@ import {includes} from 'src/polyfill.js'; import { expect } from 'chai'; import { parseUrl } from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); function setCookie(name, value, expires) { document.cookie = name + '=' + value + @@ -74,7 +74,7 @@ describe('finteza analytics adapter', function () { }; // Emit the events with the "real" arguments - events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + events.emit(EVENTS.BID_REQUESTED, bidRequest); expect(server.requests.length).to.equal(1); @@ -117,7 +117,7 @@ describe('finteza analytics adapter', function () { }; // Emit the events with the "real" arguments - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); @@ -171,7 +171,7 @@ describe('finteza analytics adapter', function () { } // Emit the events with the "real" arguments - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); @@ -211,7 +211,7 @@ describe('finteza analytics adapter', function () { ]; // Emit the events with the "real" arguments - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); expect(server.requests[0].method).to.equal('GET'); expect(server.requests[0].withCredentials).to.equal(true); diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index 79874f5d756..2d9c7b4ae45 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -1,8 +1,8 @@ import {defaultHandler, GenericAnalytics} from '../../../modules/genericAnalyticsAdapter.js'; import * as events from 'src/events.js'; -import * as CONSTANTS from 'src/constants.json'; +import {EVENTS} from 'src/constants.js'; -const {AUCTION_INIT, BID_RESPONSE} = CONSTANTS.EVENTS; +const {AUCTION_INIT, BID_RESPONSE} = EVENTS; describe('Generic analytics', () => { describe('adapter', () => { diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index 211a3efa3c6..ecc24bce6b5 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -1,11 +1,11 @@ import * as utils from '../../../src/utils.js'; -import {loadExternalScript} from '../../../src/adloader.js'; +import { loadExternalScript } from '../../../src/adloader.js'; import * as geoedgeRtdModule from '../../../modules/geoedgeRtdProvider.js'; -import {server} from '../../../test/mocks/xhr.js'; +import { server } from '../../../test/mocks/xhr.js'; import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; -let { +const { geoedgeSubmodule, getClientUrl, getInPageUrl, @@ -17,35 +17,35 @@ let { markAsLoaded } = geoedgeRtdModule; -let key = '123123123'; +const key = '123123123'; function makeConfig(gpt) { return { name: 'geoedge', params: { wap: false, - key: key, + key, bidders: { bidderA: true, bidderB: false }, - gpt: gpt + gpt } }; } function mockBid(bidderCode) { return { - 'ad': '', - 'adId': '1234', - 'cpm': '1.00', - 'width': 300, - 'height': 250, - 'bidderCode': bidderCode, - 'requestId': utils.getUniqueIdentifierStr(), - 'creativeId': 'id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360 + ad: '', + adId: '1234', + cpm: '1.00', + width: 300, + height: 250, + bidderCode, + requestId: utils.getUniqueIdentifierStr(), + creativeId: 'id', + currency: 'USD', + netRevenue: true, + ttl: 360 }; } @@ -58,7 +58,7 @@ function mockMessageFromClient(key) { }; } -let mockWrapper = `${htmlPlaceholder}`; +const mockWrapper = `${htmlPlaceholder}`; describe('Geoedge RTD module', function () { describe('submodule', function () { @@ -75,8 +75,8 @@ describe('Geoedge RTD module', function () { geoedgeRtdModule.preloadClient.restore(); }); it('should return false when missing params or key', function () { - let missingParams = geoedgeSubmodule.init({}); - let missingKey = geoedgeSubmodule.init({ params: {} }); + const missingParams = geoedgeSubmodule.init({}); + const missingKey = geoedgeSubmodule.init({ params: {} }); expect(missingParams || missingKey).to.equal(false); }); it('should return true when params are ok', function () { @@ -84,8 +84,8 @@ describe('Geoedge RTD module', function () { }); it('should fetch the wrapper', function () { geoedgeSubmodule.init(makeConfig(false)); - let request = server.requests[0]; - let isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; + const request = server.requests[0]; + const isWrapperRequest = request && request.url && request.url && request.url === WRAPPER_URL; expect(isWrapperRequest).to.equal(true); }); it('should call preloadClient', function () { @@ -93,7 +93,7 @@ describe('Geoedge RTD module', function () { }); it('should emit billable events with applicable winning bids', function (done) { let counter = 0; - events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { + events.on(EVENTS.BILLABLE_EVENT, function (event) { if (event.vendor === geoedgeSubmodule.name && event.type === 'impression') { counter += 1; } @@ -104,28 +104,28 @@ describe('Geoedge RTD module', function () { }); it('should load the in page code when gpt params is true', function () { geoedgeSubmodule.init(makeConfig(true)); - let isInPageUrl = arg => arg === getInPageUrl(key); + const isInPageUrl = arg => arg === getInPageUrl(key); expect(loadExternalScript.calledWith(sinon.match(isInPageUrl))).to.equal(true); }); it('should set the window.grumi config object when gpt params is true', function () { - let hasGrumiObj = typeof window.grumi === 'object'; + const hasGrumiObj = typeof window.grumi === 'object'; expect(hasGrumiObj && window.grumi.key === key && window.grumi.fromPrebid).to.equal(true); }); }); describe('preloadClient', function () { let iframe; preloadClient(key); - let loadExternalScriptCall = loadExternalScript.getCall(0); + const loadExternalScriptCall = loadExternalScript.getCall(0); it('should create an invisible iframe and insert it to the DOM', function () { iframe = document.getElementById('grumiFrame'); expect(iframe && iframe.style.display === 'none'); }); it('should assign params object to the iframe\'s window', function () { - let grumi = iframe.contentWindow.grumi; + const grumi = iframe.contentWindow.grumi; expect(grumi.key).to.equal(key); }); it('should preload the client into the iframe', function () { - let isClientUrl = arg => arg === getClientUrl(key); + const isClientUrl = arg => arg === getClientUrl(key); expect(loadExternalScriptCall.calledWithMatch(isClientUrl)).to.equal(true); }); }); @@ -137,28 +137,28 @@ describe('Geoedge RTD module', function () { }); describe('getMacros', function () { it('return a dictionary of macros replaced with values from bid object', function () { - let bid = mockBid('testBidder'); - let dict = getMacros(bid, key); - let hasCpm = dict['%_hbCpm!'] === bid.cpm; - let hasCurrency = dict['%_hbCurrency!'] === bid.currency; + const bid = mockBid('testBidder'); + const dict = getMacros(bid, key); + const hasCpm = dict['%_hbCpm!'] === bid.cpm; + const hasCurrency = dict['%_hbCurrency!'] === bid.currency; expect(hasCpm && hasCurrency); }); }); describe('onBidResponseEvent', function () { - let bidFromA = mockBid('bidderA'); + const bidFromA = mockBid('bidderA'); it('should wrap bid html when bidder is configured', function () { geoedgeSubmodule.onBidResponseEvent(bidFromA, makeConfig(false)); expect(bidFromA.ad.indexOf('')).to.equal(0); }); it('should not wrap bid html when bidder is not configured', function () { - let bidFromB = mockBid('bidderB'); + const bidFromB = mockBid('bidderB'); geoedgeSubmodule.onBidResponseEvent(bidFromB, makeConfig(false)); expect(bidFromB.ad.indexOf('')).to.equal(-1); }); it('should only muatate the bid ad porperty', function () { - let copy = Object.assign({}, bidFromA); + const copy = Object.assign({}, bidFromA); delete copy.ad; - let equalsOriginal = Object.keys(copy).every(key => copy[key] === bidFromA[key]); + const equalsOriginal = Object.keys(copy).every(key => copy[key] === bidFromA[key]); expect(equalsOriginal).to.equal(true); }); }); diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 7b68b0dea46..efaeb06ae53 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -10,7 +10,7 @@ import {expect} from 'chai'; import sinon from 'sinon'; const events = require('src/events'); -const constants = require('src/constants.json'); +const constants = require('src/constants.js'); const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; diff --git a/test/spec/modules/greenbidsRtdProvider_spec.js b/test/spec/modules/greenbidsRtdProvider_spec.js index d0083d4dc7a..ae63a0b00a0 100644 --- a/test/spec/modules/greenbidsRtdProvider_spec.js +++ b/test/spec/modules/greenbidsRtdProvider_spec.js @@ -8,7 +8,7 @@ import { } from 'modules/greenbidsRtdProvider.js'; import { server } from '../../mocks/xhr.js'; import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; describe('greenbidsRtdProvider', () => { const endPoint = 't.greenbids.ai'; @@ -332,7 +332,7 @@ describe('greenbidsRtdProvider', () => { it('should emit billable event if greenbids has set the adunit.ext value', function (done) { let counter = 0; - events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, function (event) { + events.on(EVENTS.BILLABLE_EVENT, function (event) { if (event.vendor === 'greenbidsRtdProvider' && event.type === 'auction') { counter += 1; } diff --git a/test/spec/modules/growthCodeAnalyticsAdapter_spec.js b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js index cd9c12a729c..266bc104fd8 100644 --- a/test/spec/modules/growthCodeAnalyticsAdapter_spec.js +++ b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import adapterManager from '../../../src/adapterManager.js'; import growthCodeAnalyticsAdapter from '../../../modules/growthCodeAnalyticsAdapter.js'; import { expect } from 'chai'; import * as events from '../../../src/events.js'; -import constants from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { generateUUID } from '../../../src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -58,7 +58,7 @@ describe('growthCode analytics adapter', () => { adUnitCodes: ['usr1234'] }], }; - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); assert(server.requests.length > 0) const body = JSON.parse(server.requests[0].requestBody); var eventTypes = []; diff --git a/test/spec/modules/hadronAnalyticsAdapter_spec.js b/test/spec/modules/hadronAnalyticsAdapter_spec.js index bea131fb78f..68e5bc3aecb 100644 --- a/test/spec/modules/hadronAnalyticsAdapter_spec.js +++ b/test/spec/modules/hadronAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import adapterManager from '../../../src/adapterManager.js'; import hadronAnalyticsAdapter from '../../../modules/hadronAnalyticsAdapter.js'; import { expect } from 'chai'; import * as events from '../../../src/events.js'; -import constants from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { generateUUID } from '../../../src/utils.js'; import { server } from 'test/mocks/xhr.js'; @@ -48,13 +48,13 @@ describe('Hadron analytics adapter', () => { adUnitCodes: ['usr1234'] }], }; - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); assert(server.requests.length > 0) const body = JSON.parse(server.requests[0].requestBody); var eventTypes = []; body.events.forEach(e => eventTypes.push(e.eventType)); assert(eventTypes.length > 0) - assert(eventTypes.indexOf(constants.EVENTS.AUCTION_END) > -1); + assert(eventTypes.indexOf(EVENTS.AUCTION_END) > -1); hadronAnalyticsAdapter.disableAnalytics(); }); }); diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js index 9cb7233ce7c..c9d21daa4e0 100644 --- a/test/spec/modules/id5AnalyticsAdapter_spec.js +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import adapterManager from '../../../src/adapterManager.js'; import id5AnalyticsAdapter from '../../../modules/id5AnalyticsAdapter.js'; import { expect } from 'chai'; import * as events from '../../../src/events.js'; -import constants from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { generateUUID } from '../../../src/utils.js'; import {server} from '../../mocks/xhr.js'; @@ -98,7 +98,7 @@ describe('ID5 analytics adapter', () => { it('sends auction end events to the backend', () => { id5AnalyticsAdapter.enableAnalytics(config); server.respond(); - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); server.respond(); // Why 3? 1: config, 2: tcfEnforcement, 3: auctionEnd @@ -307,7 +307,7 @@ describe('ID5 analytics adapter', () => { id5AnalyticsAdapter.enableAnalytics(config); server.respond(); - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); server.respond(); expect(server.requests).to.have.length(3); @@ -360,7 +360,7 @@ describe('ID5 analytics adapter', () => { ]); id5AnalyticsAdapter.enableAnalytics(config); server.respond(); - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); server.respond(); expect(server.requests).to.have.length(2); @@ -441,7 +441,7 @@ describe('ID5 analytics adapter', () => { ]); id5AnalyticsAdapter.enableAnalytics(config); server.respond(); - events.emit(constants.EVENTS.AUCTION_END, auction); + events.emit(EVENTS.AUCTION_END, auction); server.respond(); expect(server.requests).to.have.length(3); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index af468f2fe4d..fd5d24295f5 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -8,7 +8,7 @@ import { } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import * as utils from '../../../src/utils.js'; import {uspDataHandler, gppDataHandler} from '../../../src/adapterManager.js'; import '../../../src/prebid.js'; @@ -1004,7 +1004,7 @@ describe('ID5 ID System', function () { }, {adUnits}); }).then(() => { expect(xhrServerMock.hasReceivedAnyRequest()).is.false; - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); return xhrServerMock.expectFetchRequest(); }).then(request => { const requestBody = JSON.parse(request.requestBody); diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index a8828515ffd..e866d2404f3 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -2,8 +2,8 @@ import invisiblyAdapter from 'modules/invisiblyAnalyticsAdapter.js'; import { expect } from 'chai'; import {expectEvents} from '../../helpers/analytics.js'; import {server} from '../../mocks/xhr.js'; +import { EVENTS, STATUS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('Invisibly Analytics Adapter test suite', function () { let xhr; @@ -26,7 +26,7 @@ describe('Invisibly Analytics Adapter test suite', function () { hb_source: 'client', }, getStatusCode() { - return CONSTANTS.STATUS.GOOD; + return STATUS.GOOD; }, }; @@ -54,7 +54,7 @@ describe('Invisibly Analytics Adapter test suite', function () { hb_source: 'server', }, getStatusCode() { - return CONSTANTS.STATUS.GOOD; + return STATUS.GOOD; }, }; @@ -204,11 +204,11 @@ describe('Invisibly Analytics Adapter test suite', function () { options: {}, }); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(EVENTS.BID_WON, MOCK.BID_WON); invisiblyAdapter.flush(); sinon.assert.callCount(invisiblyAdapter.track, 0); }); @@ -230,7 +230,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for auction init event it('auction init event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -252,7 +252,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bid adjustment event it('bid adjustment event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); + events.emit(EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -274,7 +274,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bid timeout event it('bid timeout event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -296,7 +296,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bid requested event it('bid requested event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -318,7 +318,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bid response event it('bid response event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -338,7 +338,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for no bid event it('no bid event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); + events.emit(EVENTS.NO_BID, MOCK.NO_BID); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -360,7 +360,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bid won event it('bid won event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + events.emit(EVENTS.BID_WON, MOCK.BID_WON); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -378,7 +378,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for bidder done event it('bidder done event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -403,7 +403,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for set targeting event it('set targeting event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); + events.emit(EVENTS.SET_TARGETING, MOCK.SET_TARGETING); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -428,7 +428,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for request bids event it('request bids event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); + events.emit(EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -450,7 +450,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for add ad units event it('add ad units event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); + events.emit(EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -472,7 +472,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for ad render failed event it('ad render failed event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); + events.emit(EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -494,7 +494,7 @@ describe('Invisibly Analytics Adapter test suite', function () { // spec for auction end event it('auction end event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END); invisiblyAdapter.flush(); const invisiblyEvents = JSON.parse( @@ -516,7 +516,7 @@ describe('Invisibly Analytics Adapter test suite', function () { it('it should not call sendEvent for this event emit', function () { sinon.spy(invisiblyAdapter, 'sendEvent'); invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(constants.EVENTS.INVALID_EVENT, MOCK.INVALID_EVENT); + events.emit(EVENTS.INVALID_EVENT, MOCK.INVALID_EVENT); invisiblyAdapter.flush(); expect(requests.length).to.equal(0); @@ -529,19 +529,19 @@ describe('Invisibly Analytics Adapter test suite', function () { invisiblyAdapter.enableAnalytics(MOCK.config); expectEvents([ - constants.EVENTS.AUCTION_INIT, - constants.EVENTS.AUCTION_END, - constants.EVENTS.BID_ADJUSTMENT, - constants.EVENTS.BID_TIMEOUT, - constants.EVENTS.BID_REQUESTED, - constants.EVENTS.BID_RESPONSE, - constants.EVENTS.NO_BID, - constants.EVENTS.BID_WON, - constants.EVENTS.BIDDER_DONE, - constants.EVENTS.SET_TARGETING, - constants.EVENTS.REQUEST_BIDS, - constants.EVENTS.ADD_AD_UNITS, - constants.EVENTS.AD_RENDER_FAILED + EVENTS.AUCTION_INIT, + EVENTS.AUCTION_END, + EVENTS.BID_ADJUSTMENT, + EVENTS.BID_TIMEOUT, + EVENTS.BID_REQUESTED, + EVENTS.BID_RESPONSE, + EVENTS.NO_BID, + EVENTS.BID_WON, + EVENTS.BIDDER_DONE, + EVENTS.SET_TARGETING, + EVENTS.REQUEST_BIDS, + EVENTS.ADD_AD_UNITS, + EVENTS.AD_RENDER_FAILED ]).to.beTrackedBy(invisiblyAdapter.track); }); }); @@ -558,11 +558,11 @@ describe('Invisibly Analytics Adapter test suite', function () { }, }); - events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); - events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); - events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); - events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(EVENTS.BID_WON, MOCK.BID_WON); invisiblyAdapter.flush(); sinon.assert.callCount(invisiblyAdapter.sendEvent, 0); diff --git a/test/spec/modules/kargoAnalyticsAdapter_spec.js b/test/spec/modules/kargoAnalyticsAdapter_spec.js index c27c8499aa1..c2acd86defa 100644 --- a/test/spec/modules/kargoAnalyticsAdapter_spec.js +++ b/test/spec/modules/kargoAnalyticsAdapter_spec.js @@ -1,8 +1,8 @@ import kargoAnalyticsAdapter from 'modules/kargoAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('Kargo Analytics Adapter', function () { const adapterConfig = { @@ -30,10 +30,10 @@ describe('Kargo Analytics Adapter', function () { timeToRespond: 192, }; - events.emit(constants.EVENTS.AUCTION_INIT, { + events.emit(EVENTS.AUCTION_INIT, { timeout: 1000 }); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.BID_RESPONSE, bidResponse); expect(server.requests.length).to.equal(1); expect(server.requests[0].url).to.equal('https://krk.kargo.com/api/v1/event/auction-data?aid=66529d4c-8998-47c2-ab3e-5b953490b98f&ato=1000&rt=192&it=0'); diff --git a/test/spec/modules/konduitAnalyticsAdapter_spec.js b/test/spec/modules/konduitAnalyticsAdapter_spec.js index e79ae2feeeb..496dd171afa 100644 --- a/test/spec/modules/konduitAnalyticsAdapter_spec.js +++ b/test/spec/modules/konduitAnalyticsAdapter_spec.js @@ -2,19 +2,19 @@ import konduitAnalyticsAdapter from 'modules/konduitAnalyticsAdapter'; import { expect } from 'chai'; import { config } from '../../../src/config.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let CONSTANTS = require('src/constants.json'); const eventsData = { - [CONSTANTS.EVENTS.AUCTION_INIT]: { + [EVENTS.AUCTION_INIT]: { 'auctionId': 'test_auction_id', 'timestamp': Date.now(), 'auctionStatus': 'inProgress', 'adUnitCodes': ['video-test'], 'timeout': 700 }, - [CONSTANTS.EVENTS.BID_REQUESTED]: { + [EVENTS.BID_REQUESTED]: { 'bidderCode': 'test_bidder_code', 'time': Date.now(), 'bids': [{ @@ -25,13 +25,13 @@ const eventsData = { 'params': { 'testParam': 'test_param' } }] }, - [CONSTANTS.EVENTS.NO_BID]: { + [EVENTS.NO_BID]: { 'bidderCode': 'test_bidder_code2', 'transactionId': 'test_transaction_id', 'adUnitCode': 'video-test', 'bidId': 'test_bid_id' }, - [CONSTANTS.EVENTS.BID_RESPONSE]: { + [EVENTS.BID_RESPONSE]: { 'bidderCode': 'test_bidder_code', 'adUnitCode': 'video-test', 'statusMessage': 'Bid available', @@ -44,7 +44,7 @@ const eventsData = { 'requestId': 'test_request_id', 'creativeId': 144876543 }, - [CONSTANTS.EVENTS.AUCTION_END]: { + [EVENTS.AUCTION_END]: { 'auctionId': 'test_auction_id', 'timestamp': Date.now(), 'auctionEnd': Date.now() + 400, @@ -52,7 +52,7 @@ const eventsData = { 'adUnitCodes': ['video-test'], 'timeout': 700 }, - [CONSTANTS.EVENTS.BID_WON]: { + [EVENTS.BID_WON]: { 'bidderCode': 'test_bidder_code', 'adUnitCode': 'video-test', 'statusMessage': 'Bid available', @@ -99,12 +99,12 @@ describe(`Konduit Analytics Adapter`, () => { expect(konduitAnalyticsAdapter.context.aggregatedEvents).to.be.an('array'); const eventTypes = [ - CONSTANTS.EVENTS.AUCTION_INIT, - CONSTANTS.EVENTS.BID_REQUESTED, - CONSTANTS.EVENTS.NO_BID, - CONSTANTS.EVENTS.BID_RESPONSE, - CONSTANTS.EVENTS.BID_WON, - CONSTANTS.EVENTS.AUCTION_END, + EVENTS.AUCTION_INIT, + EVENTS.BID_REQUESTED, + EVENTS.NO_BID, + EVENTS.BID_RESPONSE, + EVENTS.BID_WON, + EVENTS.AUCTION_END, ]; const args = eventTypes.map(eventType => eventsData[eventType]); diff --git a/test/spec/modules/lemmaDigitalBidAdapter_spec.js b/test/spec/modules/lemmaDigitalBidAdapter_spec.js index d50728dce3c..ab4c3259671 100644 --- a/test/spec/modules/lemmaDigitalBidAdapter_spec.js +++ b/test/spec/modules/lemmaDigitalBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/lemmaDigitalBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; -const constants = require('src/constants.json'); +const constants = require('src/constants.js'); describe('lemmaDigitalBidAdapter', function () { let bidRequests; diff --git a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js index d00bfbc7bb5..e833440bf03 100644 --- a/test/spec/modules/liveIntentAnalyticsAdapter_spec.js +++ b/test/spec/modules/liveIntentAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; import { auctionManager } from 'src/auctionManager.js'; import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; let utils = require('src/utils'); let refererDetection = require('src/refererDetection'); @@ -13,7 +14,6 @@ let clock; let now = new Date(); let events = require('src/events'); -let constants = require('src/constants.json'); let auctionId = '99abbc81-c1f1-41cd-8f25-f7149244c897' const configWithSamplingAll = { @@ -286,7 +286,7 @@ describe('LiveIntent Analytics Adapter ', () => { sandbox.stub(utils, 'generateUUID').returns(instanceId); sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); - events.emit(constants.EVENTS.AUCTION_END, args); + events.emit(EVENTS.AUCTION_END, args); clock.tick(2000); expect(server.requests.length).to.equal(1); @@ -305,7 +305,7 @@ describe('LiveIntent Analytics Adapter ', () => { sandbox.stub(utils, 'generateUUID').returns(instanceId); sandbox.stub(refererDetection, 'getRefererInfo').returns({page: url}); sandbox.stub(auctionManager.index, 'getAuction').withArgs(auctionId).returns({ getWinningBids: () => winningBids }); - events.emit(constants.EVENTS.AUCTION_END, args); + events.emit(EVENTS.AUCTION_END, args); clock.tick(2000); expect(server.requests.length).to.equal(0); }); diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index d07b48752c6..f82cc4c4f52 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ import livewrappedAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/livewrappedAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; +import { AD_RENDER_FAILED_REASON, EVENTS, STATUS } from 'src/constants.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import { setConfig } from 'modules/currency.js'; @@ -9,21 +9,16 @@ let utils = require('src/utils'); let adapterManager = require('src/adapterManager').default; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - SET_TARGETING, - AD_RENDER_FAILED - }, - STATUS: { - GOOD - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING, + AD_RENDER_FAILED +} = EVENTS; const BID1 = { width: 980, @@ -43,7 +38,7 @@ const BID1 = { }, dealId: 'dealid', getStatusCode() { - return CONSTANTS.STATUS.GOOD; + return STATUS.GOOD; } }; @@ -71,7 +66,7 @@ const BID3 = { auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', mediaType: 'banner', getStatusCode() { - return CONSTANTS.STATUS.GOOD; + return STATUS.GOOD; } }; @@ -135,7 +130,7 @@ const MOCK = { AD_RENDER_FAILED: [ { 'bidId': '2ecff0db240757', - 'reason': CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + 'reason': AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, 'message': 'message', 'bid': BID1 } @@ -275,7 +270,7 @@ const ANALYTICS_MESSAGE = { adUnitId: 'adunitid', bidder: 'livewrapped', auctionId: 0, - rsn: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + rsn: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, msg: 'message' }, ] diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 397ee4a8577..1798c96c341 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -6,7 +6,7 @@ import magniteAdapter, { detectBrowserFromUa, callPrebidCacheHook } from '../../../modules/magniteAnalyticsAdapter.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import * as mockGpt from '../integration/faker/googletag.js'; @@ -17,19 +17,17 @@ let events = require('src/events.js'); let utils = require('src/utils.js'); const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - BILLABLE_EVENT, - SEAT_NON_BID, - BID_REJECTED - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + BILLABLE_EVENT, + SEAT_NON_BID, + BID_REJECTED +} = EVENTS; const STUBBED_UUID = '12345678-1234-1234-1234-123456789abc'; diff --git a/test/spec/modules/malltvAnalyticsAdapter_spec.js b/test/spec/modules/malltvAnalyticsAdapter_spec.js index c96069df0f9..2be9fe4b09f 100644 --- a/test/spec/modules/malltvAnalyticsAdapter_spec.js +++ b/test/spec/modules/malltvAnalyticsAdapter_spec.js @@ -5,7 +5,7 @@ import { import { expect } from 'chai' import { getCpmInEur } from '../../../modules/malltvAnalyticsAdapter' import * as events from 'src/events' -import constants from 'src/constants.json' +import { EVENTS } from 'src/constants.js' const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e' const propertyId = '123456' @@ -481,14 +481,14 @@ describe('Malltv Prebid AnalyticsAdapter Testing', function () { it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { sinon.spy(malltvAnalyticsAdapter, 'handleBidTimeout') - events.emit(constants.EVENTS.BID_TIMEOUT, {}) + events.emit(EVENTS.BID_TIMEOUT, {}) sinon.assert.callCount(malltvAnalyticsAdapter.handleBidTimeout, 1) malltvAnalyticsAdapter.handleBidTimeout.restore() }) it('should call handleAuctionEnd as AUCTION_END trigger event', function() { sinon.spy(malltvAnalyticsAdapter, 'handleAuctionEnd') - events.emit(constants.EVENTS.AUCTION_END, {}) + events.emit(EVENTS.AUCTION_END, {}) sinon.assert.callCount(malltvAnalyticsAdapter.handleAuctionEnd, 1) malltvAnalyticsAdapter.handleAuctionEnd.restore() }) diff --git a/test/spec/modules/mediafilterRtdProvider_spec.js b/test/spec/modules/mediafilterRtdProvider_spec.js index 3395c7be691..fe9fb855629 100644 --- a/test/spec/modules/mediafilterRtdProvider_spec.js +++ b/test/spec/modules/mediafilterRtdProvider_spec.js @@ -1,7 +1,7 @@ import * as utils from '../../../src/utils.js'; import * as hook from '../../../src/hook.js' import * as events from '../../../src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import { MediaFilter, @@ -121,7 +121,7 @@ describe('The Media Filter RTD module', function () { eventHandler(mockEvent); - expect(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BILLABLE_EVENT, { + expect(eventsEmitSpy.calledWith(EVENTS.BILLABLE_EVENT, { 'billingId': sinon.match.string, 'configurationHash': configurationHash, 'type': 'impression', diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index e19c27cc2d3..7c3cf88dace 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -1,13 +1,13 @@ import { expect } from 'chai'; import medianetAnalytics from 'modules/medianetAnalyticsAdapter.js'; import * as utils from 'src/utils.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import * as events from 'src/events.js'; import {clearEvents} from 'src/events.js'; const { - EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON } -} = CONSTANTS; + AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, NO_BID, BID_TIMEOUT, AUCTION_END, SET_TARGETING, BID_WON +} = EVENTS; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; diff --git a/test/spec/modules/nobidAnalyticsAdapter_spec.js b/test/spec/modules/nobidAnalyticsAdapter_spec.js index f6c741bb7ff..07823d3536f 100644 --- a/test/spec/modules/nobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/nobidAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ import nobidAnalytics from 'modules/nobidAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); const TOP_LOCATION = 'https://www.somesite.com'; const SITE_ID = 1234; @@ -46,7 +46,7 @@ describe('NoBid Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, + events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now(), bidderRequests: [{refererInfo: {topmostLocation: TOP_LOCATION}}]}); @@ -77,27 +77,27 @@ describe('NoBid Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, + events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now(), bidderRequests: [{refererInfo: {topmostLocation: TOP_LOCATION}}]}); - events.emit(constants.EVENTS.BID_WON, {}); + events.emit(EVENTS.BID_WON, {}); clock.tick(5000); expect(server.requests).to.have.length(1); - events.emit(constants.EVENTS.BID_REQUESTED, {}); + events.emit(EVENTS.BID_REQUESTED, {}); clock.tick(5000); expect(server.requests).to.have.length(1); - events.emit(constants.EVENTS.BID_RESPONSE, {}); + events.emit(EVENTS.BID_RESPONSE, {}); clock.tick(5000); expect(server.requests).to.have.length(1); - events.emit(constants.EVENTS.BID_TIMEOUT, {}); + events.emit(EVENTS.BID_TIMEOUT, {}); clock.tick(5000); expect(server.requests).to.have.length(1); - events.emit(constants.EVENTS.AD_RENDER_SUCCEEDED, {}); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, {}); clock.tick(5000); expect(server.requests).to.have.length(1); @@ -191,13 +191,13 @@ describe('NoBid Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, + events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now(), bidderRequests: [{refererInfo: {topmostLocation: TOP_LOCATION}}]}); // Step 3: Send bid won event - events.emit(constants.EVENTS.BID_WON, requestIncoming); + events.emit(EVENTS.BID_WON, requestIncoming); clock.tick(5000); expect(server.requests).to.have.length(1); const bidWonRequest = JSON.parse(server.requests[0].requestBody); @@ -388,13 +388,13 @@ describe('NoBid Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, + events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now(), bidderRequests: [{refererInfo: {topmostLocation: `${TOP_LOCATION}_something`}}]}); // Step 3: Send bid won event - events.emit(constants.EVENTS.AUCTION_END, requestIncoming); + events.emit(EVENTS.AUCTION_END, requestIncoming); clock.tick(5000); expect(server.requests).to.have.length(1); const auctionEndRequest = JSON.parse(server.requests[0].requestBody); @@ -428,22 +428,22 @@ describe('NoBid Prebid Analytic', function () { nobidAnalytics.processServerResponse(JSON.stringify({disabled: 0})); disabled = nobidAnalytics.isAnalyticsDisabled(); expect(disabled).to.equal(false); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '1234567890'}); + events.emit(EVENTS.AUCTION_END, {auctionId: '1234567890'}); clock.tick(1000); expect(server.requests).to.have.length(1); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '12345678901'}); + events.emit(EVENTS.AUCTION_END, {auctionId: '12345678901'}); clock.tick(1000); expect(server.requests).to.have.length(2); nobidAnalytics.processServerResponse('disabled: true'); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '12345678902'}); + events.emit(EVENTS.AUCTION_END, {auctionId: '12345678902'}); clock.tick(1000); expect(server.requests).to.have.length(3); nobidAnalytics.processServerResponse(JSON.stringify({disabled: 1})); disabled = nobidAnalytics.isAnalyticsDisabled(); expect(disabled).to.equal(true); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '12345678902'}); + events.emit(EVENTS.AUCTION_END, {auctionId: '12345678902'}); clock.tick(5000); expect(server.requests).to.have.length(3); @@ -481,7 +481,7 @@ describe('NoBid Prebid Analytic', function () { nobidAnalytics.enableAnalytics(initOptions); adapterManager.enableAnalytics({ provider: 'nobid', options: initOptions }); - let eventType = constants.EVENTS.AUCTION_END; + let eventType = EVENTS.AUCTION_END; let disabled; nobidAnalytics.processServerResponse(JSON.stringify({disabled: 0})); disabled = nobidAnalytics.isAnalyticsDisabled(); @@ -508,14 +508,14 @@ describe('NoBid Prebid Analytic', function () { nobidAnalytics.processServerResponse(JSON.stringify({disabled_auctionEnd: 0})); disabled = nobidAnalytics.isAnalyticsDisabled(eventType); expect(disabled).to.equal(false); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '1234567890'}); + events.emit(EVENTS.AUCTION_END, {auctionId: '1234567890'}); clock.tick(1000); expect(server.requests).to.have.length(1); server.requests.length = 0; expect(server.requests).to.have.length(0); - eventType = constants.EVENTS.BID_WON; + eventType = EVENTS.BID_WON; nobidAnalytics.processServerResponse(JSON.stringify({disabled_bidWon: 1})); disabled = nobidAnalytics.isAnalyticsDisabled(eventType); expect(disabled).to.equal(true); @@ -526,7 +526,7 @@ describe('NoBid Prebid Analytic', function () { server.requests.length = 0; expect(server.requests).to.have.length(0); - eventType = constants.EVENTS.AUCTION_END; + eventType = EVENTS.AUCTION_END; nobidAnalytics.processServerResponse(JSON.stringify({disabled: 1})); disabled = nobidAnalytics.isAnalyticsDisabled(eventType); expect(disabled).to.equal(true); @@ -537,16 +537,16 @@ describe('NoBid Prebid Analytic', function () { server.requests.length = 0; expect(server.requests).to.have.length(0); - eventType = constants.EVENTS.AUCTION_END; + eventType = EVENTS.AUCTION_END; nobidAnalytics.processServerResponse(JSON.stringify({disabled_auctionEnd: 1, disabled_bidWon: 0})); disabled = nobidAnalytics.isAnalyticsDisabled(eventType); expect(disabled).to.equal(true); events.emit(eventType, {auctionId: '1234567890'}); clock.tick(1000); expect(server.requests).to.have.length(0); - disabled = nobidAnalytics.isAnalyticsDisabled(constants.EVENTS.BID_WON); + disabled = nobidAnalytics.isAnalyticsDisabled(EVENTS.BID_WON); expect(disabled).to.equal(false); - events.emit(constants.EVENTS.BID_WON, {bidderCode: 'nobid'}); + events.emit(EVENTS.BID_WON, {bidderCode: 'nobid'}); clock.tick(1000); expect(server.requests).to.have.length(1); diff --git a/test/spec/modules/ooloAnalyticsAdapter_spec.js b/test/spec/modules/ooloAnalyticsAdapter_spec.js index 1224c3f0740..f5b3cebf307 100644 --- a/test/spec/modules/ooloAnalyticsAdapter_spec.js +++ b/test/spec/modules/ooloAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import ooloAnalytics, { PAGEVIEW_ID } from 'modules/ooloAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; -import constants from 'src/constants.json' +import { EVENTS } from 'src/constants.js' import * as events from 'src/events' import { config } from 'src/config'; import { buildAuctionData, generatePageViewId } from 'modules/ooloAnalyticsAdapter'; @@ -151,12 +151,12 @@ const bidWon = { } function simulateAuction () { - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); - events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); - events.emit(constants.EVENTS.NO_BID, noBid); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.AUCTION_INIT, auctionInit); + events.emit(EVENTS.BID_REQUESTED, bidRequested); + events.emit(EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.NO_BID, noBid); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); } describe('oolo Prebid Analytic', () => { @@ -321,16 +321,16 @@ describe('oolo Prebid Analytic', () => { } }) - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); - events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + events.emit(EVENTS.AUCTION_INIT, auctionInit); + events.emit(EVENTS.BID_REQUESTED, bidRequested); + events.emit(EVENTS.BID_RESPONSE, bidResponse); // configuration returned in an arbitrary moment server.requests[0].respond(500) - events.emit(constants.EVENTS.NO_BID, noBid); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.NO_BID, noBid); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); clock.tick(1500) @@ -442,7 +442,7 @@ describe('oolo Prebid Analytic', () => { server.requests[0].respond(500) simulateAuction() - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); clock.tick(1500) // no bidWon @@ -466,7 +466,7 @@ describe('oolo Prebid Analytic', () => { })) simulateAuction() - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); clock.tick(499) // no auction data @@ -491,7 +491,7 @@ describe('oolo Prebid Analytic', () => { server.requests[0].respond(500) simulateAuction() clock.tick(1500) - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests).to.have.length(5) @@ -516,7 +516,7 @@ describe('oolo Prebid Analytic', () => { server.requests[0].respond(500) simulateAuction() clock.tick(1500) - events.emit(constants.EVENTS.AD_RENDER_FAILED, { bidId: 'abcdef', reason: 'exception' }); + events.emit(EVENTS.AD_RENDER_FAILED, { bidId: 'abcdef', reason: 'exception' }); expect(server.requests).to.have.length(5) @@ -557,12 +557,12 @@ describe('oolo Prebid Analytic', () => { } })) - events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); - events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.transactionId = '123'; return b }) }); - events.emit(constants.EVENTS.NO_BID, { ...noBid, src: 'client' }); - events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, adUrl: '...' }); - events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd, winningBids: [] }); - events.emit(constants.EVENTS.BID_WON, { ...bidWon, statusMessage: 'msg2' }); + events.emit(EVENTS.AUCTION_INIT, { ...auctionInit }); + events.emit(EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.transactionId = '123'; return b }) }); + events.emit(EVENTS.NO_BID, { ...noBid, src: 'client' }); + events.emit(EVENTS.BID_RESPONSE, { ...bidResponse, adUrl: '...' }); + events.emit(EVENTS.AUCTION_END, { ...auctionEnd, winningBids: [] }); + events.emit(EVENTS.BID_WON, { ...bidWon, statusMessage: 'msg2' }); clock.tick(1500) @@ -596,12 +596,12 @@ describe('oolo Prebid Analytic', () => { } })) - events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); - events.emit(constants.EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.custom_2 = true; return b }) }); - events.emit(constants.EVENTS.NO_BID, { ...noBid, custom_3: true }); - events.emit(constants.EVENTS.BID_RESPONSE, { ...bidResponse, custom_4: true }); - events.emit(constants.EVENTS.AUCTION_END, { ...auctionEnd }); - events.emit(constants.EVENTS.BID_WON, { ...bidWon, custom_5: true }); + events.emit(EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + events.emit(EVENTS.BID_REQUESTED, { ...bidRequested, bids: bidRequested.bids.map(b => { b.custom_2 = true; return b }) }); + events.emit(EVENTS.NO_BID, { ...noBid, custom_3: true }); + events.emit(EVENTS.BID_RESPONSE, { ...bidResponse, custom_4: true }); + events.emit(EVENTS.AUCTION_END, { ...auctionEnd }); + events.emit(EVENTS.BID_WON, { ...bidWon, custom_5: true }); clock.tick(1500) @@ -633,7 +633,7 @@ describe('oolo Prebid Analytic', () => { } })) - events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); + events.emit(EVENTS.AUCTION_INIT, { ...auctionInit, custom_1: true }); clock.tick(1500) @@ -661,7 +661,7 @@ describe('oolo Prebid Analytic', () => { } })) - events.emit(constants.EVENTS.AUCTION_INIT, { ...auctionInit }); + events.emit(EVENTS.AUCTION_INIT, { ...auctionInit }); expect(server.requests[3].url).to.equal('https://pbjs.com/') }) @@ -686,8 +686,8 @@ describe('oolo Prebid Analytic', () => { } })) - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit) - events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); + events.emit(EVENTS.AUCTION_INIT, auctionInit) + events.emit(EVENTS.BID_REQUESTED, bidRequested); const request = JSON.parse(server.requests[3].requestBody) diff --git a/test/spec/modules/optimonAnalyticsAdapter_spec.js b/test/spec/modules/optimonAnalyticsAdapter_spec.js index f1aa00334b5..270f3aec395 100644 --- a/test/spec/modules/optimonAnalyticsAdapter_spec.js +++ b/test/spec/modules/optimonAnalyticsAdapter_spec.js @@ -3,7 +3,6 @@ import { expect } from 'chai'; import optimonAnalyticsAdapter from '../../../modules/optimonAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; -import constants from 'src/constants.json' import {expectEvents} from '../../helpers/analytics.js'; const AD_UNIT_CODE = 'demo-adunit-1'; diff --git a/test/spec/modules/oxxionAnalyticsAdapter_spec.js b/test/spec/modules/oxxionAnalyticsAdapter_spec.js index 9d06be24f68..f9bcdb40e16 100644 --- a/test/spec/modules/oxxionAnalyticsAdapter_spec.js +++ b/test/spec/modules/oxxionAnalyticsAdapter_spec.js @@ -2,10 +2,10 @@ import oxxionAnalytics from 'modules/oxxionAnalyticsAdapter.js'; import {dereferenceWithoutRenderer} from 'modules/oxxionAnalyticsAdapter.js'; import { expect } from 'chai'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + let adapterManager = require('src/adapterManager').default; let events = require('src/events'); -let constants = require('src/constants.json'); - describe('Oxxion Analytics', function () { let timestamp = new Date() - 256; let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; @@ -303,10 +303,10 @@ describe('Oxxion Analytics', function () { } }); - events.emit(constants.EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); - events.emit(constants.EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.BID_REQUESTED, auctionEnd['bidderRequests'][0]); + events.emit(EVENTS.BID_RESPONSE, auctionEnd['bidsReceived'][0]); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); expect(server.requests.length).to.equal(1); let message = JSON.parse(server.requests[0].requestBody); expect(message).to.have.property('auctionEnd').exist; @@ -336,7 +336,7 @@ describe('Oxxion Analytics', function () { domain: 'test' } }); - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests.length).to.equal(1); let message = JSON.parse(server.requests[0].requestBody); expect(message).not.to.have.property('ad'); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 3d264e87e51..2c7c959ef19 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -15,7 +15,7 @@ import { reset } from 'modules/paapi.js'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -69,7 +69,7 @@ describe('paapi module', () => { cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, cf1); addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, cf2); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); }); it('and make them available at end of auction', () => { @@ -123,9 +123,9 @@ describe('paapi module', () => { }); it('should drop auction configs after end of auction', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + events.emit(EVENTS.AUCTION_END, { auctionId }); addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + events.emit(EVENTS.AUCTION_END, { auctionId }); expect(getPAAPIConfig({auctionId})).to.eql({}); }); @@ -136,7 +136,7 @@ describe('paapi module', () => { ortb2: {fpd: 1}, ortb2Imp: {fpd: 2} }, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId}); + events.emit(EVENTS.AUCTION_END, { auctionId }); sinon.assert.match(getPAAPIConfig({auctionId}), { au1: { componentAuctions: [{ @@ -165,13 +165,13 @@ describe('paapi module', () => { describe('onAuctionConfig', () => { const auctionId = 'aid'; it('is invoked with null configs when there\'s no config', () => { - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au'] }); submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); }); it('is invoked with relevant configs', () => { addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); submods.forEach(submod => { sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { au1: {componentAuctions: [fledgeAuctionConfig]}, @@ -185,12 +185,12 @@ describe('paapi module', () => { markAsUsed('au1'); }); addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); expect(getPAAPIConfig()).to.eql({}); }); it('keeps them available if they do not', () => { addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); expect(getPAAPIConfig()).to.not.be.empty; }) }); @@ -294,7 +294,7 @@ describe('paapi module', () => { it('should populate bidfloor/bidfloorcur', () => { addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, payload); + events.emit(EVENTS.AUCTION_END, payload); const signals = getPAAPIConfig({auctionId}).au.componentAuctions[0].auctionSignals; expect(signals.prebid?.bidfloor).to.eql(bidfloor); expect(signals.prebid?.bidfloorcur).to.eql(bidfloorcur); @@ -339,7 +339,7 @@ describe('paapi module', () => { configs[auctionId][adUnitCode] = cfg; addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode}, cfg); }); - events.emit(CONSTANTS.EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); + events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes) }); }); }); diff --git a/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js b/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js index 0c4949264a7..ea0dd4ab793 100644 --- a/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js +++ b/test/spec/modules/pianoDmpAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import pianoDmpAnalytics from 'modules/pianoDmpAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; -import constants from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import { expect } from 'chai'; describe('Piano DMP Analytics Adapter', () => { @@ -31,14 +31,14 @@ describe('Piano DMP Analytics Adapter', () => { it('should pass events to call queue', () => { const eventsList = [ - constants.EVENTS.AUCTION_INIT, - constants.EVENTS.AUCTION_END, - constants.EVENTS.BID_ADJUSTMENT, - constants.EVENTS.BID_TIMEOUT, - constants.EVENTS.BID_REQUESTED, - constants.EVENTS.BID_RESPONSE, - constants.EVENTS.NO_BID, - constants.EVENTS.BID_WON, + EVENTS.AUCTION_INIT, + EVENTS.AUCTION_END, + EVENTS.BID_ADJUSTMENT, + EVENTS.BID_TIMEOUT, + EVENTS.BID_REQUESTED, + EVENTS.BID_RESPONSE, + EVENTS.NO_BID, + EVENTS.BID_WON, ]; // Given diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 2bab144dae7..9c2ac8a23a9 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -12,7 +12,7 @@ import {deepAccess, deepClone, mergeDeep} from 'src/utils.js'; import {ajax} from 'src/ajax.js'; import {config} from 'src/config.js'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import {server} from 'test/mocks/xhr.js'; import 'modules/appnexusBidAdapter.js'; // appnexus alias test import 'modules/rubiconBidAdapter.js'; // rubicon alias test @@ -2879,7 +2879,7 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(400, {}, {}); BID_REQUESTS.forEach(bidderRequest => { - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.BIDDER_ERROR, sinon.match({bidderRequest})) + sinon.assert.calledWith(events.emit, EVENTS.BIDDER_ERROR, sinon.match({ bidderRequest })) }) }) @@ -3062,7 +3062,7 @@ describe('S2S Adapter', function () { sinon.assert.calledOnce(events.emit); const event = events.emit.firstCall.args; - expect(event[0]).to.equal(CONSTANTS.EVENTS.BIDDER_DONE); + expect(event[0]).to.equal(EVENTS.BIDDER_DONE); expect(event[1].bids[0]).to.have.property('serverResponseTimeMs', 8); sinon.assert.calledOnce(addBidResponse); @@ -3093,7 +3093,7 @@ describe('S2S Adapter', function () { Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) server.requests[0].respond(200, {}, JSON.stringify(responding)); const event = events.emit.secondCall.args; - expect(event[0]).to.equal(CONSTANTS.EVENTS.SEAT_NON_BID); + expect(event[0]).to.equal(EVENTS.SEAT_NON_BID); expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); expect(event[1].requestedBidders).to.deep.equal(['appnexus']); expect(event[1].response).to.deep.equal(responding); @@ -3584,7 +3584,7 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - events.emit(CONSTANTS.EVENTS.BID_WON, { + events.emit(EVENTS.BID_WON, { auctionId: '173afb6d132ba3', adId: '1000' }); @@ -3603,7 +3603,7 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - events.emit(CONSTANTS.EVENTS.BID_WON, { + events.emit(EVENTS.BID_WON, { auctionId: '173afb6d132ba3', adId: 'missingAdId' }); @@ -3619,7 +3619,7 @@ describe('S2S Adapter', function () { adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(clonedResponse)); - events.emit(CONSTANTS.EVENTS.BID_WON, { + events.emit(EVENTS.BID_WON, { auctionId: '173afb6d132ba3', adId: '1060' }); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js index 25834e8574d..9241fda8c81 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js @@ -3,9 +3,9 @@ import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('Prebid Manager Analytics Adapter', function () { let bidWonEvent = { @@ -66,7 +66,7 @@ describe('Prebid Manager Analytics Adapter', function () { } }); - events.emit(constants.EVENTS.BID_WON, bidWonEvent); + events.emit(EVENTS.BID_WON, bidWonEvent); prebidmanagerAnalytics.flush(); expect(server.requests.length).to.equal(1); diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 7ea7722b12a..7223940bc45 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1,8 +1,9 @@ import {expect} from 'chai'; import * as utils from 'src/utils.js'; import { getGlobal } from 'src/prebidGlobal.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS, STATUS } from 'src/constants.js'; import { + FLOOR_SKIPPED_REASON, _floorDataForAuction, getFloorsDataForAuction, getFirstMatchingFloor, @@ -737,7 +738,7 @@ describe('the price floors module', function () { handleSetFloorsConfig(floorConfig); const floorData = createFloorsDataForAuction(adUnits, 'id'); - expect(floorData.skippedReason).to.equal(CONSTANTS.FLOOR_SKIPPED_REASON.NOT_FOUND); + expect(floorData.skippedReason).to.equal(FLOOR_SKIPPED_REASON.NOT_FOUND); }); it('should have skippedReason set to "random" if there is floor data and skipped is true', function() { @@ -746,7 +747,7 @@ describe('the price floors module', function () { handleSetFloorsConfig(floorConfig); const floorData = createFloorsDataForAuction(adUnits, 'id'); - expect(floorData.skippedReason).to.equal(CONSTANTS.FLOOR_SKIPPED_REASON.RANDOM); + expect(floorData.skippedReason).to.equal(FLOOR_SKIPPED_REASON.RANDOM); }); }); @@ -2218,7 +2219,7 @@ describe('the price floors module', function () { let next = (adUnitCode, bid) => { returnedBidResponse = bid; }; - addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(CONSTANTS.STATUS.GOOD, {auctionId: AUCTION_ID}), bidResp), reject); + addBidResponseHook(next, bidResp.adUnitCode, Object.assign(createBid(STATUS.GOOD, { auctionId: AUCTION_ID }), bidResp), reject); }; it('continues with the auction if not floors data is present without any flooring', function () { runBidResponse(); @@ -2345,7 +2346,7 @@ describe('the price floors module', function () { it('should wait 3 seconds before deleting auction floor data', function () { handleSetFloorsConfig({enabled: true}); _floorDataForAuction[AUCTION_END_EVENT.auctionId] = utils.deepClone(basicFloorConfig); - events.emit(CONSTANTS.EVENTS.AUCTION_END, AUCTION_END_EVENT); + events.emit(EVENTS.AUCTION_END, AUCTION_END_EVENT); // should still be here expect(_floorDataForAuction[AUCTION_END_EVENT.auctionId]).to.not.be.undefined; // tick for 4 seconds diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 951b5135260..cb13a4db234 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import pubmaticAnalyticsAdapter, { getMetadata } from 'modules/pubmaticAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS, REJECTION_REASON } from 'src/constants.js'; import { config } from 'src/config.js'; import { setConfig } from 'modules/currency.js'; import { server } from '../../mocks/xhr.js'; @@ -18,18 +18,16 @@ const setUAMobile = () => { window.navigator.__defineGetter__('userAgent', funct const setUANull = () => { window.navigator.__defineGetter__('userAgent', function () { return null }) }; const { - EVENTS: { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_RESPONSE, - BID_REJECTED, - BIDDER_DONE, - BID_WON, - BID_TIMEOUT, - SET_TARGETING - } -} = CONSTANTS; + AUCTION_INIT, + AUCTION_END, + BID_REQUESTED, + BID_RESPONSE, + BID_REJECTED, + BIDDER_DONE, + BID_WON, + BID_TIMEOUT, + SET_TARGETING +} = EVENTS; const BID = { 'bidder': 'pubmatic', @@ -110,7 +108,7 @@ const BID2 = Object.assign({}, BID, { }); const BID3 = Object.assign({}, BID2, { - rejectionReason: CONSTANTS.REJECTION_REASON.FLOOR_NOT_MET + rejectionReason: REJECTION_REASON.FLOOR_NOT_MET }) const MOCK = { SET_TARGETING: { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index fda2c853e87..8aa29724ad6 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -4,7 +4,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; import { bidderSettings } from 'src/bidderSettings.js'; -const constants = require('src/constants.json'); +const constants = require('src/constants.js'); describe('PubMatic adapter', function () { let bidRequests; diff --git a/test/spec/modules/pubstackAnalyticsAdapter_spec.js b/test/spec/modules/pubstackAnalyticsAdapter_spec.js index fe7441e91e5..6e532698d8b 100644 --- a/test/spec/modules/pubstackAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubstackAnalyticsAdapter_spec.js @@ -2,7 +2,6 @@ import * as utils from 'src/utils.js'; import pubstackAnalytics from '../../../modules/pubstackAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; -import constants from 'src/constants.json' import {expectEvents} from '../../helpers/analytics.js'; describe('Pubstack Analytics Adapter', () => { diff --git a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js index 92d5972cc13..688a827de03 100644 --- a/test/spec/modules/pubwiseAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubwiseAnalyticsAdapter_spec.js @@ -2,10 +2,10 @@ import {expect} from 'chai'; import pubwiseAnalytics from 'modules/pubwiseAnalyticsAdapter.js'; import {expectEvents} from '../../helpers/analytics.js'; import {server} from '../../mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); describe('PubWise Prebid Analytics', function () { let requests; @@ -54,14 +54,14 @@ describe('PubWise Prebid Analytics', function () { sandbox.spy(pubwiseAnalytics, 'track'); expectEvents([ - constants.EVENTS.AUCTION_INIT, - constants.EVENTS.BID_REQUESTED, - constants.EVENTS.BID_RESPONSE, - constants.EVENTS.BID_WON, - constants.EVENTS.AD_RENDER_FAILED, - constants.EVENTS.TCF2_ENFORCEMENT, - constants.EVENTS.BID_TIMEOUT, - constants.EVENTS.AUCTION_END, + EVENTS.AUCTION_INIT, + EVENTS.BID_REQUESTED, + EVENTS.BID_RESPONSE, + EVENTS.BID_WON, + EVENTS.AD_RENDER_FAILED, + EVENTS.TCF2_ENFORCEMENT, + EVENTS.BID_TIMEOUT, + EVENTS.AUCTION_END, ]).to.beTrackedBy(pubwiseAnalytics.track); }); @@ -69,10 +69,10 @@ describe('PubWise Prebid Analytics', function () { pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); // sent - events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); + events.emit(EVENTS.AUCTION_INIT, mock.AUCTION_INIT); + events.emit(EVENTS.BID_REQUESTED, {}); + events.emit(EVENTS.BID_RESPONSE, {}); + events.emit(EVENTS.BID_WON, {}); // force flush clock.tick(500); @@ -120,7 +120,7 @@ describe('PubWise Prebid Analytics', function () { pubwiseAnalytics.enableAnalytics(mock.DEFAULT_PW_CONFIG); // sent - events.emit(constants.EVENTS.AUCTION_INIT, mock.AUCTION_INIT_EXTRAS); + events.emit(EVENTS.AUCTION_INIT, mock.AUCTION_INIT_EXTRAS); // force flush clock.tick(500); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index e0f4497a8c8..9af1ef185e1 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -4,9 +4,9 @@ import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; import {getGptSlotInfoForAdUnitCode} from '../../../libraries/gptUtils/gptUtils.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('pubxai analytics adapter', function() { beforeEach(function() { @@ -671,19 +671,19 @@ describe('pubxai analytics adapter', function() { it('builds and sends auction data', function() { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); expect(server.requests.length).to.equal(1); @@ -692,7 +692,7 @@ describe('pubxai analytics adapter', function() { expect(realAfterBid).to.deep.equal(expectedAfterBid); // Step 6: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); expect(server.requests.length).to.equal(2); diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js index 9baa526e4cc..c9f92e8af67 100644 --- a/test/spec/modules/qortexRtdProvider_spec.js +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils'; import * as ajax from 'src/ajax.js'; import * as events from 'src/events.js'; -import CONSTANTS from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import {loadExternalScript} from 'src/adloader.js'; import { qortexSubmodule as module, @@ -127,7 +127,7 @@ describe('qortexRtdProvider', () => { let config = cloneDeep(validModuleConfig); config.params.tagConfig = validTagConfig; - events.on(CONSTANTS.EVENTS.BILLABLE_EVENT, (e) => { + events.on(EVENTS.BILLABLE_EVENT, (e) => { billableEvents.push(e); }) diff --git a/test/spec/modules/realTimeDataModule_spec.js b/test/spec/modules/realTimeDataModule_spec.js index 938e2e2f3c1..0f66b0253a2 100644 --- a/test/spec/modules/realTimeDataModule_spec.js +++ b/test/spec/modules/realTimeDataModule_spec.js @@ -1,7 +1,7 @@ import * as rtdModule from 'modules/rtdModule/index.js'; import {config} from 'src/config.js'; import * as sinon from 'sinon'; -import {default as CONSTANTS} from '../../../src/constants.json'; +import { EVENTS } from '../../../src/constants.js'; import * as events from '../../../src/events.js'; import 'src/prebid.js'; import {attachRealTimeDataProvider, onDataDeletionRequest} from 'modules/rtdModule/index.js'; @@ -255,11 +255,11 @@ describe('Real time module', function () { }); describe('event', () => { - const EVENTS = { - [CONSTANTS.EVENTS.AUCTION_INIT]: 'onAuctionInitEvent', - [CONSTANTS.EVENTS.AUCTION_END]: 'onAuctionEndEvent', - [CONSTANTS.EVENTS.BID_RESPONSE]: 'onBidResponseEvent', - [CONSTANTS.EVENTS.BID_REQUESTED]: 'onBidRequestEvent' + const TEST_EVENTS = { + [EVENTS.AUCTION_INIT]: 'onAuctionInitEvent', + [EVENTS.AUCTION_END]: 'onAuctionEndEvent', + [EVENTS.BID_RESPONSE]: 'onBidResponseEvent', + [EVENTS.BID_REQUESTED]: 'onBidRequestEvent' } const conf = { 'realTimeData': { @@ -281,7 +281,7 @@ describe('Real time module', function () { name: name, init: () => true, } - Object.values(EVENTS).forEach((ev) => provider[ev] = sinon.spy()); + Object.values(TEST_EVENTS).forEach((ev) => provider[ev] = sinon.spy()); return provider; } @@ -303,13 +303,13 @@ describe('Real time module', function () { adUnitCodes: ['a1'], adUnits: [{code: 'a1'}] }; - mockEmitEvent(CONSTANTS.EVENTS.AUCTION_END, auction); + mockEmitEvent(EVENTS.AUCTION_END, auction); providers.forEach(p => { expect(p.getTargetingData.calledWith(auction.adUnitCodes)).to.be.true; }); }); - Object.entries(EVENTS).forEach(([event, hook]) => { + Object.entries(TEST_EVENTS).forEach(([event, hook]) => { it(`'${event}' should be propagated to providers through '${hook}'`, () => { const eventArg = {}; mockEmitEvent(event, eventArg); diff --git a/test/spec/modules/relevantAnalyticsAdapter_spec.js b/test/spec/modules/relevantAnalyticsAdapter_spec.js index 5c818fe01d4..e3d0eca1b7b 100644 --- a/test/spec/modules/relevantAnalyticsAdapter_spec.js +++ b/test/spec/modules/relevantAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import relevantAnalytics from '../../../modules/relevantAnalyticsAdapter.js'; import adapterManager from 'src/adapterManager'; import * as events from 'src/events'; -import constants from 'src/constants.json' +import { EVENTS } from 'src/constants.js' import { expect } from 'chai'; describe('Relevant Analytics Adapter', () => { @@ -18,8 +18,8 @@ describe('Relevant Analytics Adapter', () => { it('should pass all events to the global array', () => { // Given const testEvents = [ - { ev: constants.EVENTS.AUCTION_INIT, args: { test: 1 } }, - { ev: constants.EVENTS.BID_REQUESTED, args: { test: 2 } }, + { ev: EVENTS.AUCTION_INIT, args: { test: 1 } }, + { ev: EVENTS.BID_REQUESTED, args: { test: 2 } }, ]; // When diff --git a/test/spec/modules/relevantdigitalBidAdapter_spec.js b/test/spec/modules/relevantdigitalBidAdapter_spec.js index 0e21453c8ba..1575c5c6b94 100644 --- a/test/spec/modules/relevantdigitalBidAdapter_spec.js +++ b/test/spec/modules/relevantdigitalBidAdapter_spec.js @@ -1,7 +1,7 @@ import {spec, resetBidderConfigs} from 'modules/relevantdigitalBidAdapter.js'; import { parseUrl, deepClone } from 'src/utils.js'; import { config } from 'src/config.js'; -import CONSTANTS from 'src/constants.json'; +import { S2S } from 'src/constants.js'; import adapterManager, { } from 'src/adapterManager.js'; @@ -16,7 +16,7 @@ const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; const CONFIG = { enabled: true, - endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT, + endpoint: S2S.DEFAULT_ENDPOINT, timeout: 1000, maxBids: 1, adapter: 'prebidServer', diff --git a/test/spec/modules/rivrAnalyticsAdapter_spec.js b/test/spec/modules/rivrAnalyticsAdapter_spec.js index 9add7ed5f7d..6aab92b6b5d 100644 --- a/test/spec/modules/rivrAnalyticsAdapter_spec.js +++ b/test/spec/modules/rivrAnalyticsAdapter_spec.js @@ -19,7 +19,7 @@ import { import {expect} from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; const events = require('../../../src/events'); @@ -98,7 +98,7 @@ describe('RIVR Analytics adapter', () => { expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + events.emit(EVENTS.AUCTION_INIT, { auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000 }); expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); @@ -111,12 +111,12 @@ describe('RIVR Analytics adapter', () => { expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(0); - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000}); + events.emit(EVENTS.AUCTION_INIT, { auctionId: EMITTED_AUCTION_ID, config: {}, timeout: 3000 }); expect(rivraddonsTrackPbjsEventStub.callCount).to.be.equal(1); const firstArgument = rivraddonsTrackPbjsEventStub.getCall(0).args[0]; - expect(firstArgument.eventType).to.be.equal(CONSTANTS.EVENTS.AUCTION_INIT); + expect(firstArgument.eventType).to.be.equal(EVENTS.AUCTION_INIT); expect(firstArgument.args.auctionId).to.be.equal(EMITTED_AUCTION_ID); window.rivraddon.analytics.trackPbjsEvent.restore(); diff --git a/test/spec/modules/roxotAnalyticsAdapter_spec.js b/test/spec/modules/roxotAnalyticsAdapter_spec.js index 79c58e36735..6fc7f356333 100644 --- a/test/spec/modules/roxotAnalyticsAdapter_spec.js +++ b/test/spec/modules/roxotAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ import roxotAnalytic from 'modules/roxotAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('Roxot Prebid Analytic', function () { let roxotConfigServerUrl = 'config-server'; @@ -181,18 +181,18 @@ describe('Roxot Prebid Analytic', function () { expect(server.requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); server.requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); - events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentWithBid); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseWithBid); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentNoBid); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseNoBid); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentAfterTimeout); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseAfterTimeout); - events.emit(constants.EVENTS.BIDDER_DONE, bidderDone); - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.AUCTION_INIT, auctionInit); + events.emit(EVENTS.BID_REQUESTED, bidRequested); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentWithBid); + events.emit(EVENTS.BID_RESPONSE, bidResponseWithBid); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentNoBid); + events.emit(EVENTS.BID_RESPONSE, bidResponseNoBid); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentAfterTimeout); + events.emit(EVENTS.BID_RESPONSE, bidResponseAfterTimeout); + events.emit(EVENTS.BIDDER_DONE, bidderDone); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests.length).to.equal(4); @@ -260,18 +260,18 @@ describe('Roxot Prebid Analytic', function () { expect(server.requests[0].url).to.equal('https://' + roxotConfigServerUrl + '/c?publisherId=' + publisherId + '&host=localhost'); server.requests[0].respond(200, {'Content-Type': 'application/json'}, '{"a": 1, "i": 1, "bat": 1}'); - events.emit(constants.EVENTS.AUCTION_INIT, auctionInit); - events.emit(constants.EVENTS.BID_REQUESTED, bidRequested); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentWithBid); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseWithBid); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentNoBid); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseNoBid); - events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); - events.emit(constants.EVENTS.BID_ADJUSTMENT, bidAdjustmentAfterTimeout); - events.emit(constants.EVENTS.BID_RESPONSE, bidResponseAfterTimeout); - events.emit(constants.EVENTS.BIDDER_DONE, bidderDone); - events.emit(constants.EVENTS.BID_WON, bidWon); + events.emit(EVENTS.AUCTION_INIT, auctionInit); + events.emit(EVENTS.BID_REQUESTED, bidRequested); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentWithBid); + events.emit(EVENTS.BID_RESPONSE, bidResponseWithBid); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentNoBid); + events.emit(EVENTS.BID_RESPONSE, bidResponseNoBid); + events.emit(EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.BID_ADJUSTMENT, bidAdjustmentAfterTimeout); + events.emit(EVENTS.BID_RESPONSE, bidResponseAfterTimeout); + events.emit(EVENTS.BIDDER_DONE, bidderDone); + events.emit(EVENTS.BID_WON, bidWon); expect(server.requests.length).to.equal(3); diff --git a/test/spec/modules/scaleableAnalyticsAdapter_spec.js b/test/spec/modules/scaleableAnalyticsAdapter_spec.js index c65740252d2..5f86073894a 100644 --- a/test/spec/modules/scaleableAnalyticsAdapter_spec.js +++ b/test/spec/modules/scaleableAnalyticsAdapter_spec.js @@ -1,13 +1,13 @@ import scaleableAnalytics from 'modules/scaleableAnalyticsAdapter.js'; import { expect } from 'chai'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import { server } from 'test/mocks/xhr.js'; -const BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -const AUCTION_INIT = CONSTANTS.EVENTS.AUCTION_INIT; -const BID_WON = CONSTANTS.EVENTS.BID_WON; -const AUCTION_END = CONSTANTS.EVENTS.AUCTION_END; +const BID_TIMEOUT = EVENTS.BID_TIMEOUT; +const AUCTION_INIT = EVENTS.AUCTION_INIT; +const BID_WON = EVENTS.BID_WON; +const AUCTION_END = EVENTS.AUCTION_END; describe('Scaleable Analytics Adapter', function() { const bidsReceivedObj = { diff --git a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js index 6cdc3c448b9..1d8e38f19ec 100644 --- a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js +++ b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js @@ -4,7 +4,6 @@ import {expectEvents} from '../../helpers/analytics.js'; let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); describe('sigmoid Prebid Analytic', function () { after(function () { diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js index ed8ccd22eea..c34de91dd9f 100644 --- a/test/spec/modules/sonobiAnalyticsAdapter_spec.js +++ b/test/spec/modules/sonobiAnalyticsAdapter_spec.js @@ -1,9 +1,10 @@ import sonobiAnalytics, {DEFAULT_EVENT_URL} from 'modules/sonobiAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); describe('Sonobi Prebid Analytic', function () { var clock; @@ -55,25 +56,25 @@ describe('Sonobi Prebid Analytic', function () { }); // Step 2: Send init auction event - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: '13', timestamp: Date.now()}); + events.emit(EVENTS.AUCTION_INIT, { config: initOptions, auctionId: '13', timestamp: Date.now() }); expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); // Step 3: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); + events.emit(EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); // Step 4: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, bid); + events.emit(EVENTS.BID_RESPONSE, bid); // Step 5: Send bid won event - events.emit(constants.EVENTS.BID_WON, bid); + events.emit(EVENTS.BID_WON, bid); // Step 6: Send bid timeout event - events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: '13'}); + events.emit(EVENTS.BID_TIMEOUT, { auctionId: '13' }); // Step 7: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, {auctionId: '13', bidsReceived: [bid]}); + events.emit(EVENTS.AUCTION_END, { auctionId: '13', bidsReceived: [bid] }); clock.tick(5000); const req = server.requests.find(req => req.url.indexOf(DEFAULT_EVENT_URL) !== -1); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js index d0363eab144..7945bdc9910 100644 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ b/test/spec/modules/sovrnAnalyticsAdapter_spec.js @@ -4,11 +4,11 @@ import {config} from 'src/config.js'; import adaptermanager from 'src/adapterManager.js'; import {server} from 'test/mocks/xhr.js'; import {expectEvents, fireEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; var assert = require('assert'); let events = require('src/events'); -let constants = require('src/constants.json'); /** * Emit analytics events @@ -18,7 +18,7 @@ let constants = require('src/constants.json'); */ function emitEvent(eventType, event, auctionId) { event.auctionId = auctionId; - events.emit(constants.EVENTS[eventType], event); + events.emit(EVENTS[eventType], event); } let auctionStartTimestamp = Date.now(); diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js index f8e3ba83bbe..3f28098e1d1 100644 --- a/test/spec/modules/staqAnalyticsAdapter_spec.js +++ b/test/spec/modules/staqAnalyticsAdapter_spec.js @@ -1,7 +1,7 @@ import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; const events = require('../../../src/events'); @@ -216,14 +216,14 @@ describe('', function() { }); it('should handle auction init event', function() { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, { config: {}, timeout: 3000 }); + events.emit(EVENTS.AUCTION_INIT, { config: {}, timeout: 3000 }); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); expect(ev[0]).to.be.eql({ event: 'auctionInit', auctionId: undefined }); }); it('should handle bid request event', function() { - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); + events.emit(EVENTS.BID_REQUESTED, REQUEST); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(2); expect(ev[1]).to.be.eql({ @@ -236,7 +236,7 @@ describe('', function() { }); it('should handle bid response event', function() { - events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); + events.emit(EVENTS.BID_RESPONSE, RESPONSE); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(3); expect(ev[2]).to.be.eql({ @@ -255,7 +255,7 @@ describe('', function() { }); it('should handle timeouts properly', function() { - events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); + events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(5); // remember, we added 2 timeout events @@ -268,7 +268,7 @@ describe('', function() { }); it('should handle winning bid', function() { - events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); + events.emit(EVENTS.BID_WON, RESPONSE); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(6); expect(ev[5]).to.be.eql({ @@ -287,7 +287,7 @@ describe('', function() { it('should handle auction end event', function() { timer.tick(447); - events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); + events.emit(EVENTS.AUCTION_END, RESPONSE); let ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(0); expect(ajaxStub.calledOnce).to.be.equal(true); diff --git a/test/spec/modules/terceptAnalyticsAdapter_spec.js b/test/spec/modules/terceptAnalyticsAdapter_spec.js index a1384bfd919..8a0d04ff6b3 100644 --- a/test/spec/modules/terceptAnalyticsAdapter_spec.js +++ b/test/spec/modules/terceptAnalyticsAdapter_spec.js @@ -3,9 +3,9 @@ import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; let events = require('src/events'); -let constants = require('src/constants.json'); describe('tercept analytics adapter', function () { beforeEach(function () { @@ -753,19 +753,19 @@ describe('tercept analytics adapter', function () { it('builds and sends auction data', function () { // Step 1: Send auction init event - events.emit(constants.EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); // Step 2: Send bid requested event - events.emit(constants.EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); // Step 3: Send bid response event - events.emit(constants.EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); // Step 4: Send bid time out event - events.emit(constants.EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); // Step 5: Send auction end event - events.emit(constants.EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); expect(server.requests.length).to.equal(1); @@ -774,7 +774,7 @@ describe('tercept analytics adapter', function () { expect(realAfterBid).to.deep.equal(expectedAfterBid); // Step 6: Send auction bid won event - events.emit(constants.EVENTS.BID_WON, prebidEvent['bidWon']); + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); expect(server.requests.length).to.equal(2); diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index d7f09c2a057..2d0438e37e5 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -3,7 +3,7 @@ import * as utils from 'src/utils.js'; import {spec, acceptPostMessage, getStorageData, setStorageData} from 'modules/trionBidAdapter.js'; import {deepClone} from 'src/utils.js'; -const CONSTANTS = require('src/constants.json'); +const CONSTANTS = require('src/constants.js'); const adloader = require('src/adloader'); const PLACEMENT_CODE = 'ad-tag'; diff --git a/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js b/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js index 2b7f047c85a..997586c195e 100644 --- a/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js +++ b/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js @@ -6,7 +6,7 @@ import { import {expect} from 'chai'; const events = require('src/events'); -const constants = require('src/constants.json'); +const constants = require('src/constants.js'); const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; const adid = 'test-ad-83444226E44368D1E32E49EEBE6D29'; diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 1e909d79ed4..2ff19424e09 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -18,7 +18,7 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; import {resetConsentData, } from 'modules/consentManagement.js'; import {server} from 'test/mocks/xhr.js'; @@ -1714,7 +1714,7 @@ describe('User ID', function () { // check user sync is delayed after auction is ended mockIdCallback.calledOnce.should.equal(false); events.on.calledOnce.should.equal(true); - events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + events.on.calledWith(EVENTS.AUCTION_END, sinon.match.func); // once auction is ended, sync user ids after delay events.on.callArg(1); @@ -1748,7 +1748,7 @@ describe('User ID', function () { // sync delay after auction is ended mockIdCallback.calledOnce.should.equal(false); events.on.calledOnce.should.equal(true); - events.on.calledWith(CONSTANTS.EVENTS.AUCTION_END, sinon.match.func); + events.on.calledWith(EVENTS.AUCTION_END, sinon.match.func); // once auction is ended, if no sync delay, fetch ids events.on.callArg(1); @@ -3104,7 +3104,7 @@ describe('User ID', function () { }); function endAuction() { - events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); + events.emit(EVENTS.AUCTION_END, {}); return new Promise((resolve) => setTimeout(resolve)); } diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js index 1ccd9766eab..58af1a15e43 100644 --- a/test/spec/modules/videoModule/pbVideo_spec.js +++ b/test/spec/modules/videoModule/pbVideo_spec.js @@ -1,7 +1,7 @@ import 'src/prebid.js'; import { expect } from 'chai'; import { PbVideo } from 'modules/videoModule'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; let ortbVideoMock; let ortbContentMock; @@ -225,7 +225,7 @@ describe('Prebid Video', function () { const pbEvents = { emit: () => {}, on: (event, callback) => { - if (event === CONSTANTS.EVENTS.AUCTION_END) { + if (event === EVENTS.AUCTION_END) { auctionEndCallback = callback } }, @@ -337,7 +337,7 @@ describe('Prebid Video', function () { const pbEvents = { on: (event, callback) => { - if (event === CONSTANTS.EVENTS.BID_ADJUSTMENT) { + if (event === EVENTS.BID_ADJUSTMENT) { bidAdjustmentCb = callback; } else if (event === 'videoAdImpression') { adImpressionCb = callback; diff --git a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js index ea52f89773e..88438f383ee 100644 --- a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js +++ b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js @@ -2,9 +2,10 @@ import yieldoneAnalytics from 'modules/yieldoneAnalyticsAdapter.js'; import { targeting } from 'src/targeting.js'; import { expect } from 'chai'; import _ from 'lodash'; +import { EVENTS } from 'src/constants.js'; + let events = require('src/events'); let adapterManager = require('src/adapterManager').default; -let constants = require('src/constants.json'); describe('Yieldone Prebid Analytic', function () { let sendStatStub; @@ -187,38 +188,38 @@ describe('Yieldone Prebid Analytic', function () { const expectedEvents = [ { - eventType: constants.EVENTS.AUCTION_INIT, + eventType: EVENTS.AUCTION_INIT, params: { config: initOptions, auctionId: auctionId } }, { - eventType: constants.EVENTS.BID_REQUESTED, + eventType: EVENTS.BID_REQUESTED, params: Object.assign(request[0]) }, { - eventType: constants.EVENTS.BID_REQUESTED, + eventType: EVENTS.BID_REQUESTED, params: Object.assign(request[1]) }, { - eventType: constants.EVENTS.BID_REQUESTED, + eventType: EVENTS.BID_REQUESTED, params: Object.assign(request[2]) }, { - eventType: constants.EVENTS.BID_RESPONSE, + eventType: EVENTS.BID_RESPONSE, params: Object.assign(preparedResponses[0]) }, { - eventType: constants.EVENTS.BID_RESPONSE, + eventType: EVENTS.BID_RESPONSE, params: Object.assign(preparedResponses[1]) }, { - eventType: constants.EVENTS.BID_RESPONSE, + eventType: EVENTS.BID_RESPONSE, params: Object.assign(preparedResponses[2]) }, { - eventType: constants.EVENTS.BID_TIMEOUT, + eventType: EVENTS.BID_TIMEOUT, params: Object.assign(request[2]) } ]; @@ -235,7 +236,7 @@ describe('Yieldone Prebid Analytic', function () { delete preparedWinnerParams.ad; const wonExpectedEvents = [ { - eventType: constants.EVENTS.BID_WON, + eventType: EVENTS.BID_WON, params: preparedWinnerParams } ]; @@ -251,29 +252,29 @@ describe('Yieldone Prebid Analytic', function () { options: initOptions }); - events.emit(constants.EVENTS.AUCTION_INIT, {config: initOptions, auctionId: auctionId}); + events.emit(EVENTS.AUCTION_INIT, { config: initOptions, auctionId: auctionId }); - events.emit(constants.EVENTS.BID_REQUESTED, request[0]); - events.emit(constants.EVENTS.BID_REQUESTED, request[1]); - events.emit(constants.EVENTS.BID_REQUESTED, request[2]); + events.emit(EVENTS.BID_REQUESTED, request[0]); + events.emit(EVENTS.BID_REQUESTED, request[1]); + events.emit(EVENTS.BID_REQUESTED, request[2]); - events.emit(constants.EVENTS.BID_RESPONSE, responses[0]); - events.emit(constants.EVENTS.BID_RESPONSE, responses[1]); - events.emit(constants.EVENTS.BID_RESPONSE, responses[2]); + events.emit(EVENTS.BID_RESPONSE, responses[0]); + events.emit(EVENTS.BID_RESPONSE, responses[1]); + events.emit(EVENTS.BID_RESPONSE, responses[2]); - events.emit(constants.EVENTS.BID_TIMEOUT, [responses[3], responses[4]]); + events.emit(EVENTS.BID_TIMEOUT, [responses[3], responses[4]]); - events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(EVENTS.AUCTION_END, auctionEnd); sinon.assert.match(yieldoneAnalytics.eventsStorage[auctionId], expectedResult); delete yieldoneAnalytics.eventsStorage[auctionId]; setTimeout(function() { - events.emit(constants.EVENTS.BID_WON, winner); + events.emit(EVENTS.BID_WON, winner); sinon.assert.callCount(sendStatStub, 2) - const billableEventIndex = yieldoneAnalytics.eventsStorage[auctionId].events.findIndex(event => event.eventType === constants.EVENTS.BILLABLE_EVENT); + const billableEventIndex = yieldoneAnalytics.eventsStorage[auctionId].events.findIndex(event => event.eventType === EVENTS.BILLABLE_EVENT); if (billableEventIndex > -1) { yieldoneAnalytics.eventsStorage[auctionId].events.splice(billableEventIndex, 1); } diff --git a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js index e8eb4ab73be..c0de9a0e2fa 100644 --- a/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js +++ b/test/spec/modules/yuktamediaAnalyticsAdapter_spec.js @@ -1,7 +1,8 @@ import yuktamediaAnalyticsAdapter from 'modules/yuktamediaAnalyticsAdapter.js'; import { expect } from 'chai'; +import { EVENTS } from 'src/constants.js'; + let events = require('src/events'); -let constants = require('src/constants.json'); let prebidAuction = { 'auctionInit': { @@ -402,7 +403,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(EVENTS.AUCTION_INIT, prebidAuction[EVENTS.AUCTION_INIT]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -417,7 +418,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(EVENTS.BID_REQUESTED, prebidAuction[EVENTS.BID_REQUESTED]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -432,7 +433,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); + events.emit(EVENTS.NO_BID, prebidAuction[EVENTS.NO_BID]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -447,7 +448,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(EVENTS.BID_TIMEOUT, prebidAuction[EVENTS.BID_TIMEOUT]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -462,7 +463,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(EVENTS.BID_RESPONSE, prebidAuction[EVENTS.BID_RESPONSE]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -477,7 +478,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + events.emit(EVENTS.AUCTION_END, prebidAuction[EVENTS.AUCTION_END]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -488,11 +489,11 @@ describe('yuktamedia analytics adapter', function () { } }); - events.emit(constants.EVENTS.AUCTION_INIT, {}); - events.emit(constants.EVENTS.AUCTION_END, {}); - events.emit(constants.EVENTS.BID_REQUESTED, {}); - events.emit(constants.EVENTS.BID_RESPONSE, {}); - events.emit(constants.EVENTS.BID_WON, {}); + events.emit(EVENTS.AUCTION_INIT, {}); + events.emit(EVENTS.AUCTION_END, {}); + events.emit(EVENTS.BID_REQUESTED, {}); + events.emit(EVENTS.BID_RESPONSE, {}); + events.emit(EVENTS.BID_WON, {}); sinon.assert.callCount(yuktamediaAnalyticsAdapter.track, 0); }); @@ -508,7 +509,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_INIT, prebidNativeAuction[constants.EVENTS.AUCTION_INIT]); + events.emit(EVENTS.AUCTION_INIT, prebidNativeAuction[EVENTS.AUCTION_INIT]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -523,7 +524,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED]); + events.emit(EVENTS.BID_REQUESTED, prebidNativeAuction[EVENTS.BID_REQUESTED]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -538,7 +539,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_REQUESTED, prebidNativeAuction[constants.EVENTS.BID_REQUESTED + '1']); + events.emit(EVENTS.BID_REQUESTED, prebidNativeAuction[EVENTS.BID_REQUESTED + '1']); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -553,7 +554,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.NO_BID, prebidNativeAuction[constants.EVENTS.NO_BID]); + events.emit(EVENTS.NO_BID, prebidNativeAuction[EVENTS.NO_BID]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -568,7 +569,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_TIMEOUT, prebidNativeAuction[constants.EVENTS.BID_TIMEOUT]); + events.emit(EVENTS.BID_TIMEOUT, prebidNativeAuction[EVENTS.BID_TIMEOUT]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -583,7 +584,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.BID_RESPONSE, prebidNativeAuction[constants.EVENTS.BID_RESPONSE]); + events.emit(EVENTS.BID_RESPONSE, prebidNativeAuction[EVENTS.BID_RESPONSE]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -598,7 +599,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.AUCTION_END]); + events.emit(EVENTS.AUCTION_END, prebidNativeAuction[EVENTS.AUCTION_END]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); @@ -613,7 +614,7 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_END, prebidNativeAuction[constants.EVENTS.BID_WON]); + events.emit(EVENTS.AUCTION_END, prebidNativeAuction[EVENTS.BID_WON]); sinon.assert.called(yuktamediaAnalyticsAdapter.track); }); }); @@ -681,12 +682,12 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); - events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); - events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); - events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); - events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); - events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + events.emit(EVENTS.AUCTION_INIT, prebidAuction[EVENTS.AUCTION_INIT]); + events.emit(EVENTS.BID_REQUESTED, prebidAuction[EVENTS.BID_REQUESTED]); + events.emit(EVENTS.NO_BID, prebidAuction[EVENTS.NO_BID]); + events.emit(EVENTS.BID_TIMEOUT, prebidAuction[EVENTS.BID_TIMEOUT]); + events.emit(EVENTS.BID_RESPONSE, prebidAuction[EVENTS.BID_RESPONSE]); + events.emit(EVENTS.AUCTION_END, prebidAuction[EVENTS.AUCTION_END]); expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.not.equal(null); }); @@ -701,12 +702,12 @@ describe('yuktamedia analytics adapter', function () { enableUserIdCollection: true } }); - events.emit(constants.EVENTS.AUCTION_INIT, prebidAuction[constants.EVENTS.AUCTION_INIT]); - events.emit(constants.EVENTS.BID_REQUESTED, prebidAuction[constants.EVENTS.BID_REQUESTED]); - events.emit(constants.EVENTS.NO_BID, prebidAuction[constants.EVENTS.NO_BID]); - events.emit(constants.EVENTS.BID_TIMEOUT, prebidAuction[constants.EVENTS.BID_TIMEOUT]); - events.emit(constants.EVENTS.BID_RESPONSE, prebidAuction[constants.EVENTS.BID_RESPONSE]); - events.emit(constants.EVENTS.AUCTION_END, prebidAuction[constants.EVENTS.AUCTION_END]); + events.emit(EVENTS.AUCTION_INIT, prebidAuction[EVENTS.AUCTION_INIT]); + events.emit(EVENTS.BID_REQUESTED, prebidAuction[EVENTS.BID_REQUESTED]); + events.emit(EVENTS.NO_BID, prebidAuction[EVENTS.NO_BID]); + events.emit(EVENTS.BID_TIMEOUT, prebidAuction[EVENTS.BID_TIMEOUT]); + events.emit(EVENTS.BID_RESPONSE, prebidAuction[EVENTS.BID_RESPONSE]); + events.emit(EVENTS.AUCTION_END, prebidAuction[EVENTS.AUCTION_END]); expect(localStorage.getItem('yuktamediaAnalytics_session_id')).to.equal(null); }); }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index 54b61f19506..fa4b50d7693 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -1,13 +1,13 @@ import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; import {config} from 'src/config'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import {server} from '../../mocks/xhr.js'; import {logError} from '../../../src/utils'; let utils = require('src/utils'); let events = require('src/events'); -const EVENTS = { +const SAMPLE_EVENTS = { AUCTION_END: { 'auctionId': '75e394d9', 'timestamp': 1638441234544, @@ -388,22 +388,22 @@ describe('Zeta Global SSP Analytics Adapter', function() { it('Move ZetaParams through analytics events', function() { this.timeout(3000); - events.emit(CONSTANTS.EVENTS.AUCTION_END, EVENTS.AUCTION_END); - events.emit(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, EVENTS.AD_RENDER_SUCCEEDED); + events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); expect(requests.length).to.equal(2); const auctionEnd = JSON.parse(requests[0].requestBody); const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(EVENTS.AUCTION_END.adUnits[0].bids[0].params); - expect(EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); + expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].params); + expect(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); }); it('Keep only needed fields', function() { this.timeout(3000); - events.emit(CONSTANTS.EVENTS.AUCTION_END, EVENTS.AUCTION_END); - events.emit(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, EVENTS.AD_RENDER_SUCCEEDED); + events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); + events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); expect(requests.length).to.equal(2); const auctionEnd = JSON.parse(requests[0].requestBody); diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 9184601a76d..5d1a43cc57f 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -16,7 +16,7 @@ import { fireClickTrackers, setNativeResponseProperties, } from 'src/native.js'; -import CONSTANTS from 'src/constants.json'; +import { NATIVE_KEYS } from 'src/constants.js'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import { convertOrtbRequestToProprietaryNative, fromOrtbNativeRequest } from '../../src/native.js'; import {auctionManager} from '../../src/auctionManager.js'; @@ -199,9 +199,9 @@ describe('native.js', function () { it('gets native targeting keys', function () { const targeting = getNativeTargeting(bid); - expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); - expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal(bid.native.body); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + expect(targeting[NATIVE_KEYS.title]).to.equal(bid.native.title); + expect(targeting[NATIVE_KEYS.body]).to.equal(bid.native.body); + expect(targeting[NATIVE_KEYS.clickUrl]).to.equal( bid.native.clickUrl ); expect(targeting.hb_native_foo).to.equal(bid.native.foo); @@ -230,11 +230,11 @@ describe('native.js', function () { }; const targeting = getNativeTargeting(bid, deps(adUnit)); - expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal(bid.native.title); - expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal( + expect(targeting[NATIVE_KEYS.title]).to.equal(bid.native.title); + expect(targeting[NATIVE_KEYS.body]).to.equal( 'hb_native_body:123' ); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal( + expect(targeting[NATIVE_KEYS.clickUrl]).to.equal( 'hb_native_linkurl:123' ); expect(targeting.hb_native_foo).to.equal(bid.native.ext.foo); @@ -244,9 +244,9 @@ describe('native.js', function () { it('sends placeholdes targetings with ortb native response', function () { const targeting = getNativeTargeting(completeNativeBid); - expect(targeting[CONSTANTS.NATIVE_KEYS.title]).to.equal('Native Creative'); - expect(targeting[CONSTANTS.NATIVE_KEYS.body]).to.equal('Cool description great stuff'); - expect(targeting[CONSTANTS.NATIVE_KEYS.clickUrl]).to.equal('https://www.link.example'); + expect(targeting[NATIVE_KEYS.title]).to.equal('Native Creative'); + expect(targeting[NATIVE_KEYS.body]).to.equal('Cool description great stuff'); + expect(targeting[NATIVE_KEYS.clickUrl]).to.equal('https://www.link.example'); }); it('should only include native targeting keys with values', function () { @@ -269,9 +269,9 @@ describe('native.js', function () { const targeting = getNativeTargeting(bidWithUndefinedFields, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title, - CONSTANTS.NATIVE_KEYS.sponsoredBy, - CONSTANTS.NATIVE_KEYS.clickUrl, + NATIVE_KEYS.title, + NATIVE_KEYS.sponsoredBy, + NATIVE_KEYS.clickUrl, 'hb_native_foo', ]); }); @@ -294,7 +294,7 @@ describe('native.js', function () { }; const targeting = getNativeTargeting(bid, deps(adUnit)); - expect(Object.keys(targeting)).to.deep.equal([CONSTANTS.NATIVE_KEYS.title]); + expect(Object.keys(targeting)).to.deep.equal([NATIVE_KEYS.title]); }); it('should only include targeting if sendTargetingKeys not set to false', function () { @@ -342,10 +342,10 @@ describe('native.js', function () { const targeting = getNativeTargeting(bid, deps(adUnit)); expect(Object.keys(targeting)).to.deep.equal([ - CONSTANTS.NATIVE_KEYS.title, - CONSTANTS.NATIVE_KEYS.body, - CONSTANTS.NATIVE_KEYS.image, - CONSTANTS.NATIVE_KEYS.clickUrl, + NATIVE_KEYS.title, + NATIVE_KEYS.body, + NATIVE_KEYS.image, + NATIVE_KEYS.clickUrl, 'hb_native_foo', ]); }); @@ -353,7 +353,7 @@ describe('native.js', function () { it('should include rendererUrl in targeting', function () { const rendererUrl = 'https://www.renderer.com/'; const targeting = getNativeTargeting({...bid, native: {...bid.native, rendererUrl: {url: rendererUrl}}}, deps({})); - expect(targeting[CONSTANTS.NATIVE_KEYS.rendererUrl]).to.eql(rendererUrl); + expect(targeting[NATIVE_KEYS.rendererUrl]).to.eql(rendererUrl); }); it('fires impression trackers', function () { diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index c2f62842c7e..df837e5547e 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -7,7 +7,7 @@ import { handleNativeMessage, handleRender } from '../../../src/adRendering.js'; -import CONSTANTS from 'src/constants.json'; +import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; import {expect} from 'chai/index.mjs'; import {config} from 'src/config.js'; import {VIDEO} from '../../../src/mediaTypes.js'; @@ -65,7 +65,7 @@ describe('adRendering', () => { }); function expectAdRenderFailedEvent(reason) { - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({adId, reason})); + sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_FAILED, sinon.match({ adId, reason })); } describe('doRender', () => { @@ -103,7 +103,7 @@ describe('adRendering', () => { it('emits AD_RENDER_SUCCEDED', () => { doRender({renderFn, bidResponse}); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ + sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ bid: bidResponse, adId: bidResponse.adId })); @@ -114,7 +114,7 @@ describe('adRendering', () => { it('should emit AD_RENDER_FAILED on video bids', () => { bidResponse.mediaType = VIDEO; doRender({renderFn, bidResponse}); - expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT) + expectAdRenderFailedEvent(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT) }); } @@ -159,26 +159,26 @@ describe('adRendering', () => { describe('should emit AD_RENDER_FAILED', () => { it('when bidResponse is missing', () => { handleRender({adId}); - expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD); + expectAdRenderFailedEvent(AD_RENDER_FAILED_REASON.CANNOT_FIND_AD); sinon.assert.notCalled(doRenderStub); }); it('on exceptions', () => { doRenderStub.throws(new Error()); handleRender({adId, bidResponse}); - expectAdRenderFailedEvent(CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION); + expectAdRenderFailedEvent(AD_RENDER_FAILED_REASON.EXCEPTION); }); }) describe('when bid was already rendered', () => { beforeEach(() => { - bidResponse.status = CONSTANTS.BID_STATUS.RENDERED; + bidResponse.status = BID_STATUS.RENDERED; }); afterEach(() => { config.resetConfig(); }) it('should emit STALE_RENDER', () => { handleRender({adId, bidResponse}); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.STALE_RENDER, bidResponse); + sinon.assert.calledWith(events.emit, EVENTS.STALE_RENDER, bidResponse); sinon.assert.called(doRenderStub); }); it('should skip rendering if suppressStaleRender', () => { @@ -190,7 +190,7 @@ describe('adRendering', () => { it('should mark bid as won and emit BID_WON', () => { handleRender({renderFn, bidResponse}); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.BID_WON, bidResponse); + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bidResponse); sinon.assert.calledWith(auctionManager.addWinningBid, bidResponse); }) }) @@ -201,17 +201,17 @@ describe('adRendering', () => { beforeEach(() => { sandbox.stub(events, 'emit'); bid = { - status: CONSTANTS.BID_STATUS.RENDERED + status: BID_STATUS.RENDERED } }); it('emits AD_RENDER_FAILED with given reason', () => { - handleCreativeEvent({event: CONSTANTS.EVENTS.AD_RENDER_FAILED, info: {reason: 'reason', message: 'message'}}, bid); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({bid, reason: 'reason', message: 'message'})); + handleCreativeEvent({ event: EVENTS.AD_RENDER_FAILED, info: { reason: 'reason', message: 'message' } }, bid); + sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_FAILED, sinon.match({ bid, reason: 'reason', message: 'message' })); }); it('emits AD_RENDER_SUCCEEDED', () => { - handleCreativeEvent({event: CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED}, bid); - sinon.assert.calledWith(events.emit, CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, sinon.match({bid})); + handleCreativeEvent({ event: EVENTS.AD_RENDER_SUCCEEDED }, bid); + sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_SUCCEEDED, sinon.match({ bid })); }); it('logs an error on other events', () => { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index dac70696b4b..590d4829b49 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -12,7 +12,7 @@ import { getServerTestingsAds, getBidRequests } from 'test/fixtures/fixtures.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS, S2S } from 'src/constants.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { registerBidder } from 'src/adapters/bidderFactory.js'; @@ -28,7 +28,7 @@ var events = require('../../../../src/events'); const CONFIG = { enabled: true, - endpoint: CONSTANTS.S2S.DEFAULT_ENDPOINT, + endpoint: S2S.DEFAULT_ENDPOINT, timeout: 1000, maxBids: 1, adapter: 'prebidServer', @@ -172,7 +172,7 @@ describe('adapterManager tests', function () { // function to count BID_REQUESTED events let cnt = 0; let count = () => cnt++; - events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.on(EVENTS.BID_REQUESTED, count); let bidRequests = [{ 'bidderCode': 'appnexus', 'auctionId': '1863e370099523', @@ -207,7 +207,7 @@ describe('adapterManager tests', function () { adapterManager.callBids(adUnits, bidRequests, () => {}, () => {}); expect(cnt).to.equal(1); sinon.assert.calledOnce(appnexusAdapterMock.callBids); - events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.off(EVENTS.BID_REQUESTED, count); }); it('should give bidders access to bidder-specific config', function(done) { @@ -390,7 +390,7 @@ describe('adapterManager tests', function () { }); it('should NOT call onBidWon when the bid is S2S', () => { - bids[0].src = CONSTANTS.S2S.SRC + bids[0].src = S2S.SRC adapterManager.callBidWonBidder(bids[0].bidder, bids[0], adUnits); sinon.assert.notCalled(criteoSpec.onBidWon); }) @@ -407,7 +407,7 @@ describe('adapterManager tests', function () { }); it('should NOT call onSetTargeting when bid is S2S', () => { - bids[0].src = CONSTANTS.S2S.SRC; + bids[0].src = S2S.SRC; adapterManager.callSetTargetingBidder(bids[0].bidder, bids[0], adUnits); sinon.assert.notCalled(criteoSpec.onSetTargeting); }) @@ -421,7 +421,7 @@ describe('adapterManager tests', function () { sinon.assert.called(criteoSpec.onBidViewable); }); it('should NOT call onBidViewable when bid is S2S', () => { - bids[0].src = CONSTANTS.S2S.SRC; + bids[0].src = S2S.SRC; adapterManager.callBidViewableBidder(bids[0].bidder, bids[0]); sinon.assert.notCalled(criteoSpec.onBidViewable); }) @@ -649,11 +649,11 @@ describe('adapterManager tests', function () { beforeEach(function () { prebidServerAdapterMock.callBids.reset(); cnt = 0; - events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.on(EVENTS.BID_REQUESTED, count); }); afterEach(function () { - events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.off(EVENTS.BID_REQUESTED, count); }); it('should fire for s2s requests', function () { @@ -1037,11 +1037,11 @@ describe('adapterManager tests', function () { beforeEach(function () { prebidServerAdapterMock.callBids.reset(); cnt = 0; - events.on(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.on(EVENTS.BID_REQUESTED, count); }); afterEach(function () { - events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); + events.off(EVENTS.BID_REQUESTED, count); }); it('should fire for s2s requests', function () { @@ -1691,7 +1691,7 @@ describe('adapterManager tests', function () { }) } - events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + events.on(EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); adapterManager.makeBidRequests( adUnits, Date.now(), @@ -1700,7 +1700,7 @@ describe('adapterManager tests', function () { }, [] ); - events.off(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); + events.off(EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; }); } @@ -2075,11 +2075,11 @@ describe('adapterManager tests', function () { const ortb2Fragments = {}; const req = { bidderCode: 'appnexus', - src: CONSTANTS.S2S.SRC, + src: S2S.SRC, adUnitsS2SCopy: adUnits, bids: [{ bidder: 'appnexus', - src: CONSTANTS.S2S.SRC + src: S2S.SRC }] }; adapterManager.callBids(adUnits, [req], sinon.stub(), sinon.stub(), {request: sinon.stub(), done: sinon.stub()}, 1000, sinon.stub(), ortb2Fragments); diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index 5fe5a1accfc..1e70c938a57 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -5,7 +5,7 @@ import {expect} from 'chai'; import {userSync} from 'src/userSync.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; -import CONSTANTS from 'src/constants.json'; +import { EVENTS } from 'src/constants.js'; import * as events from 'src/events.js'; import {hook} from '../../../../src/hook.js'; import {auctionManager} from '../../../../src/auctionManager.js'; @@ -552,7 +552,7 @@ describe('bidderFactory', () => { expect(ajaxStub.calledTwice).to.equal(true); expect(eventEmitterSpy.getCalls() - .filter(call => call.args[0] === CONSTANTS.EVENTS.BEFORE_BIDDER_HTTP) + .filter(call => call.args[0] === EVENTS.BEFORE_BIDDER_HTTP) ).to.length(2); eventEmitterSpy.restore(); @@ -863,7 +863,7 @@ describe('bidderFactory', () => { expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + sinon.assert.calledWith(eventEmitterStub, EVENTS.BIDDER_ERROR, { error: xhrErrorMock, bidderRequest: MOCK_BIDS_REQUEST }); @@ -889,7 +889,7 @@ describe('bidderFactory', () => { expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + sinon.assert.calledWith(eventEmitterStub, EVENTS.BIDDER_ERROR, { error: xhrErrorMock, bidderRequest: MOCK_BIDS_REQUEST }); @@ -915,7 +915,7 @@ describe('bidderFactory', () => { expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + sinon.assert.calledWith(eventEmitterStub, EVENTS.BIDDER_ERROR, { error: xhrErrorMock, bidderRequest: MOCK_BIDS_REQUEST }); @@ -941,7 +941,7 @@ describe('bidderFactory', () => { expect(callBidderErrorStub.firstCall.args[0]).to.equal(CODE); expect(callBidderErrorStub.firstCall.args[1]).to.equal(xhrErrorMock); expect(callBidderErrorStub.firstCall.args[2]).to.equal(MOCK_BIDS_REQUEST); - sinon.assert.calledWith(eventEmitterStub, CONSTANTS.EVENTS.BIDDER_ERROR, { + sinon.assert.calledWith(eventEmitterStub, EVENTS.BIDDER_ERROR, { error: xhrErrorMock, bidderRequest: MOCK_BIDS_REQUEST }); diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index ba9aeff70d1..54ea942e373 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -7,7 +7,7 @@ import { } from 'src/targeting.js'; import {config} from 'src/config.js'; import {createBidReceived} from 'test/fixtures/fixtures.js'; -import CONSTANTS from 'src/constants.json'; +import { DEFAULT_TARGETING_KEYS, JSON_MAPPING, NATIVE_KEYS, STATUS, TARGETING_KEYS } from 'src/constants.js'; import {auctionManager} from 'src/auctionManager.js'; import * as utils from 'src/utils.js'; import {deepClone} from 'src/utils.js'; @@ -15,7 +15,7 @@ import {createBid} from '../../../../src/bidfactory.js'; import {hook} from '../../../../src/hook.js'; import {getHighestCpm} from '../../../../src/utils/reducers.js'; -function mkBid(bid, status = CONSTANTS.STATUS.GOOD) { +function mkBid(bid, status = STATUS.GOOD) { return Object.assign(createBid(status), bid); } @@ -40,10 +40,10 @@ const sampleBid = { 'size': '300x250', 'adserverTargeting': { 'foobar': '300x250', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '148018fe5e', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.53', - [CONSTANTS.TARGETING_KEYS.DEAL]: '1234' + [TARGETING_KEYS.BIDDER]: 'rubicon', + [TARGETING_KEYS.AD_ID]: '148018fe5e', + [TARGETING_KEYS.PRICE_BUCKET]: '0.53', + [TARGETING_KEYS.DEAL]: '1234' }, 'dealId': '1234', 'netRevenue': true, @@ -74,9 +74,9 @@ const bid2 = mkBid({ 'size': '300x250', 'adserverTargeting': { 'foobar': '300x250', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '5454545', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.25' + [TARGETING_KEYS.BIDDER]: 'rubicon', + [TARGETING_KEYS.AD_ID]: '5454545', + [TARGETING_KEYS.PRICE_BUCKET]: '0.25' }, 'netRevenue': true, 'currency': 'USD', @@ -104,9 +104,9 @@ const bid3 = mkBid({ 'size': '300x600', 'adserverTargeting': { 'foobar': '300x600', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'rubicon', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '48747745', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '0.75' + [TARGETING_KEYS.BIDDER]: 'rubicon', + [TARGETING_KEYS.AD_ID]: '48747745', + [TARGETING_KEYS.PRICE_BUCKET]: '0.75' }, 'netRevenue': true, 'currency': 'USD', @@ -166,18 +166,18 @@ const nativeBid1 = mkBid({ 'pbCg': '', 'size': '0x0', 'adserverTargeting': { - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '591e7c9354b633', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.SIZE]: '0x0', - [CONSTANTS.TARGETING_KEYS.SOURCE]: 'client', - [CONSTANTS.TARGETING_KEYS.FORMAT]: 'native', - [CONSTANTS.NATIVE_KEYS.title]: 'This is a Prebid Native Creative', - [CONSTANTS.NATIVE_KEYS.body]: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', - [CONSTANTS.NATIVE_KEYS.sponsoredBy]: 'Prebid.org', - [CONSTANTS.NATIVE_KEYS.clickUrl]: 'http://prebid.org/dev-docs/show-native-ads.html', - [CONSTANTS.NATIVE_KEYS.image]: 'http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', - [CONSTANTS.NATIVE_KEYS.icon]: 'http://vcdn.adnxs.com/p/creative-image/bd/59/a6/c6/bd59a6c6-0851-411d-a16d-031475a51312.png' + [TARGETING_KEYS.BIDDER]: 'appnexus', + [TARGETING_KEYS.AD_ID]: '591e7c9354b633', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.SIZE]: '0x0', + [TARGETING_KEYS.SOURCE]: 'client', + [TARGETING_KEYS.FORMAT]: 'native', + [NATIVE_KEYS.title]: 'This is a Prebid Native Creative', + [NATIVE_KEYS.body]: 'This is a Prebid Native Creative. There are many like it, but this one is mine.', + [NATIVE_KEYS.sponsoredBy]: 'Prebid.org', + [NATIVE_KEYS.clickUrl]: 'http://prebid.org/dev-docs/show-native-ads.html', + [NATIVE_KEYS.image]: 'http://vcdn.adnxs.com/p/creative-image/94/22/cd/0f/9422cd0f-f400-45d3-80f5-2b92629d9257.jpg', + [NATIVE_KEYS.icon]: 'http://vcdn.adnxs.com/p/creative-image/bd/59/a6/c6/bd59a6c6-0851-411d-a16d-031475a51312.png' } }); @@ -225,17 +225,17 @@ const nativeBid2 = mkBid({ 'pbCg': '', 'size': '0x0', 'adserverTargeting': { - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'dgads', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '6e0aba55ed54e5', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '1.90', - [CONSTANTS.TARGETING_KEYS.SIZE]: '0x0', - [CONSTANTS.TARGETING_KEYS.SOURCE]: 'client', - [CONSTANTS.TARGETING_KEYS.FORMAT]: 'native', - [CONSTANTS.NATIVE_KEYS.image]: 'https://ads-tr.bigmining.com/img/300x250.png', - [CONSTANTS.NATIVE_KEYS.title]: 'Test Title', - [CONSTANTS.NATIVE_KEYS.body]: 'Test Description', - [CONSTANTS.NATIVE_KEYS.sponsoredBy]: 'test.com', - [CONSTANTS.NATIVE_KEYS.clickUrl]: 'http://prebid.org/' + [TARGETING_KEYS.BIDDER]: 'dgads', + [TARGETING_KEYS.AD_ID]: '6e0aba55ed54e5', + [TARGETING_KEYS.PRICE_BUCKET]: '1.90', + [TARGETING_KEYS.SIZE]: '0x0', + [TARGETING_KEYS.SOURCE]: 'client', + [TARGETING_KEYS.FORMAT]: 'native', + [NATIVE_KEYS.image]: 'https://ads-tr.bigmining.com/img/300x250.png', + [NATIVE_KEYS.title]: 'Test Title', + [NATIVE_KEYS.body]: 'Test Description', + [NATIVE_KEYS.sponsoredBy]: 'test.com', + [NATIVE_KEYS.clickUrl]: 'http://prebid.org/' } }); @@ -351,7 +351,7 @@ describe('targeting tests', function () { return [ { 'code': adUnitCode, - [CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]: adServerTargeting + [JSON_MAPPING.ADSERVER_TARGETING]: adServerTargeting } ]; }); @@ -489,7 +489,7 @@ describe('targeting tests', function () { }); const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); - let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(TARGETING_KEYS.PRICE_BUCKET + '_') != -1) expect(limitedBids.length).to.equal(1); }); @@ -502,7 +502,7 @@ describe('targeting tests', function () { }); const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); - let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(TARGETING_KEYS.PRICE_BUCKET + '_') != -1) expect(limitedBids.length).to.equal(2); }); @@ -515,7 +515,7 @@ describe('targeting tests', function () { }); const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); - let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + let limitedBids = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(TARGETING_KEYS.PRICE_BUCKET + '_') != -1) expect(limitedBids.length).to.equal(2); }); @@ -611,7 +611,7 @@ describe('targeting tests', function () { beforeEach(function () { bidsReceived = [bid1, bid2, nativeBid1, nativeBid2].map(deepClone); bidsReceived.forEach((bid) => { - bid.adserverTargeting[CONSTANTS.TARGETING_KEYS.SOURCE] = 'test-source'; + bid.adserverTargeting[TARGETING_KEYS.SOURCE] = 'test-source'; bid.adUnitCode = 'adunit'; if (winningBid == null || bid.cpm > winningBid.cpm) { winningBid = bid; @@ -642,7 +642,7 @@ describe('targeting tests', function () { addTargetingKeys: ['SOURCE'] } }); - expect(targetingResult()).to.include.all.keys(...expandKey(CONSTANTS.TARGETING_KEYS.SOURCE)); + expect(targetingResult()).to.include.all.keys(...expandKey(TARGETING_KEYS.SOURCE)); }); it('should keep default and native keys', function() { @@ -651,9 +651,9 @@ describe('targeting tests', function () { addTargetingKeys: ['SOURCE'] } }); - const defaultKeys = new Set(Object.values(CONSTANTS.DEFAULT_TARGETING_KEYS)); + const defaultKeys = new Set(Object.values(DEFAULT_TARGETING_KEYS)); if (FEATURES.NATIVE) { - Object.values(CONSTANTS.NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); + Object.values(NATIVE_KEYS).forEach((k) => defaultKeys.add(k)); } const expectedKeys = new Set(); @@ -670,8 +670,8 @@ describe('targeting tests', function () { it('should not be allowed together with allowTargetingKeys', function () { config.setConfig({ targetingControls: { - allowTargetingKeys: [CONSTANTS.TARGETING_KEYS.BIDDER], - addTargetingKeys: [CONSTANTS.TARGETING_KEYS.SOURCE] + allowTargetingKeys: [TARGETING_KEYS.BIDDER], + addTargetingKeys: [TARGETING_KEYS.SOURCE] } }); expect(targetingResult).to.throw(); @@ -849,12 +849,12 @@ describe('targeting tests', function () { // we should only get the targeting data for the one requested adunit expect(Object.keys(targeting).length).to.equal(1); - let sendAllBidCpm = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_') != -1) + let sendAllBidCpm = Object.keys(targeting['/123456/header-bid-tag-0']).filter(key => key.indexOf(TARGETING_KEYS.PRICE_BUCKET + '_') != -1) // we shouldn't get more than 1 key for hb_pb_${bidder} expect(sendAllBidCpm.length).to.equal(1); // expect the winning CPM to be equal to the sendAllBidCPM - expect(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]); + expect(targeting['/123456/header-bid-tag-0'][TARGETING_KEYS.PRICE_BUCKET + '_rubicon']).to.deep.equal(targeting['/123456/header-bid-tag-0'][TARGETING_KEYS.PRICE_BUCKET]); }); if (FEATURES.NATIVE) { diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 7f55a2cddf0..deb80873cfa 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -27,6 +27,7 @@ import {enrichFPD} from '../../../src/fpd/enrichment.js'; import {mockFpdEnrichments} from '../../helpers/fpd.js'; import {generateUUID} from '../../../src/utils.js'; import {getCreativeRenderer} from '../../../src/creativeRenderers.js'; +import { BID_STATUS, EVENTS, GRANULARITY_OPTIONS, TARGETING_KEYS } from 'src/constants.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -34,7 +35,6 @@ var expect = require('chai').expect; var utils = require('src/utils'); var adapterManager = require('src/adapterManager').default; var events = require('src/events'); -var CONSTANTS = require('src/constants.json'); // These bid adapters are required to be loaded for the following tests to work require('modules/appnexusBidAdapter'); @@ -284,7 +284,7 @@ describe('Unit: Prebid Module', function () { it('should return targeting info as a string', function () { const adUnitCode = config.adUnitCodes[0]; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); - var expected = 'foobar=0x0%2C300x250%2C300x600&' + CONSTANTS.TARGETING_KEYS.SIZE + '=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift=0x0&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon=300x600&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; var result = $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr(adUnitCode); assert.equal(expected, result, 'returns expected string of ad targeting info'); }); @@ -330,17 +330,17 @@ describe('Unit: Prebid Module', function () { var expected = { '/19968336/header-bid-tag-0': { foobar: '0x0,300x250,300x600', - [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '300x250', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '728x90', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [TARGETING_KEYS.BIDDER]: 'appnexus' } }; assert.deepEqual(targeting, expected); @@ -378,17 +378,17 @@ describe('Unit: Prebid Module', function () { '/19968336/header-bid-tag-0': { foobar: '300x250,300x600', always_use_me: 'abc', - [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '300x250', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '728x90', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [TARGETING_KEYS.BIDDER]: 'appnexus' } }; assert.deepEqual(targeting, expected); @@ -407,7 +407,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.bidderSettings = { 'standard': { adserverTargeting: [{ - key: CONSTANTS.TARGETING_KEYS.BIDDER, + key: TARGETING_KEYS.BIDDER, val: function(bidResponse) { return bidResponse.bidderCode; } @@ -417,7 +417,7 @@ describe('Unit: Prebid Module', function () { return bidResponse.adId; } }, { - key: CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + key: TARGETING_KEYS.PRICE_BUCKET, val: function(bidResponse) { return bidResponse.pbMg; } @@ -436,17 +436,17 @@ describe('Unit: Prebid Module', function () { '/19968336/header-bid-tag-0': { foobar: '300x250', custom_ad_id: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '300x250', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { foobar: '728x90', - [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', + [TARGETING_KEYS.SIZE]: '728x90', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [TARGETING_KEYS.BIDDER]: 'appnexus', custom_ad_id: '24bd938435ec3fc' } }; @@ -607,32 +607,32 @@ describe('Unit: Prebid Module', function () { indexStub.restore(); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 0 to 5', function () { RESPONSE.tags[0].ads[0].cpm = 2.1234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('2.12'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('2.12'); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 5 to 8', function () { RESPONSE.tags[0].ads[0].cpm = 6.78; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('6.75'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('6.75'); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 8 to 20', function () { RESPONSE.tags[0].ads[0].cpm = 19.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('19.50'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('19.50'); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' when using bid.cpm is between 20 to 25', function () { RESPONSE.tags[0].ads[0].cpm = 21.5234; auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('21.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('21.00'); }); }); @@ -867,7 +867,7 @@ describe('Unit: Prebid Module', function () { indexStub.restore(); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 0 - 5', function () { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -878,10 +878,10 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); }); - it('should get correct ' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', function() { + it('should get correct ' + TARGETING_KEYS.PRICE_BUCKET + ' with cpm between 21 - 100', function () { initTestConfig({ adUnits: [createAdUnit('div-gpt-ad-1460505748561-0')], adUnitCodes: ['div-gpt-ad-1460505748561-0'] @@ -892,7 +892,7 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('43.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('43.00'); }); it('should only apply price granularity if bid media type matches', function () { @@ -906,7 +906,7 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.25'); if (FEATURES.VIDEO) { ajaxStub.restore(); @@ -921,7 +921,7 @@ describe('Unit: Prebid Module', function () { auction.callBids(cbTimeout); let bidTargeting = targeting.getAllTargeting(); - expect(bidTargeting['div-gpt-ad-1460505748561-0'][CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); + expect(bidTargeting['div-gpt-ad-1460505748561-0'][TARGETING_KEYS.PRICE_BUCKET]).to.equal('3.00'); } }); }); @@ -1067,7 +1067,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); $$PREBID_GLOBAL$$.setTargetingForGPTAsync('/19968336/header-bid-tag-0'); - expect(slots[0].spySetTargeting.args).to.deep.contain.members([[CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus'], [CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); + expect(slots[0].spySetTargeting.args).to.deep.contain.members([[TARGETING_KEYS.BIDDER, 'appnexus'], [TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); }); it('should set targeting when passed an array of ad unit codes with enableSendAllBids', function () { @@ -1076,7 +1076,7 @@ describe('Unit: Prebid Module', function () { $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); $$PREBID_GLOBAL$$.setTargetingForGPTAsync(['/19968336/header-bid-tag-0']); - expect(slots[0].spySetTargeting.args).to.deep.contain.members([[CONSTANTS.TARGETING_KEYS.BIDDER, 'appnexus'], [CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); + expect(slots[0].spySetTargeting.args).to.deep.contain.members([[TARGETING_KEYS.BIDDER, 'appnexus'], [TARGETING_KEYS.AD_ID + '_appnexus', '233bcbee889d46d'], [TARGETING_KEYS.PRICE_BUCKET + '_appnexus', '10.00']]); }); it('should set targeting from googletag data', function () { @@ -1123,19 +1123,19 @@ describe('Unit: Prebid Module', function () { var expected = [ [ - CONSTANTS.TARGETING_KEYS.BIDDER, + TARGETING_KEYS.BIDDER, 'appnexus' ], [ - CONSTANTS.TARGETING_KEYS.AD_ID, + TARGETING_KEYS.AD_ID, '233bcbee889d46d' ], [ - CONSTANTS.TARGETING_KEYS.PRICE_BUCKET, + TARGETING_KEYS.PRICE_BUCKET, '10.00' ], [ - CONSTANTS.TARGETING_KEYS.SIZE, + TARGETING_KEYS.SIZE, '300x250' ], [ @@ -1360,8 +1360,8 @@ describe('Unit: Prebid Module', function () { var onWonEvent = sinon.stub(); var onStaleEvent = sinon.stub(); - $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + $$PREBID_GLOBAL$$.onEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.onEvent(EVENTS.STALE_RENDER, onStaleEvent); pushBidResponseToAuction({ ad: "" @@ -1377,7 +1377,7 @@ describe('Unit: Prebid Module', function () { sinon.assert.calledWith(onWonEvent, adResponse); sinon.assert.notCalled(onStaleEvent); - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); // Reset call history for spies and stubs spyLogMessage.resetHistory(); @@ -1398,8 +1398,8 @@ describe('Unit: Prebid Module', function () { sinon.assert.calledWith(onStaleEvent, adResponse); // Clean up - $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); }); it('should stop stale rendering', function () { @@ -1411,8 +1411,8 @@ describe('Unit: Prebid Module', function () { // Setting suppressStaleRender to true explicitly configObj.setConfig({'auctionOptions': {'suppressStaleRender': true}}); - $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.onEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + $$PREBID_GLOBAL$$.onEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.onEvent(EVENTS.STALE_RENDER, onStaleEvent); pushBidResponseToAuction({ ad: "" @@ -1425,7 +1425,7 @@ describe('Unit: Prebid Module', function () { sinon.assert.calledOnce(spyAddWinningBid); sinon.assert.calledWith(spyAddWinningBid, adResponse); - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); sinon.assert.calledWith(onWonEvent, adResponse); sinon.assert.notCalled(onStaleEvent); @@ -1448,8 +1448,8 @@ describe('Unit: Prebid Module', function () { sinon.assert.calledWith(onStaleEvent, adResponse); // Clean up - $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(CONSTANTS.EVENTS.STALE_RENDER, onStaleEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); configObj.setConfig({'auctionOptions': {}}); }); }); @@ -2766,16 +2766,16 @@ describe('Unit: Prebid Module', function () { let expected = { '/19968336/header-bid-tag-0': { 'foobar': '0x0,300x250,300x600', - [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus' + [TARGETING_KEYS.SIZE]: '300x250', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [TARGETING_KEYS.BIDDER]: 'appnexus' }, '/19968336/header-bid-tag1': { - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.SIZE]: '728x90', + [TARGETING_KEYS.BIDDER]: 'appnexus', + [TARGETING_KEYS.AD_ID]: '24bd938435ec3fc', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.SIZE]: '728x90', 'foobar': '728x90' } } @@ -2813,7 +2813,7 @@ describe('Unit: Prebid Module', function () { it('should emit event BID_ACCEPTED when invoked', function () { var callback = sinon.spy(); $$PREBID_GLOBAL$$.onEvent('bidAccepted', callback); - events.emit(CONSTANTS.EVENTS.BID_ACCEPTED); + events.emit(EVENTS.BID_ACCEPTED); sinon.assert.calledOnce(callback); }); @@ -3059,7 +3059,7 @@ describe('Unit: Prebid Module', function () { describe('emit', function () { it('should be able to emit event without arguments', function () { var spyEventsEmit = sinon.spy(events, 'emit'); - events.emit(CONSTANTS.EVENTS.REQUEST_BIDS); + events.emit(EVENTS.REQUEST_BIDS); assert.ok(spyEventsEmit.calledWith('requestBids')); events.emit.restore(); }); @@ -3179,7 +3179,7 @@ describe('Unit: Prebid Module', function () { let priceGranularity = configObj.getConfig('priceGranularity'); let newCustomPriceBucket = configObj.getConfig('customPriceBucket'); expect(goodConfig).to.deep.equal(newCustomPriceBucket); - expect(priceGranularity).to.equal(CONSTANTS.GRANULARITY_OPTIONS.CUSTOM); + expect(priceGranularity).to.equal(GRANULARITY_OPTIONS.CUSTOM); }); }); @@ -3319,11 +3319,11 @@ describe('Unit: Prebid Module', function () { 'auctionId': 123456, 'adserverTargeting': { 'foobar': '300x250', - [CONSTANTS.TARGETING_KEYS.BIDDER]: 'appnexus', - [CONSTANTS.TARGETING_KEYS.AD_ID]: '233bcbee889d46d', - [CONSTANTS.TARGETING_KEYS.PRICE_BUCKET]: '10.00', - [CONSTANTS.TARGETING_KEYS.SIZE]: '300x250', - [CONSTANTS.TARGETING_KEYS.DEAL + '_appnexusDummyName']: '1234' + [TARGETING_KEYS.BIDDER]: 'appnexus', + [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', + [TARGETING_KEYS.PRICE_BUCKET]: '10.00', + [TARGETING_KEYS.SIZE]: '300x250', + [TARGETING_KEYS.DEAL + '_appnexusDummyName']: '1234' } } ]; @@ -3363,7 +3363,7 @@ describe('Unit: Prebid Module', function () { auction.getBidsReceived = function() { return _bidsReceived }; const highestBid1 = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); expect(highestBid1).to.deep.equal(_bidsReceived[1]) - _bidsReceived[1].status = CONSTANTS.BID_STATUS.RENDERED + _bidsReceived[1].status = BID_STATUS.RENDERED const highestBid2 = $$PREBID_GLOBAL$$.getHighestUnusedBidResponseForAdUnitCode('/19968336/header-bid-tag-0'); expect(highestBid2).to.deep.equal(_bidsReceived[2]) }) @@ -3457,7 +3457,7 @@ describe('Unit: Prebid Module', function () { const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + expect(markedBid.status).to.equal(BID_STATUS.RENDERED); }); it('try and mark the bid object, but fail because we supplied the wrong adId', function () { @@ -3465,7 +3465,7 @@ describe('Unit: Prebid Module', function () { const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.not.equal(CONSTANTS.BID_STATUS.RENDERED); + expect(markedBid.status).to.not.equal(BID_STATUS.RENDERED); }); it('marks the winning bid object as used for the given adUnitCode', function () { @@ -3474,7 +3474,7 @@ describe('Unit: Prebid Module', function () { const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + expect(markedBid.status).to.equal(BID_STATUS.RENDERED); }); it('marks a bid object as used for the given adId', function () { @@ -3483,7 +3483,7 @@ describe('Unit: Prebid Module', function () { const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, bid => bid.adId === winningBid.adId); - expect(markedBid.status).to.equal(CONSTANTS.BID_STATUS.RENDERED); + expect(markedBid.status).to.equal(BID_STATUS.RENDERED); }); }); } @@ -3553,11 +3553,11 @@ describe('Unit: Prebid Module', function () { expect(window.apntag.tags[adUnitCode].keywords).to.deep.equal({}); }); - it('should not find ' + CONSTANTS.TARGETING_KEYS.AD_ID + ' key in lowercase for all bidders', function() { + it('should not find ' + TARGETING_KEYS.AD_ID + ' key in lowercase for all bidders', function () { const adUnitCode = '/19968336/header-bid-tag-0'; $$PREBID_GLOBAL$$.setConfig({ enableSendAllBids: true }); targeting.setTargetingForAst(); - const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, CONSTANTS.TARGETING_KEYS.AD_ID.length) === CONSTANTS.TARGETING_KEYS.AD_ID)); + const keywords = Object.keys(window.apntag.tags[adUnitCode].keywords).filter(keyword => (keyword.substring(0, TARGETING_KEYS.AD_ID.length) === TARGETING_KEYS.AD_ID)); expect(keywords.length).to.equal(0); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index a7be4e327f0..189066f7f88 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -13,7 +13,7 @@ import 'modules/nativeRendering.js'; import {expect} from 'chai'; -var CONSTANTS = require('src/constants.json'); +import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; describe('secureCreatives', () => { let sandbox; @@ -160,10 +160,10 @@ describe('secureCreatives', () => { sinon.assert.calledWith(spyAddWinningBid, adResponse); sinon.assert.calledOnce(adResponse.renderer.render); sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); }); it('should allow stale rendering without config', function () { @@ -187,10 +187,10 @@ describe('secureCreatives', () => { sinon.assert.calledWith(spyAddWinningBid, adResponse); sinon.assert.calledOnce(adResponse.renderer.render); sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); resetHistories(adResponse.renderer.render); @@ -201,8 +201,8 @@ describe('secureCreatives', () => { sinon.assert.calledWith(spyAddWinningBid, adResponse); sinon.assert.calledOnce(adResponse.renderer.render); sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); }); it('should stop stale rendering with config', function () { @@ -228,10 +228,10 @@ describe('secureCreatives', () => { sinon.assert.calledWith(spyAddWinningBid, adResponse); sinon.assert.calledOnce(adResponse.renderer.render); sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - expect(adResponse).to.have.property('status', CONSTANTS.BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); resetHistories(adResponse.renderer.render); @@ -240,8 +240,8 @@ describe('secureCreatives', () => { sinon.assert.calledWith(spyLogWarn, warning); sinon.assert.notCalled(spyAddWinningBid); sinon.assert.notCalled(adResponse.renderer.render); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); configObj.setConfig({'auctionOptions': {}}); }); @@ -254,8 +254,8 @@ describe('secureCreatives', () => { }) }); receiveMessage(ev); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, adId: 'missing' })); }); @@ -272,8 +272,8 @@ describe('secureCreatives', () => { }) }); receiveMessage(ev) - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: CONSTANTS.AD_RENDER_FAILED_REASON.EXCEPTION, + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, adId: bidId })); }); @@ -368,9 +368,9 @@ describe('secureCreatives', () => { sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); sinon.assert.calledOnce(ev.source.postMessage); sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, CONSTANTS.EVENTS.STALE_RENDER); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); }); it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { @@ -392,17 +392,17 @@ describe('secureCreatives', () => { }); receiveMessage(ev); - sinon.assert.calledWith(stubEmit, CONSTANTS.EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); receiveMessage(ev); - stubEmit.withArgs(CONSTANTS.EVENTS.BID_WON, adResponse).calledOnce; + stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; }); }); describe('Prebid Event', () => { Object.entries({ 'unrendered': [false, (bid) => { delete bid.status; }], - 'rendered': [true, (bid) => { bid.status = CONSTANTS.BID_STATUS.RENDERED }] + 'rendered': [true, (bid) => { bid.status = BID_STATUS.RENDERED }] }).forEach(([test, [shouldEmit, prepBid]]) => { describe(`for ${test} bids`, () => { beforeEach(() => { @@ -414,7 +414,7 @@ describe('secureCreatives', () => { const event = makeEvent({ data: JSON.stringify({ message: 'Prebid Event', - event: CONSTANTS.EVENTS.AD_RENDER_FAILED, + event: EVENTS.AD_RENDER_FAILED, adId: bidId, info: { reason: 'Fail reason', @@ -423,7 +423,7 @@ describe('secureCreatives', () => { }) }); receiveMessage(event); - expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_FAILED, { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { adId: bidId, bid: adResponse, reason: 'Fail reason', @@ -435,12 +435,12 @@ describe('secureCreatives', () => { const event = makeEvent({ data: JSON.stringify({ message: 'Prebid Event', - event: CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, + event: EVENTS.AD_RENDER_SUCCEEDED, adId: bidId, }) }); receiveMessage(event); - expect(stubEmit.calledWith(CONSTANTS.EVENTS.AD_RENDER_SUCCEEDED, { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { adId: bidId, bid: adResponse, doc: null diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index c84fe124db6..e0a114d8cf6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,6 +1,6 @@ import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; import {expect} from 'chai'; -import CONSTANTS from 'src/constants.json'; +import { TARGETING_KEYS } from 'src/constants.js'; import * as utils from 'src/utils.js'; import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; import {binarySearch, deepEqual, memoize, waitForElementToLoad} from 'src/utils.js'; @@ -65,7 +65,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=0x0%2C300x250%2C300x600&' + CONSTANTS.TARGETING_KEYS.SIZE + '=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_triplelift=0x0&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + CONSTANTS.TARGETING_KEYS.SIZE + '_appnexus=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pagescience=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brightcom=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + CONSTANTS.TARGETING_KEYS.SIZE + '_brealtime=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + CONSTANTS.TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + CONSTANTS.TARGETING_KEYS.SIZE + '_rubicon=300x600&' + CONSTANTS.TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + CONSTANTS.TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + CONSTANTS.TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; + var expected = 'foobar=0x0%2C300x250%2C300x600&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; assert.equal(output, expected); }); From 269437b5f8100aa0ad39f50adcba92dc949ee794 Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Tue, 16 Apr 2024 10:28:22 +1000 Subject: [PATCH 631/760] Adnuntius Bid Adapter: network-scope metadata from adserver. (#11324) --- .../gpt/adnuntius_example.html | 8 +- modules/adnuntiusBidAdapter.js | 80 ++++++++++++------- test/spec/modules/adnuntiusBidAdapter_spec.js | 13 +-- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/integrationExamples/gpt/adnuntius_example.html b/integrationExamples/gpt/adnuntius_example.html index b61c4e0674e..541a5888f3b 100644 --- a/integrationExamples/gpt/adnuntius_example.html +++ b/integrationExamples/gpt/adnuntius_example.html @@ -17,7 +17,7 @@ bidder: 'adnuntius', params: { auId: "201208", - network: "adnuntius", + network: "1287", bidType: 'netBid' } }] @@ -50,6 +50,12 @@ } }); + pbjs.bidderSettings = { + standard: { + storageAllowed: true + } + }; + pbjs.addAdUnits(adUnits); pbjs.requestBids({bidsBackHandler: initAdserver}); }); diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 97419fb94bd..78f9f4c19aa 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -15,7 +15,8 @@ const GVLID = 855; const DEFAULT_VAST_VERSION = 'vast4' const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; -const META_DATA_KEY = 'adn.metaData'; +const METADATA_KEY = 'adn.metaData'; +const METADATA_KEY_SEPARATOR = '@@@'; export const misc = { getUnixTimestamp: function (addDays, asMinutes) { @@ -28,23 +29,28 @@ const storageTool = (function () { const storage = getStorageManager({ bidderCode: BIDDER_CODE }); let metaInternal; - const getMetaInternal = function () { + const getMetaDataFromLocalStorage = function (pNetwork) { if (!storage.localStorageIsEnabled()) { return []; } let parsedJson; try { - parsedJson = JSON.parse(storage.getDataFromLocalStorage(META_DATA_KEY)); + parsedJson = JSON.parse(storage.getDataFromLocalStorage(METADATA_KEY)); } catch (e) { return []; } + let network = pNetwork; + if (Array.isArray(pNetwork)) { + network = (pNetwork.find((p) => p.network) || {}).network; + } + let filteredEntries = parsedJson ? parsedJson.filter((datum) => { if (datum.key === 'voidAuIds' && Array.isArray(datum.value)) { return true; } - return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp(); + return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp() && (!network || network === datum.network); }) : []; const voidAuIdsEntry = filteredEntries.find(entry => entry.key === 'voidAuIds'); if (voidAuIdsEntry) { @@ -57,7 +63,7 @@ const storageTool = (function () { return filteredEntries; }; - const setMetaInternal = function (apiResponse) { + const setMetaInternal = function (apiRespMetadata, network) { if (!storage.localStorageIsEnabled()) { return; } @@ -74,41 +80,48 @@ const storageTool = (function () { return notNewExistingAuIds.concat(apiIdsArray) || []; } - const metaAsObj = getMetaInternal().reduce((a, entry) => ({ ...a, [entry.key]: { value: entry.value, exp: entry.exp } }), {}); - for (const key in apiResponse) { + // use the metadata key separator to distinguish the same key for different networks. + const metaAsObj = getMetaDataFromLocalStorage().reduce((a, entry) => ({ ...a, [entry.key + METADATA_KEY_SEPARATOR + (entry.network ? entry.network : '')]: { value: entry.value, exp: entry.exp, network: entry.network } }), {}); + for (const key in apiRespMetadata) { if (key !== 'voidAuIds') { - metaAsObj[key] = { - value: apiResponse[key], - exp: misc.getUnixTimestamp(100) + metaAsObj[key + METADATA_KEY_SEPARATOR + network] = { + value: apiRespMetadata[key], + exp: misc.getUnixTimestamp(100), + network: network } } } - const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiResponse.voidAuIds); + const currentAuIds = updateVoidAuIds(metaAsObj.voidAuIds || [], apiRespMetadata.voidAuIds); if (currentAuIds.length > 0) { metaAsObj.voidAuIds = { value: currentAuIds }; } const metaDataForSaving = Object.entries(metaAsObj).map((entrySet) => { - if (entrySet[0] === 'voidAuIds') { + if (entrySet.length !== 2) { + return {}; + } + const key = entrySet[0].split(METADATA_KEY_SEPARATOR)[0]; + if (key === 'voidAuIds') { return { - key: entrySet[0], + key: key, value: entrySet[1].value }; } return { - key: entrySet[0], + key: key, value: entrySet[1].value, - exp: entrySet[1].exp + exp: entrySet[1].exp, + network: entrySet[1].network } - }); - storage.setDataInLocalStorage(META_DATA_KEY, JSON.stringify(metaDataForSaving)); + }).filter(entry => entry.key); + storage.setDataInLocalStorage(METADATA_KEY, JSON.stringify(metaDataForSaving)); }; - const getUsi = function (meta, ortb2, bidderRequest) { + const getUsi = function (meta, ortb2, bidParams) { // Fetch user id from parameters. - for (let i = 0; i < (bidderRequest.bids || []).length; i++) { - const bid = bidderRequest.bids[i]; - if (bid.params && bid.params.userId) { - return bid.params.userId; + for (let i = 0; i < bidParams.length; i++) { + const bidParam = bidParams[i]; + if (bidParam.userId) { + return bidParam.userId; } } if (ortb2 && ortb2.user && ortb2.user.id) { @@ -136,8 +149,11 @@ const storageTool = (function () { return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; - metaInternal = getMetaInternal().reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); - metaInternal.usi = getUsi(metaInternal, ortb2, bidderRequest); + const bidParams = (bidderRequest.bids || []).map((b) => { + return b.params ? b.params : {}; + }); + metaInternal = getMetaDataFromLocalStorage(bidParams).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); + metaInternal.usi = getUsi(metaInternal, ortb2, bidParams); if (!metaInternal.usi) { delete metaInternal.usi; } @@ -148,15 +164,17 @@ const storageTool = (function () { } metaInternal.segments = getSegmentsFromOrtb(ortb2); }, - saveToStorage: function (serverData) { - setMetaInternal(serverData); + saveToStorage: function (serverData, network) { + setMetaInternal(serverData, network); }, getUrlRelatedData: function () { + // getting the URL information is theoretically not network-specific const { segments, usi, voidAuIdsArray } = metaInternal; return { segments, usi, voidAuIdsArray }; }, - getPayloadRelatedData: function () { - const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = metaInternal; + getPayloadRelatedData: function (network) { + // getting the payload data should be network-specific + const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({...a, [entry.key]: entry.value}), {}); return payloadRelatedData; } }; @@ -223,7 +241,7 @@ export const spec = { networks[network].adUnits = networks[network].adUnits || []; if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; - const payloadRelatedData = storageTool.getPayloadRelatedData(); + const payloadRelatedData = storageTool.getPayloadRelatedData(bid.params.network); if (Object.keys(payloadRelatedData).length > 0) { networks[network].metaData = payloadRelatedData; } @@ -239,7 +257,7 @@ export const spec = { } const requests = []; - const networkKeys = Object.keys(networks) + const networkKeys = Object.keys(networks); for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) } @@ -257,7 +275,7 @@ export const spec = { interpretResponse: function (serverResponse, bidRequest) { if (serverResponse.body.metaData) { - storageTool.saveToStorage(serverResponse.body.metaData); + storageTool.saveToStorage(serverResponse.body.metaData, serverResponse.body.network); } const adUnits = serverResponse.body.adUnits; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 0e0206c2933..2fa1a52e863 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -12,7 +12,7 @@ describe('adnuntiusBidAdapter', function() { const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const usi = utils.generateUUID() - const meta = [{key: 'valueless'}, {value: 'keyless'}, {key: 'voidAuIds'}, {key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp()}, {exp: misc.getUnixTimestamp(1)}]}, {key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1)}, {key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp()}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}, {key: 'usi', value: usi, exp: misc.getUnixTimestamp(100)}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}] + const meta = [{key: 'valueless'}, {value: 'keyless'}, {key: 'voidAuIds'}, {key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp()}, {exp: misc.getUnixTimestamp(1)}]}, {key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1)}, {key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1)}, {key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp()}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius'}, {key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius'}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}] let storage; before(() => { @@ -257,7 +257,8 @@ describe('adnuntiusBidAdapter', function() { 'usi': 'from-api-server dude', 'voidAuIds': '00000000000abcde;00000000000fffff', 'randomApiKey': 'randomApiValue' - } + }, + 'network': 'some-network-id' } } const serverVideoResponse = { @@ -464,7 +465,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"metaData":{"valid":"also-valid"}}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); }); it('Test requests with no local storage', function() { @@ -695,7 +696,7 @@ describe('adnuntiusBidAdapter', function() { network: 'adnuntius' } } - ] + ]; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') @@ -913,10 +914,11 @@ describe('adnuntiusBidAdapter', function() { expect(interpretedResponse[1].dealCount).to.equal(0); const results = JSON.parse(storage.getDataFromLocalStorage('adn.metaData')); - const usiEntry = results.find(entry => entry.key === 'usi'); + const usiEntry = results.find(entry => entry.key === 'usi' && entry.network === 'some-network-id'); expect(usiEntry.key).to.equal('usi'); expect(usiEntry.value).to.equal('from-api-server dude'); expect(usiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + expect(usiEntry.network).to.equal('some-network-id') const voidAuIdsEntry = results.find(entry => entry.key === 'voidAuIds'); expect(voidAuIdsEntry.key).to.equal('voidAuIds'); @@ -937,6 +939,7 @@ describe('adnuntiusBidAdapter', function() { const randomApiEntry = results.find(entry => entry.key === 'randomApiKey'); expect(randomApiEntry.key).to.equal('randomApiKey'); expect(randomApiEntry.value).to.equal('randomApiValue'); + expect(randomApiEntry.network).to.equal('some-network-id'); expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); }); From d2117febee739efe5128d5609abfdaaedeaab7b8 Mon Sep 17 00:00:00 2001 From: Carlos Felix Date: Tue, 16 Apr 2024 06:19:50 -0500 Subject: [PATCH 632/760] encode the 33across `fp` query parameter (#11342) --- modules/33acrossIdSystem.js | 2 +- test/spec/modules/33acrossIdSystem_spec.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index 33086562111..e0f7435a1ec 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -80,7 +80,7 @@ function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig); if (fp) { - params.fp = fp; + params.fp = encodeURIComponent(fp); } return params; diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index cbc5b277e30..364f7d2845c 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -493,7 +493,7 @@ describe('33acrossIdSystem', () => { }); context('when a first-party ID is present in local storage', () => { - it('should call endpoint with the first-party ID included', () => { + it('should call endpoint with the encoded first-party ID included', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { @@ -506,13 +506,13 @@ describe('33acrossIdSystem', () => { sinon.stub(storage, 'getDataFromLocalStorage') .withArgs('33acrossIdFp') - .returns('33acrossIdFpValue'); + .returns('33acrossIdFpValue+'); callback(completeCallback); const [request] = server.requests; - expect(request.url).to.contain('fp=33acrossIdFpValue'); + expect(request.url).to.contain('fp=33acrossIdFpValue%2B'); storage.getDataFromLocalStorage.restore(); }); From 5ee5129a65c6da97b6ca8189ba2d26a74ee93461 Mon Sep 17 00:00:00 2001 From: Paulius Imbrasas <880130+CremboC@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:52:07 +0100 Subject: [PATCH 633/760] permutiveRtdProvider: add support for topics (#11330) --- modules/permutiveRtdProvider.js | 23 ++++++++++++-- .../spec/modules/permutiveRtdProvider_spec.js | 30 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index 5a63990f84f..c42a15d9197 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -104,6 +104,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const ssps = segmentData?.ssp?.ssps ?? [] const sspCohorts = segmentData?.ssp?.cohorts ?? [] + const topics = segmentData?.topics ?? {} const bidders = new Set([...acBidders, ...ssps]) bidders.forEach(function (bidder) { @@ -121,7 +122,7 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { cohorts = [...new Set([...cohorts, ...sspCohorts])].slice(0, maxSegs) } - const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, transformationConfigs, segmentData) + const nextConfig = updateOrtbConfig(bidder, currConfig, cohorts, sspCohorts, topics, transformationConfigs, segmentData) bidderOrtb2[bidder] = nextConfig.ortb2 }) } @@ -134,10 +135,11 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs + * @param {Object} topics - Privacy Sandbox Topics, keyed by IAB taxonomy version (600, 601, etc.) * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ -function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transformationConfigs, segmentData) { +function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, topics, transformationConfigs, segmentData) { logger.logInfo(`Current ortb2 config`, { bidder, config: currConfig }) const customCohortsData = deepAccess(segmentData, bidder) || [] @@ -161,9 +163,21 @@ function updateOrtbConfig(bidder, currConfig, segmentIDs, sspSegmentIDs, transfo const ortbConfig = mergeDeep({}, currConfig) const currentUserData = deepAccess(ortbConfig, 'ortb2.user.data') || [] + let topicsUserData = [] + for (const [k, value] of Object.entries(topics)) { + topicsUserData.push({ + name, + ext: { + segtax: Number(k) + }, + segment: value.map(topic => ({ id: topic.toString() })), + }) + } + const updatedUserData = currentUserData .filter(el => el.name !== permutiveUserData.name && el.name !== customCohortsUserData.name) .concat(permutiveUserData, transformedUserData, customCohortsUserData) + .concat(topicsUserData) logger.logInfo(`Updating ortb2.user.data`, { bidder, user_data: updatedUserData }) deepSetValue(ortbConfig, 'ortb2.user.data', updatedUserData) @@ -311,6 +325,7 @@ export function getSegments (maxSegs) { cohorts: [], ssps: [] }), + topics: readSegments('_ppsts', {}), } for (const bidder in segments) { @@ -318,6 +333,10 @@ export function getSegments (maxSegs) { if (segments[bidder].cohorts && Array.isArray(segments[bidder].cohorts)) { segments[bidder].cohorts = segments[bidder].cohorts.slice(0, maxSegs) } + } else if (bidder === 'topics') { + for (const taxonomy in segments[bidder]) { + segments[bidder][taxonomy] = segments[bidder][taxonomy].slice(0, maxSegs) + } } else { segments[bidder] = segments[bidder].slice(0, maxSegs) } diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 942ec2eaa46..73218fee7b9 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -212,6 +212,28 @@ describe('permutiveRtdProvider', function () { return { id: seg } }), }, + { + name: 'permutive.com', + ext: { + segtax: 600 + }, + segment: [ + { id: '1' }, + { id: '2' }, + { id: '3' }, + ], + }, + { + name: 'permutive.com', + ext: { + segtax: 601 + }, + segment: [ + { id: '100' }, + { id: '101' }, + { id: '102' }, + ], + }, ]) }) }) @@ -553,6 +575,10 @@ describe('permutiveRtdProvider', function () { for (const key in segments) { if (key === 'ssp') { expect(segments[key].cohorts).to.have.length(max) + } else if (key === 'topics') { + for (const topic in segments[key]) { + expect(segments[key][topic]).to.have.length(max) + } } else { expect(segments[key]).to.have.length(max) } @@ -684,6 +710,7 @@ function transformedTargeting (data = getTargetingData()) { rubicon: data._prubicons, gam: data._pdfps, ssp: data._pssps, + topics: data._ppsts, } } @@ -696,7 +723,8 @@ function getTargetingData () { _ppam: ['ppam1', 'ppam2'], _pindexs: ['pindex1', 'pindex2'], _pcrprs: ['pcrprs1', 'pcrprs2', 'dup'], - _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] } + _pssps: { ssps: ['xyz', 'abc', 'dup'], cohorts: ['123', 'abc'] }, + _ppsts: { '600': [1, 2, 3], '601': [100, 101, 102] }, } } From 2307f938d1428cb1fe319f3906435b9234bb4ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+krzysztofequativ@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:46:29 +0200 Subject: [PATCH 634/760] SAS Bid Adapter: Fledge support (#11348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smartadserver Bid Adapter: Add support for SDA user and site * Smartadserver Bid Adapter: Fix SDA support getConfig and add to unit testing * support floors per media type * Add GPP support * Rework payloads enriching * Add gpid support * Support additional video params * vpmt as array of numbers * Fix comment * Update default startDelay * Include videoMediaType's startdelay * Handle specified midroll * support paapi --------- Co-authored-by: Meven Courouble Co-authored-by: Krzysztof Sokół <88041828+smart-adserver@users.noreply.github.com> Co-authored-by: Dariusz O --- modules/smartadserverBidAdapter.js | 16 ++++- .../modules/smartadserverBidAdapter_spec.js | 65 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 7edaaa36957..5bc50190b11 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -226,6 +226,11 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } + const ae = deepAccess(bid, 'ortb2Imp.ext.ae'); + if (bidderRequest && bidderRequest.fledgeEnabled && ae) { + payload.ae = ae; + } + const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); @@ -266,7 +271,7 @@ export const spec = { */ interpretResponse: function (serverResponse, bidRequestString) { const bidResponses = []; - let response = serverResponse.body; + const response = serverResponse.body; try { if (response && !response.isNoAd && (response.ad || response.adUrl)) { const bidRequest = JSON.parse(bidRequestString.data); @@ -296,10 +301,19 @@ export const spec = { } bidResponses.push(bidResponse); + + const fledgeAuctionConfigs = response.auctionConfigs; + if (fledgeAuctionConfigs && Array.isArray(fledgeAuctionConfigs)) { + return { + bids: bidResponses, + fledgeAuctionConfigs + }; + } } } catch (error) { logError('Error while parsing smart server response', error); } + return bidResponses; }, diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index b01d95e2a4c..a2d053f841f 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -458,6 +458,27 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('Verify auctionConfigs', function () { + const request = spec.buildRequests(DEFAULT_PARAMS); + const auctionConfigs = [{ + bidId: '02g93e54w9ps', + config: { + decisionLogicUrl: 'https://seller.sas.com/decision_logic.js', + interestGroupBuyers: ['https://buyer.sas.com'], + seller: 'https://seller.sas.com' + } + }]; + const bids = spec.interpretResponse({ + body: { + ...BID_RESPONSE.body, + auctionConfigs + } + }, request[0]); + + expect(bids).to.have.property('bids').and.to.satisfy(value => (value === null || Array.isArray(value))); + expect(bids.fledgeAuctionConfigs).to.be.an('array').and.to.deep.equal(auctionConfigs); + }); + describe('gdpr tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); @@ -1505,6 +1526,50 @@ describe('Smart bid adapter tests', function () { }); }); + describe('Fledge for GPT', function () { + it('should send fledge eligibility flag when fledge enabled and ortb2Imp.ext.ae set', function () { + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + const ae = { + ae: 1 + }; + + bidRequests[0].ortb2Imp = { + ext: ae + }; + + const request = spec.buildRequests(bidRequests, { + fledgeEnabled: true + }); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('ae').and.to.equal(ae.ae); + }); + + it('should not send fledge eligibility flag when fledge not enabled', function () { + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + bidRequests[0].ortb2Imp = { + ext: { + ae: 1 + } + }; + + const request = spec.buildRequests(bidRequests); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.not.have.property('ae'); + }); + + it('should not send fledge eligibility flag when ortb2Imp.ext.ae not set', function () { + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + const request = spec.buildRequests(bidRequests, { + fledgeEnabled: true + }); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.not.have.property('ae'); + }); + }); + describe('#getValuableProperty method', function () { it('should return an object when calling with a number value', () => { const obj = spec.getValuableProperty('prop', 3); From 3ae46e075e09a12e16f60bd7ace6baf39bbd0c54 Mon Sep 17 00:00:00 2001 From: Andrii Pukh <152202940+apukh-magnite@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:47:47 +0300 Subject: [PATCH 635/760] Add PBS ARs flag to adUnit (#11347) --- modules/magniteAnalyticsAdapter.js | 2 ++ test/spec/modules/magniteAnalyticsAdapter_spec.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 6d45d05cd5c..1c82a9f3255 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -905,6 +905,8 @@ magniteAdapter.track = ({ eventType, args }) => { 'source', () => bid.src === 's2s' ? 'server' : 'client', 'status', () => 'no-bid' ]); + // add a pbs flag if one of the bids has a server source + if (adUnit.bids[bid.bidId].source === 'server') adUnit.pbsRequest = 1; // set acct site zone id on adunit if ((!adUnit.siteId || !adUnit.zoneId) && rubiconAliases.indexOf(bid.bidder) !== -1) { if (deepAccess(bid, 'params.accountId') == accountId) { diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 1798c96c341..c46e79614ce 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -1520,6 +1520,8 @@ describe('magnite analytics adapter', function () { // bid source should be 'server' expectedMessage.auctions[0].adUnits[0].bids[0].source = 'server'; + // if one of bids.source === server should add pbsRequest flag to adUnit + expectedMessage.auctions[0].adUnits[0].pbsRequest = 1; expectedMessage.bidsWon[0].source = 'server'; expect(message).to.deep.equal(expectedMessage); }); From 8cafa680f5024ae01bd861262b7f622d3987f999 Mon Sep 17 00:00:00 2001 From: Jordi Garcia Date: Tue, 16 Apr 2024 15:48:52 +0200 Subject: [PATCH 636/760] AzerionEdge RTD Module: Documentation: Required parameters (#11346) * Azerion Edge RTD Module: Initial release ### Type of change [x] Feature: New RTD Submodule ### Description of change Adds new Azerion Edge RTD module. Maintainer: azerion.com Contact: @garciapuig @mserrate @gguridi * Azerion Edge RTD Module: Initial release. Typo * AzerionEdge RTD Module: Documentation: Required parameters Type of change: Documentation/Feature Description of change: Specifying new required parameters on documentation. Updating examples. --------- Co-authored-by: Gorka Guridi --- integrationExamples/gpt/azerionedgeRtdProvider_example.html | 6 +++--- modules/azerionedgeRtdProvider.md | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/integrationExamples/gpt/azerionedgeRtdProvider_example.html b/integrationExamples/gpt/azerionedgeRtdProvider_example.html index 880fe5ed706..e85ab705235 100644 --- a/integrationExamples/gpt/azerionedgeRtdProvider_example.html +++ b/integrationExamples/gpt/azerionedgeRtdProvider_example.html @@ -40,17 +40,17 @@ { name: "azerionedge", waitForIt: true, - params: { bidders: ["appnexus"] }, + params: { bidders: ["improvedigital"] }, }, ], }, }); - pbjs.setBidderConfig({ bidders: ["appnexus"], config: {} }); + pbjs.setBidderConfig({ bidders: ["improvedigital"], config: {} }); pbjs.addAdUnits([ { code: TEST_DIV, mediaTypes: { banner: { sizes: TEST_SIZES } }, - bids: [{ bidder: "appnexus", params: { placementId: 13144370 } }], + bids: [{ bidder: "improvedigital", params: { placementId: 13144370 } }], }, ]); pbjs.requestBids({ diff --git a/modules/azerionedgeRtdProvider.md b/modules/azerionedgeRtdProvider.md index 2849bef3f63..6658907c480 100644 --- a/modules/azerionedgeRtdProvider.md +++ b/modules/azerionedgeRtdProvider.md @@ -1,6 +1,6 @@ --- layout: page_v2 -title: azerion edge RTD Provider +title: Azerion Edge RTD Provider display_name: Azerion Edge RTD Provider description: Client-side contextual cookieless audiences. page_type: module @@ -18,6 +18,8 @@ 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. +Please contact [edge@azerion.com](edge@azerion.com) for more information. + Maintainer: [azerion.com](https://www.azerion.com/) {:.no_toc} @@ -64,7 +66,7 @@ pbjs.setConfig( | :--- | :------- | :------------------ | :--------------- | | 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.key | `String` | Publisher partner specific key | Mandatory. The key is required for the module to work. If you haven't received one, please reach [support@improvedigital.com](support@improvedigital.com) | | 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 `{}`. | From d9ea9a986f5b855fed6374cf68787467c1089331 Mon Sep 17 00:00:00 2001 From: Keren Gattegno Date: Tue, 16 Apr 2024 16:49:59 +0300 Subject: [PATCH 637/760] topicsFpdModule: add undertone (#11334) * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * fix lint issue in undertone adapter spec * added user sync function to undertone adapter * * Update undertone adapter - change parameters - placementId parameter is now optional and not mandatory - undertoneBidAdapter.js * Updated undertone bid adapter tests accordingly - undertoneBidAdapter_spec.js * added user sync function to undertone adapter * added user sync function to undertone adapter * revert package-lock.json * added user sync function to undertone adapter * Update undertoneBidAdapter.js * Update browsers.json * Undertone: added GPP support and video plcmt * Fix lint issues * Add undertone to topics module --------- Co-authored-by: omerko Co-authored-by: Omer Koren Co-authored-by: AnnaPerion Co-authored-by: Oran Hollaender Co-authored-by: tamirnPerion <44399211+tamirnPerion@users.noreply.github.com> Co-authored-by: tamarm Co-authored-by: tamarm <40788385+tamarm-perion@users.noreply.github.com> Co-authored-by: Idan Botbol --- modules/topicsFpdModule.js | 3 +++ modules/topicsFpdModule.md | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 715f1ca735a..72f4068af7f 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -44,6 +44,9 @@ const bidderIframeList = { }, { bidder: 'discovery', iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html' + }, { + bidder: 'undertone', + iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html' }] } diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index 8ebddacf613..d187645f520 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -64,6 +64,10 @@ pbjs.setConfig({ bidder: 'discovery', iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html', expiry: 7 // Configurable expiry days + }, { + bidder: 'undertone', + iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html', + expiry: 7 // Configurable expiry days }] } .... From 8748f8feaa362b645bde7911b7d20e8a8ae9c091 Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Tue, 16 Apr 2024 06:53:40 -0700 Subject: [PATCH 638/760] Magnite Analytics: auction index + small cleanup (#11345) * move globals up + add auctionIndex * add / update tests --- modules/magniteAnalyticsAdapter.js | 18 ++++++++++-------- .../modules/magniteAnalyticsAdapter_spec.js | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 1c82a9f3255..225607ad6d3 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -34,6 +34,7 @@ const LAST_SEEN_EXPIRE_TIME = 1800000; // 30 mins const END_EXPIRE_TIME = 21600000; // 6 hours const MODULE_NAME = 'Magnite Analytics'; const BID_REJECTED_IPF = 'rejected-ipf'; +const DEFAULT_INTEGRATION = 'pbjs'; // List of known rubicon aliases // This gets updated on auction init to account for any custom aliases present @@ -48,6 +49,12 @@ const pbsErrorMap = { } let cookieless; +let browser; +let pageReferer; +let auctionIndex = 0; // count of auctions on page +let accountId; +let endpoint; + let prebidGlobal = getGlobal(); const { AUCTION_INIT, @@ -105,8 +112,6 @@ config.getConfig('s2sConfig', ({ s2sConfig }) => { serverConfig = s2sConfig; }); -const DEFAULT_INTEGRATION = 'pbjs'; - const adUnitIsOnlyInstream = adUnit => { return adUnit.mediaTypes && Object.keys(adUnit.mediaTypes).length === 1 && deepAccess(adUnit, 'mediaTypes.video.context') === 'instream'; } @@ -309,8 +314,6 @@ const addFloorData = floorData => { } } -let pageReferer; - const getTopLevelDetails = () => { let payload = { channel: 'web', @@ -645,9 +648,6 @@ export const detectBrowserFromUa = userAgent => { return 'OTHER'; } -let accountId; -let endpoint; - let magniteAdapter = adapter({ analyticsType: 'endpoint' }); magniteAdapter.originEnableAnalytics = magniteAdapter.enableAnalytics; @@ -703,6 +703,7 @@ magniteAdapter.disableAnalytics = function () { magniteAdapter._oldEnable = enableMgniAnalytics; endpoint = undefined; accountId = undefined; + auctionIndex = 0; resetConfs(); getHook('callPrebidCache').getHooks({ hook: callPrebidCacheHook }).remove(); magniteAdapter.originDisableAnalytics(); @@ -791,10 +792,10 @@ const getLatencies = (args, auctionStart) => { } } -let browser; magniteAdapter.track = ({ eventType, args }) => { switch (eventType) { case AUCTION_INIT: + auctionIndex += 1; // Update session cache.sessionData = storage.localStorageIsEnabled() && updateRpaCookie(); // set the rubicon aliases @@ -810,6 +811,7 @@ magniteAdapter.track = ({ eventType, args }) => { 'timeout as clientTimeoutMillis', ]); auctionData.accountId = accountId; + auctionData.auctionIndex = auctionIndex; // get browser if (!browser) { diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index c46e79614ce..731b4ab1682 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -239,6 +239,7 @@ const ANALYTICS_MESSAGE = { }, 'auctions': [ { + 'auctionIndex': 1, 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', 'auctionStart': 1658868383741, 'samplingFactor': 1, @@ -601,7 +602,22 @@ describe('magnite analytics adapter', function () { it(`should parse browser from ${testData.expected} user agent correctly`, function () { expect(detectBrowserFromUa(testData.ua)).to.equal(testData.expected); }); - }) + }); + + it('should increment auctionIndex each auction', function () { + // run 3 auctions + performStandardAuction(); + performStandardAuction(); + performStandardAuction(); + + expect(server.requests.length).to.equal(3); + server.requests.forEach((request, index) => { + let message = JSON.parse(request.requestBody); + + // should be index of array + 1 + expect(message?.auctions?.[0].auctionIndex).to.equal(index + 1); + }); + }); it('should pass along 1x1 size if no sizes in adUnit', function () { const auctionInit = utils.deepClone(MOCK.AUCTION_INIT); From da3e7d2304762ac94642b2a8af6af5f3a1ed7057 Mon Sep 17 00:00:00 2001 From: maelmrgt <77864748+maelmrgt@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:05:49 +0200 Subject: [PATCH 639/760] Greenbids Analytics Adapter: debug flag analytics (#11297) * greenbids analytics adapter: always sampled when degug * Greenbids analytics adapter: lint + test * greenbids analytics adapter: bump version * greenbids analytics adapter: minor fixes * greenbids analytics adapter: change the flag from pbjs_debut to greenbids_force_sampling * remove unused import * TEST: fix --------- Co-authored-by: Jean-Baptiste Pettit --- modules/greenbidsAnalyticsAdapter.js | 9 +++++++-- .../modules/greenbidsAnalyticsAdapter_spec.js | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index fd2bc1c25ea..d6293adac82 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -2,11 +2,11 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; -import {deepClone, generateUUID, logError, logInfo, logWarn} from '../src/utils.js'; +import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} from '../src/utils.js'; const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.2.0'; +export const ANALYTICS_VERSION = '2.2.1'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -26,6 +26,11 @@ export const BIDDER_STATUS = { const analyticsOptions = {}; export const isSampled = function(greenbidsId, samplingRate, exploratorySamplingSplit) { + const isSamplingForced = getParameterByName('greenbids_force_sampling'); + if (isSamplingForced) { + logInfo('Greenbids Analytics: sampling flag detected, forcing analytics'); + return true; + } if (samplingRate < 0 || samplingRate > 1) { logWarn('Sampling rate must be between 0 and 1'); return true; diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index efaeb06ae53..918da50d8bc 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -4,8 +4,9 @@ import { ANALYTICS_VERSION, BIDDER_STATUS } from 'modules/greenbidsAnalyticsAdapter.js'; import { - generateUUID, + generateUUID } from '../../../src/utils.js'; +import * as utils from 'src/utils.js'; import {expect} from 'chai'; import sinon from 'sinon'; @@ -424,4 +425,16 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0, 1.0)).to.be.false; }); }); + + describe('isSampled when analytic isforced', function() { + before(() => { + sinon.stub(utils, 'getParameterByName').callsFake(par => par === 'greenbids_force_sampling' ? true : undefined); + }); + it('should return determinist true when sampling flag activated', function() { + expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.true; + }); + after(() => { + utils.getParameterByName.restore(); + }); + }); }); From 524617df46e01655e384b960bfce4448e858df1e Mon Sep 17 00:00:00 2001 From: Catalin Ciocov Date: Tue, 16 Apr 2024 17:24:27 +0300 Subject: [PATCH 640/760] Improvedigital Bid Adapter : update razr (#11290) * Improve Digital PG flag * ImproveDigital - update internal logic due to ad markup changes * Updated tests --------- Co-authored-by: Jozef Bartek --- modules/improvedigitalBidAdapter.js | 3 ++- test/spec/modules/improvedigitalBidAdapter_spec.js | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 3a258dfa327..fbceeee6d90 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -391,7 +391,8 @@ const ID_RAZR = { const cfgStr = JSON.stringify(cfg).replace(/<\/script>/ig, '\\x3C/script>'); const s = ``; - bid.ad = bid.ad.replace(/]*>/, match => match + s); + // prepend RAZR config to ad markup: + bid.ad = s + bid.ad; this.installListener(); }, diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index a86b9be73e6..a955b716584 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -1071,7 +1071,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1081,7 +1081,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', }) ]; @@ -1094,7 +1094,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, From 44e8e9fcb9be27d7344646cf171d244c1afb4e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Sok=C3=B3=C5=82?= <88041828+krzysztofequativ@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:32:37 +0200 Subject: [PATCH 641/760] Revert "SAS Bid Adapter: Fledge support (#11348)" (#11349) This reverts commit 2307f938d1428cb1fe319f3906435b9234bb4ac8. --- modules/smartadserverBidAdapter.js | 16 +---- .../modules/smartadserverBidAdapter_spec.js | 65 ------------------- 2 files changed, 1 insertion(+), 80 deletions(-) diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 5bc50190b11..7edaaa36957 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -226,11 +226,6 @@ export const spec = { payload.us_privacy = bidderRequest.uspConsent; } - const ae = deepAccess(bid, 'ortb2Imp.ext.ae'); - if (bidderRequest && bidderRequest.fledgeEnabled && ae) { - payload.ae = ae; - } - const bannerMediaType = deepAccess(bid, 'mediaTypes.banner'); const videoMediaType = deepAccess(bid, 'mediaTypes.video'); const isSupportedVideoContext = videoMediaType && (videoMediaType.context === 'instream' || videoMediaType.context === 'outstream'); @@ -271,7 +266,7 @@ export const spec = { */ interpretResponse: function (serverResponse, bidRequestString) { const bidResponses = []; - const response = serverResponse.body; + let response = serverResponse.body; try { if (response && !response.isNoAd && (response.ad || response.adUrl)) { const bidRequest = JSON.parse(bidRequestString.data); @@ -301,19 +296,10 @@ export const spec = { } bidResponses.push(bidResponse); - - const fledgeAuctionConfigs = response.auctionConfigs; - if (fledgeAuctionConfigs && Array.isArray(fledgeAuctionConfigs)) { - return { - bids: bidResponses, - fledgeAuctionConfigs - }; - } } } catch (error) { logError('Error while parsing smart server response', error); } - return bidResponses; }, diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index a2d053f841f..b01d95e2a4c 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -458,27 +458,6 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); - it('Verify auctionConfigs', function () { - const request = spec.buildRequests(DEFAULT_PARAMS); - const auctionConfigs = [{ - bidId: '02g93e54w9ps', - config: { - decisionLogicUrl: 'https://seller.sas.com/decision_logic.js', - interestGroupBuyers: ['https://buyer.sas.com'], - seller: 'https://seller.sas.com' - } - }]; - const bids = spec.interpretResponse({ - body: { - ...BID_RESPONSE.body, - auctionConfigs - } - }, request[0]); - - expect(bids).to.have.property('bids').and.to.satisfy(value => (value === null || Array.isArray(value))); - expect(bids.fledgeAuctionConfigs).to.be.an('array').and.to.deep.equal(auctionConfigs); - }); - describe('gdpr tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); @@ -1526,50 +1505,6 @@ describe('Smart bid adapter tests', function () { }); }); - describe('Fledge for GPT', function () { - it('should send fledge eligibility flag when fledge enabled and ortb2Imp.ext.ae set', function () { - const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); - const ae = { - ae: 1 - }; - - bidRequests[0].ortb2Imp = { - ext: ae - }; - - const request = spec.buildRequests(bidRequests, { - fledgeEnabled: true - }); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.have.property('ae').and.to.equal(ae.ae); - }); - - it('should not send fledge eligibility flag when fledge not enabled', function () { - const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); - bidRequests[0].ortb2Imp = { - ext: { - ae: 1 - } - }; - - const request = spec.buildRequests(bidRequests); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.not.have.property('ae'); - }); - - it('should not send fledge eligibility flag when ortb2Imp.ext.ae not set', function () { - const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); - const request = spec.buildRequests(bidRequests, { - fledgeEnabled: true - }); - const requestContent = JSON.parse(request[0].data); - - expect(requestContent).to.not.have.property('ae'); - }); - }); - describe('#getValuableProperty method', function () { it('should return an object when calling with a number value', () => { const obj = spec.getValuableProperty('prop', 3); From 122a72a3ad0e3dd0a88b170ca4a116245c0d961a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 16 Apr 2024 17:01:54 -0700 Subject: [PATCH 642/760] PAAPI: add top level auction example (#11259) * bidderFactory fledge API: accept single fledge configs * PAAPI: add top level auction example * update example formatting * provide bidfloor in top level auctionSignals * update example decisionLogic to enforce auction floor, remove reporting * PR feedback * add reportResult --- gulpfile.js | 10 +- .../gpt/top-level-paapi/decisionLogic.js | 57 ++++++ .../gpt/top-level-paapi/tl_paapi_example.html | 188 ++++++++++++++++++ .../creative-renderer-native/renderer.js | 2 +- modules/paapi.js | 22 +- test/spec/modules/paapi_spec.js | 32 ++- 6 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 integrationExamples/gpt/top-level-paapi/decisionLogic.js create mode 100644 integrationExamples/gpt/top-level-paapi/tl_paapi_example.html diff --git a/gulpfile.js b/gulpfile.js index 17c421f4dc1..4dbd92587fe 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -446,7 +446,15 @@ function startLocalServer(options = {}) { port: port, host: INTEG_SERVER_HOST, root: './', - livereload: options.livereload + livereload: options.livereload, + middleware: function () { + return [ + function (req, res, next) { + res.setHeader('Ad-Auction-Allowed', 'True'); + next(); + } + ]; + } }); } diff --git a/integrationExamples/gpt/top-level-paapi/decisionLogic.js b/integrationExamples/gpt/top-level-paapi/decisionLogic.js new file mode 100644 index 00000000000..265234ffeba --- /dev/null +++ b/integrationExamples/gpt/top-level-paapi/decisionLogic.js @@ -0,0 +1,57 @@ +function logPrefix(scope) { + return [ + `%c PAAPI %c ${scope} %c`, + 'color: green; background-color:yellow; border: 1px solid black', + 'color: blue; border:1px solid black', + '', + ]; +} + +function scoreAd( + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals, + directFromSellerSignals +) { + console.group(...logPrefix('scoreAd'), 'Buyer:', browserSignals.interestGroupOwner); + console.log('Context:', JSON.stringify({ + adMetadata, + bid, + auctionConfig: { + ...auctionConfig, + componentAuctions: '[omitted]' + }, + trustedScoringSignals, + browserSignals, + directFromSellerSignals + }, ' ', ' ')); + + const result = { + desirability: bid, + allowComponentAuction: true, + }; + const {bidfloor, bidfloorcur} = auctionConfig.auctionSignals?.prebid || {}; + if (bidfloor) { + if (browserSignals.bidCurrency !== '???' && browserSignals.bidCurrency !== bidfloorcur) { + console.log(`Floor currency (${bidfloorcur}) does not match bid currency (${browserSignals.bidCurrency}), and currency conversion is not yet implemented. Rejecting bid.`); + result.desirability = -1; + } else if (bid < bidfloor) { + console.log(`Bid (${bid}) lower than contextual winner/floor (${bidfloor}). Rejecting bid.`); + result.desirability = -1; + result.rejectReason = 'bid-below-auction-floor'; + } + } + console.log('Result:', result); + console.groupEnd(); + return result; +} + +function reportResult(auctionConfig, browserSignals) { + console.group(...logPrefix('reportResult')); + console.log('Context', JSON.stringify({auctionConfig, browserSignals}, ' ', ' ')); + console.groupEnd(); + sendReportTo(`${auctionConfig.seller}/report/win?${Object.entries(browserSignals).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&')}`); + return {}; +} diff --git a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html b/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html new file mode 100644 index 00000000000..9a4991d2711 --- /dev/null +++ b/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html @@ -0,0 +1,188 @@ + + + + + + + + + + + +

Standalone PAAPI Prebid.js Example

+

Start local server with:

+gulp serve-fast --https +

Chrome flags:

+--enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://privacysandbox.openx.net/fledge/advertiser +

+
Div-1
+
+ +
+ + + diff --git a/libraries/creative-renderer-native/renderer.js b/libraries/creative-renderer-native/renderer.js index 509f7943af4..57d86fc8ce3 100644 --- a/libraries/creative-renderer-native/renderer.js +++ b/libraries/creative-renderer-native/renderer.js @@ -1,2 +1,2 @@ // this file is autogenerated, see creative/README.md -export const RENDERER = "!function(){\"use strict\";const e=JSON.parse('{\"X3\":{\"B5\":\"Prebid Native\"}}').X3.B5,t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}}();" \ No newline at end of file +export const RENDERER = "!function(){\"use strict\";const e=\"Prebid Native\",t={title:\"text\",data:\"value\",img:\"url\",video:\"vasttag\"};function n(e,t){return new Promise(((n,r)=>{const i=t.createElement(\"script\");i.onload=n,i.onerror=r,i.src=e,t.body.appendChild(i)}))}function r(e,t,r,i,o=n){const{rendererUrl:s,assets:a,ortb:d,adTemplate:c}=t,l=i.document;return s?o(s,l).then((()=>{if(\"function\"!=typeof i.renderAd)throw new Error(`Renderer from '${s}' does not define renderAd()`);const e=a||[];return e.ortb=d,i.renderAd(e)})):Promise.resolve(r(c??l.body.innerHTML))}window.render=function({adId:n,native:i},{sendMessage:o},s,a=r){const{head:d,body:c}=s.document,l=()=>o(e,{action:\"resizeNativeHeight\",height:c.offsetHeight,width:c.offsetWidth}),u=function(e,{assets:n=[],ortb:r,nativeKeys:i={}}){const o=Object.fromEntries(n.map((({key:e,value:t})=>[e,t])));let s=Object.fromEntries(Object.entries(i).flatMap((([t,n])=>{const r=o.hasOwnProperty(t)?o[t]:void 0;return[[`##${n}##`,r],[`${n}:${e}`,r]]})));return r&&Object.assign(s,{\"##hb_native_linkurl##\":r.link?.url,\"##hb_native_privacy##\":r.privacy},Object.fromEntries((r.assets||[]).flatMap((e=>{const n=Object.keys(t).find((t=>e[t]));return[n&&[`##hb_native_asset_id_${e.id}##`,e[n][t[n]]],e.link?.url&&[`##hb_native_asset_link_id_${e.id}##`,e.link.url]].filter((e=>e))})))),s=Object.entries(s).concat([[/##hb_native_asset_(link_)?id_\\d+##/g]]),function(e){return s.reduce(((e,[t,n])=>e.replaceAll(t,n||\"\")),e)}}(n,i);return d&&(d.innerHTML=u(d.innerHTML)),a(n,i,u,s).then((t=>{c.innerHTML=t,\"function\"==typeof s.postRenderAd&&s.postRenderAd({adId:n,...i}),s.document.querySelectorAll(\".pb-click\").forEach((t=>{const n=t.getAttribute(\"hb_native_asset_id\");t.addEventListener(\"click\",(()=>o(e,{action:\"click\",assetId:n})))})),o(e,{action:\"fireNativeImpressionTrackers\"}),\"complete\"===s.document.readyState?l():s.onload=l}))}}();" \ No newline at end of file diff --git a/modules/paapi.js b/modules/paapi.js index 5c76b2fb327..9122ecce1a0 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -3,7 +3,7 @@ */ import {config} from '../src/config.js'; import {getHook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {deepSetValue, logInfo, logWarn, mergeDeep, parseSizesInput} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; @@ -89,19 +89,33 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { return cfg; } -function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes}) { +function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { + const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []) const allReqs = bidderRequests?.flatMap(br => br.bids); const paapiConfigs = {}; (adUnitCodes || []).forEach(au => { paapiConfigs[au] = null; !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); - }) + }); Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => { const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); paapiConfigs[adUnitCode] = { + ...slotSignals, componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) }; + // TODO: need to flesh out size treatment: + // - which size should the paapi auction pick? (this uses the first one defined) + // - should we signal it to SSPs, and how? + // - what should we do if adapters pick a different one? + // - what does size mean for video and native? + const size = parseSizesInput(adUnitsByCode[adUnitCode]?.mediaTypes?.banner?.sizes)?.[0]?.split('x'); + if (size) { + paapiConfigs[adUnitCode].requestedSize = { + width: size[0], + height: size[1], + }; + } latestAuctionForAdUnit[adUnitCode] = auctionId; }); configsForAuction(auctionId, paapiConfigs); @@ -159,7 +173,7 @@ export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = fal output[au] = null; } } - }) + }); return output; } diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index 2c7c959ef19..c7d6d88bd12 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -129,6 +129,30 @@ describe('paapi module', () => { expect(getPAAPIConfig({auctionId})).to.eql({}); }); + it('should use first size as requestedSize', () => { + addComponentAuctionHook(nextFnSpy, { + auctionId, + adUnitCode: 'au1', + }, fledgeAuctionConfig); + events.emit(EVENTS.AUCTION_END, { + auctionId, + adUnits: [ + { + code: 'au1', + mediaTypes: { + banner: { + sizes: [[200, 100], [300, 200]] + } + } + } + ] + }); + expect(getPAAPIConfig({auctionId}).au1.requestedSize).to.eql({ + width: '200', + height: '100' + }) + }) + it('should augment auctionSignals with FPD', () => { addComponentAuctionHook(nextFnSpy, { auctionId, @@ -295,9 +319,11 @@ describe('paapi module', () => { it('should populate bidfloor/bidfloorcur', () => { addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); events.emit(EVENTS.AUCTION_END, payload); - const signals = getPAAPIConfig({auctionId}).au.componentAuctions[0].auctionSignals; - expect(signals.prebid?.bidfloor).to.eql(bidfloor); - expect(signals.prebid?.bidfloorcur).to.eql(bidfloorcur); + const cfg = getPAAPIConfig({auctionId}).au; + const signals = cfg.auctionSignals; + sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); + expect(signals?.prebid?.bidfloor).to.eql(bidfloor); + expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); }); }); }); From 43680d4aca77aa129955f9e9657d51a0834298f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Wed, 17 Apr 2024 16:21:01 +0200 Subject: [PATCH 643/760] Criteo Bid Adapter : Add support of pixel based user sync (#11303) --- modules/criteoBidAdapter.js | 50 +++++++++++++--- test/spec/modules/criteoBidAdapter_spec.js | 69 ++++++++++++++++++++-- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index 968a847708e..defe2c081e3 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -55,16 +55,18 @@ export const spec = { gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - getUserSyncs: function (syncOptions, _, gdprConsent, uspConsent) { - const fastBidVersion = config.getConfig('criteo.fastBidVersion'); - if (canFastBid(fastBidVersion)) { - return []; - } - - const refererInfo = getRefererInfo(); - const origin = 'criteoPrebidAdapter'; + getUserSyncs: function (syncOptions, _, gdprConsent, uspConsent, gppConsent = {}) { + let { gppString = '', applicableSections = [] } = gppConsent; if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + const fastBidVersion = config.getConfig('criteo.fastBidVersion'); + if (canFastBid(fastBidVersion)) { + return []; + } + + const refererInfo = getRefererInfo(); + const origin = 'criteoPrebidAdapter'; + const queryParams = []; queryParams.push(`origin=${origin}`); queryParams.push(`topUrl=${refererInfo.domain}`); @@ -79,6 +81,12 @@ export const spec = { if (uspConsent) { queryParams.push(`us_privacy=${uspConsent}`); } + queryParams.push(`gpp=${gppString}`); + if (Array.isArray(applicableSections)) { + for (const applicableSection of applicableSections) { + queryParams.push(`gpp_sid=${applicableSection}`); + } + } const requestId = Math.random().toString(); @@ -126,6 +134,32 @@ export const spec = { type: 'iframe', url: `https://gum.criteo.com/syncframe?${queryParams.join('&')}#${jsonHashSerialized}` }]; + } else if (syncOptions.pixelEnabled && hasPurpose1Consent(gdprConsent)) { + const queryParams = []; + queryParams.push(`profile=207`); + if (gdprConsent) { + if (gdprConsent.gdprApplies === true) { + queryParams.push(`gdprapplies=true`); + } + if (gdprConsent.consentString) { + queryParams.push(`gdpr=${gdprConsent.consentString}`); + } + } + if (uspConsent) { + queryParams.push(`ccpa=${uspConsent}`); + } + queryParams.push(`gpp=${gppString}`); + if (Array.isArray(applicableSections)) { + for (const applicableSection of applicableSections) { + queryParams.push(`gpp_sid=${applicableSection}`); + } + } + // gpp + // gpp_sid + return [{ + type: 'image', + url: `https://ssp-sync.criteo.com/user-sync/redirect?${queryParams.join('&')}` + }]; } return []; }, diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 083ae368afb..c460b451193 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -38,7 +38,66 @@ describe('The Criteo bidding adapter', function () { ajaxStub.restore(); }); - describe('getUserSyncs', function () { + describe('getUserSyncs in pixel mode', function () { + const syncOptions = { + pixelEnabled: true + }; + + it('should not trigger sync if publisher did not enable pixel based syncs', function () { + const userSyncs = spec.getUserSyncs({ + iframeEnabled: false + }, undefined, undefined, undefined); + + expect(userSyncs).to.eql([]); + }); + + it('should not trigger sync if purpose one is not granted', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'ABC', + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + }; + const userSyncs = spec.getUserSyncs(syncOptions, undefined, gdprConsent, undefined); + + expect(userSyncs).to.eql([]); + }); + + it('should trigger sync with consent data', function () { + const usPrivacy = 'usp_string'; + + const gppConsent = { + gppString: 'gpp_string', + applicableSections: [ 1, 2 ] + }; + + const gdprConsent = { + gdprApplies: true, + consentString: 'ABC', + vendorData: { + purpose: { + consents: { + 1: true + } + } + } + }; + + const userSyncs = spec.getUserSyncs(syncOptions, undefined, gdprConsent, usPrivacy, gppConsent); + + expect(userSyncs).to.eql([{ + type: 'image', + url: 'https://ssp-sync.criteo.com/user-sync/redirect?profile=207&gdprapplies=true&gdpr=ABC&ccpa=usp_string&gpp=gpp_string&gpp_sid=1&gpp_sid=2' + }]); + }); + }); + + describe('getUserSyncs in iframe mode', function () { const syncOptionsIframeEnabled = { iframeEnabled: true }; @@ -151,7 +210,7 @@ describe('The Criteo bidding adapter', function () { expect(userSyncs).to.eql([{ type: 'iframe', - url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com#${JSON.stringify(expectedHashWithCookieData, Object.keys(expectedHashWithCookieData).sort()).replace(/"/g, '%22')}` + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&gpp=#${JSON.stringify(expectedHashWithCookieData, Object.keys(expectedHashWithCookieData).sort()).replace(/"/g, '%22')}` }]); }); @@ -175,7 +234,7 @@ describe('The Criteo bidding adapter', function () { expect(userSyncs).to.eql([{ type: 'iframe', - url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com#${JSON.stringify(expectedHashWithLocalStorageData, Object.keys(expectedHashWithLocalStorageData).sort()).replace(/"/g, '%22')}` + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&gpp=#${JSON.stringify(expectedHashWithLocalStorageData, Object.keys(expectedHashWithLocalStorageData).sort()).replace(/"/g, '%22')}` }]); }); @@ -195,7 +254,7 @@ describe('The Criteo bidding adapter', function () { expect(userSyncs).to.eql([{ type: 'iframe', - url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&gdpr=1&gdpr_consent=ABC#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&gdpr=1&gdpr_consent=ABC&gpp=#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` }]); }); @@ -204,7 +263,7 @@ describe('The Criteo bidding adapter', function () { expect(userSyncs).to.eql([{ type: 'iframe', - url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&us_privacy=ABC#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` + url: `https://gum.criteo.com/syncframe?origin=criteoPrebidAdapter&topUrl=www.abc.com&us_privacy=ABC&gpp=#${JSON.stringify(expectedHash).replace(/"/g, '%22')}` }]); }); From bf4967f3fe3354d3df3d24bd30a189d51f639988 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 17 Apr 2024 14:09:12 -0700 Subject: [PATCH 644/760] Core: fix bidderRequestsCount (#11295) --- src/adUnits.js | 30 ++--- src/adapterManager.js | 20 +++- src/prebid.js | 4 - test/spec/unit/adUnits_spec.js | 2 +- test/spec/unit/core/adapterManager_spec.js | 128 ++++++++++++++------- 5 files changed, 109 insertions(+), 75 deletions(-) diff --git a/src/adUnits.js b/src/adUnits.js index cdac649c5b8..b0c728fd945 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -1,6 +1,9 @@ import { deepAccess } from './utils.js'; let adUnits = {}; +export function reset() { + adUnits = {} +} function ensureAdUnit(adunit, bidderCode) { let adUnit = adUnits[adunit] = adUnits[adunit] || { bidders: {} }; @@ -21,7 +24,7 @@ function incrementAdUnitCount(adunit, counter, bidderCode) { * @param {string} adunit id * @returns {number} current adunit count */ -function incrementRequestsCounter(adunit) { +export function incrementRequestsCounter(adunit) { return incrementAdUnitCount(adunit, 'requestsCounter'); } @@ -31,7 +34,7 @@ function incrementRequestsCounter(adunit) { * @param {string} bidderCode code * @returns {number} current adunit bidder requests count */ -function incrementBidderRequestsCounter(adunit, bidderCode) { +export function incrementBidderRequestsCounter(adunit, bidderCode) { return incrementAdUnitCount(adunit, 'requestsCounter', bidderCode); } @@ -41,7 +44,7 @@ function incrementBidderRequestsCounter(adunit, bidderCode) { * @param {string} bidderCode code * @returns {number} current adunit bidder requests count */ -function incrementBidderWinsCounter(adunit, bidderCode) { +export function incrementBidderWinsCounter(adunit, bidderCode) { return incrementAdUnitCount(adunit, 'winsCounter', bidderCode); } @@ -50,7 +53,7 @@ function incrementBidderWinsCounter(adunit, bidderCode) { * @param {string} adunit id * @returns {number} current adunit count */ -function getRequestsCounter(adunit) { +export function getRequestsCounter(adunit) { return deepAccess(adUnits, `${adunit}.requestsCounter`) || 0; } @@ -60,7 +63,7 @@ function getRequestsCounter(adunit) { * @param {string} bidder code * @returns {number} current adunit bidder requests count */ -function getBidderRequestsCounter(adunit, bidder) { +export function getBidderRequestsCounter(adunit, bidder) { return deepAccess(adUnits, `${adunit}.bidders.${bidder}.requestsCounter`) || 0; } @@ -70,21 +73,6 @@ function getBidderRequestsCounter(adunit, bidder) { * @param {string} bidder code * @returns {number} current adunit bidder requests count */ -function getBidderWinsCounter(adunit, bidder) { +export function getBidderWinsCounter(adunit, bidder) { return deepAccess(adUnits, `${adunit}.bidders.${bidder}.winsCounter`) || 0; } - -/** - * A module which counts how many times an adunit was called - * @module adunitCounter - */ -let adunitCounter = { - incrementRequestsCounter, - incrementBidderRequestsCounter, - incrementBidderWinsCounter, - getRequestsCounter, - getBidderRequestsCounter, - getBidderWinsCounter -} - -export { adunitCounter }; diff --git a/src/adapterManager.js b/src/adapterManager.js index cbabfa4380e..557f4c1eee4 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -27,7 +27,12 @@ import {ajaxBuilder} from './ajax.js'; import {config, RANDOM} from './config.js'; import {hook} from './hook.js'; import {find, includes} from './polyfill.js'; -import {adunitCounter} from './adUnits.js'; +import { + getBidderRequestsCounter, + getBidderWinsCounter, + getRequestsCounter, incrementBidderRequestsCounter, + incrementBidderWinsCounter, incrementRequestsCounter +} from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; @@ -112,6 +117,10 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} ); } + if (src === 'client') { + incrementBidderRequestsCounter(adUnit.code, bidderCode); + } + bids.push(Object.assign({}, bid, { adUnitCode: adUnit.code, transactionId: adUnit.transactionId, @@ -122,9 +131,9 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} auctionId, src, metrics, - bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), - bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), - bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder), + bidRequestsCount: getRequestsCounter(adUnit.code), + bidderRequestsCount: getBidderRequestsCounter(adUnit.code, bid.bidder), + bidderWinsCount: getBidderWinsCounter(adUnit.code, bid.bidder), })); return bids; }, []) @@ -253,6 +262,7 @@ adapterManager.makeBidRequests = hook('sync', function (adUnits, auctionStart, a } // filter out bidders that cannot participate in the auction au.bids = au.bids.filter((bid) => !bid.bidder || dep.isAllowed(ACTIVITY_FETCH_BIDS, activityParams(MODULE_TYPE_BIDDER, bid.bidder))) + incrementRequestsCounter(au.code); }); adUnits = setupAdUnitMediaTypes(adUnits, labels); @@ -655,7 +665,7 @@ adapterManager.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeou adapterManager.callBidWonBidder = function(bidder, bid, adUnits) { // Adding user configured params to bidWon event data bid.params = getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder); - adunitCounter.incrementBidderWinsCounter(bid.adUnitCode, bid.bidder); + incrementBidderWinsCounter(bid.adUnitCode, bid.bidder); tryCallBidderMethod(bidder, 'onBidWon', bid); }; diff --git a/src/prebid.js b/src/prebid.js index e96c0b36c9f..7f2d8798e2a 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -30,7 +30,6 @@ import {isBidUsable, targeting} from './targeting.js'; import {hook, wrapHook} from './hook.js'; import {loadSession} from './debugging.js'; import {includes} from './polyfill.js'; -import {adunitCounter} from './adUnits.js'; import {createBid} from './bidfactory.js'; import {storageCallbacks} from './storageManager.js'; import {default as adapterManager, getS2SBidderSet} from './adapterManager.js'; @@ -593,11 +592,8 @@ export const startAuction = hook('async', function ({ bidsBackHandler, timeout: // drop the bidder from the ad unit if it's not compatible logWarn(unsupportedBidderMessage(adUnit, bidder)); adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); - } else { - adunitCounter.incrementBidderRequestsCounter(adUnit.code, bidder); } }); - adunitCounter.incrementRequestsCounter(adUnit.code); }); if (!adUnits || adUnits.length === 0) { logMessage('No adUnits configured. No bids requested.'); diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index a8443d36522..1b26f7d5601 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { adunitCounter } from 'src/adUnits.js'; +import * as adunitCounter from 'src/adUnits.js'; describe('Adunit Counter', function () { const ADUNIT_ID_1 = 'test1'; diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 590d4829b49..9132aaeb903 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -24,6 +24,8 @@ import {auctionManager} from '../../../../src/auctionManager.js'; import {GDPR_GVLIDS} from '../../../../src/consentHandler.js'; import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER} from '../../../../src/activities/modules.js'; import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from '../../../../src/activities/activities.js'; +import {reset as resetAdUnitCounters} from '../../../../src/adUnits.js'; +import {deepClone} from 'src/utils.js'; var events = require('../../../../src/events'); const CONFIG = { @@ -1675,12 +1677,24 @@ describe('adapterManager tests', function () { describe('makeBidRequests', function () { let adUnits; beforeEach(function () { + resetAdUnitCounters(); adUnits = utils.deepClone(getAdUnits()).map(adUnit => { adUnit.bids = adUnit.bids.filter(bid => includes(['appnexus', 'rubicon'], bid.bidder)); return adUnit; }) }); + function makeBidRequests(au = adUnits) { + return adapterManager.makeBidRequests( + au, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() { + }, + [] + ); + } + if (FEATURES.NATIVE) { it('should add nativeParams to adUnits after BEFORE_REQUEST_BIDS', () => { function beforeReqBids(adUnits) { @@ -1692,14 +1706,7 @@ describe('adapterManager tests', function () { } events.on(EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); + makeBidRequests(); events.off(EVENTS.BEFORE_REQUEST_BIDS, beforeReqBids); expect(adUnits.map((u) => u.nativeParams).some(i => i == null)).to.be.false; }); @@ -1708,13 +1715,7 @@ describe('adapterManager tests', function () { it('should make separate bidder request objects for each bidder', () => { adUnits = [utils.deepClone(getAdUnits()[0])]; - let bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); + let bidRequests = makeBidRequests(); let sizes1 = bidRequests[1].bids[0].sizes; let sizes2 = bidRequests[0].bids[0].sizes; @@ -1725,6 +1726,70 @@ describe('adapterManager tests', function () { expect(sizes1).not.to.deep.equal(sizes2); }); + it('should set and increment bidRequestsCounter', () => { + const [au1, au2] = adUnits; + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.bidRequestsCount).to.eql(1); + }) + makeBidRequests([au1]); + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.bidRequestsCount).to.eql(bid.adUnitCode === au1.code ? 3 : 2); + }); + }) + + describe('bidderRequestsCounter', () => { + it('should be set and incremented', () => { + const [au1, au2] = adUnits; + makeBidRequests([au1, au2]).flatMap(br => br.bids).forEach(bid => { + expect(bid.bidderRequestsCount).to.eql(1); + }); + const au3 = { + ...au2, + bids: [ + au2.bids[0] + ] + } + makeBidRequests([au3]); + const counts = Object.fromEntries( + makeBidRequests([au1, au2]) + .map(br => [br.bidderCode, Object.fromEntries(br.bids.map(bid => [bid.adUnitCode, bid.bidderRequestsCount]))]) + ); + expect(counts).to.eql({ + rubicon: { + [au2.code]: 2 + }, + appnexus: { + [au1.code]: 2, + [au2.code]: 3 + }, + }); + }); + + it('should NOT be incremented for s2s bids', () => { + config.setConfig({ + s2sConfig: { + enabled: true, + adapter: 'rubicon', + bidders: ['appnexus'] + } + }); + function expectBidderCounts(bidders) { + makeBidRequests().forEach(br => { + br.bids.forEach(bid => expect(bid.bidderRequestsCount).to.exist.and.eql(bidders[br.bidderCode])); + }) + } + expectBidderCounts({ + appnexus: 0, + rubicon: 1 + }); + config.resetConfig(); + expectBidderCounts({ + appnexus: 1, + rubicon: 2 + }) + }) + }); + describe('and activity controls', () => { let redactOrtb2; let redactBidRequest; @@ -1757,13 +1822,7 @@ describe('adapterManager tests', function () { componentType === MODULE_TYPE_BIDDER && allowed.includes(componentName); }); - let reqs = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); + let reqs = makeBidRequests(); const bidders = Array.from(new Set(reqs.flatMap(br => br.bids).map(bid => bid.bidder)).keys()); expect(bidders).to.have.members(allowed); }); @@ -1773,13 +1832,7 @@ describe('adapterManager tests', function () { adUnits = [ {code: 'one', bids: [{bidder: 'mockBidder1'}]} ]; - let reqs = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); + let reqs = makeBidRequests(); sinon.assert.calledWith(redactBidRequest, reqs[0].bids[0]); sinon.assert.calledWith(redactOrtb2, reqs[0].ortb2); }) @@ -1812,13 +1865,7 @@ describe('adapterManager tests', function () { {code: 'two', bids: [{module: 'pbsBidAdapter', params: {configName: 'mock1'}}, {module: 'pbsBidAdapter', params: {configName: 'mock2'}}]} ] dep.isAllowed.callsFake(({componentType}) => componentType !== 'bidder'); - let bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() {}, - [] - ); + let bidRequests = makeBidRequests(); expect(new Set(bidRequests.map(br => br.uniquePbsTid)).size).to.equal(3); }); @@ -1835,14 +1882,7 @@ describe('adapterManager tests', function () { } ]; dep.isAllowed.callsFake((_, {configName, componentName}) => !(componentName === 'pbsBidAdapter' && configName === 'mock1')); - let bidRequests = adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ); + let bidRequests = makeBidRequests(); expect(new Set(bidRequests.map(br => br.uniquePbsTid)).size).to.eql(2) }); }); From e576447077fd5a071c1d2bf01bd497bacc964582 Mon Sep 17 00:00:00 2001 From: PGAMSSP <142323401+PGAMSSP@users.noreply.github.com> Date: Thu, 18 Apr 2024 00:36:13 +0300 Subject: [PATCH 645/760] PgamSSP Bid Adapter: gpp support (#11354) * new adapter PGAMSSP * upd * support UID 2.0 * del obj * Add id5id * add support gpp * fix spaces --------- Co-authored-by: Chris Huie --- modules/pgamsspBidAdapter.js | 29 +++++++++-- test/spec/modules/pgamsspBidAdapter_spec.js | 54 +++++++++++++++++++-- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index f3062fa4ff0..fdc6bcf302f 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -171,11 +171,27 @@ export const spec = { page, placements, coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, tmax: bidderRequest.timeout }; + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + const len = validBidRequests.length; for (let i = 0; i < len; i++) { const bid = validBidRequests[i]; @@ -203,9 +219,10 @@ export const spec = { return response; }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; + if (gdprConsent && gdprConsent.consentString) { if (typeof gdprConsent.gdprApplies === 'boolean') { syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; @@ -213,10 +230,16 @@ export const spec = { syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; } } + if (uspConsent && uspConsent.consentString) { syncUrl += `&ccpa_consent=${uspConsent.consentString}`; } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + const coppa = config.getConfig('coppa') ? 1 : 0; syncUrl += `&coppa=${coppa}`; diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 0766219eda8..281a012fb7a 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -73,7 +73,9 @@ describe('PGAMBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + }, refererInfo: { referer: 'https://test.com' }, @@ -129,7 +131,7 @@ describe('PGAMBidAdapter', function () { expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -171,8 +173,8 @@ describe('PGAMBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -195,6 +197,38 @@ describe('PGAMBidAdapter', function () { }); }); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -396,5 +430,17 @@ describe('PGAMBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.pgammedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://cs.pgammedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); From 75602dcab220730576317919b49ff06b95864e99 Mon Sep 17 00:00:00 2001 From: Denis Logachov Date: Thu, 18 Apr 2024 17:25:40 +0300 Subject: [PATCH 646/760] Adkernel Bid Adapter: bid.mtype support (#11355) --- modules/adkernelBidAdapter.js | 12 +++++++++--- test/spec/modules/adkernelBidAdapter_spec.js | 13 +++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index ae02a8967b1..0c353e4332a 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -51,6 +51,12 @@ const MULTI_FORMAT_SUFFIX_BANNER = 'b' + MULTI_FORMAT_SUFFIX; const MULTI_FORMAT_SUFFIX_VIDEO = 'v' + MULTI_FORMAT_SUFFIX; const MULTI_FORMAT_SUFFIX_NATIVE = 'n' + MULTI_FORMAT_SUFFIX; +const MEDIA_TYPES = { + BANNER: 1, + VIDEO: 2, + NATIVE: 4 +}; + /** * Adapter for requesting bids from AdKernel white-label display platform */ @@ -159,17 +165,17 @@ export const spec = { if (prBid.requestId.endsWith(MULTI_FORMAT_SUFFIX)) { prBid.requestId = stripMultiformatSuffix(prBid.requestId); } - if ('banner' in imp) { + if (rtbBid.mtype === MEDIA_TYPES.BANNER) { prBid.mediaType = BANNER; prBid.width = rtbBid.w; prBid.height = rtbBid.h; prBid.ad = formatAdMarkup(rtbBid); - } else if ('video' in imp) { + } else if (rtbBid.mtype === MEDIA_TYPES.VIDEO) { prBid.mediaType = VIDEO; prBid.vastUrl = rtbBid.nurl; prBid.width = imp.video.w; prBid.height = imp.video.h; - } else if ('native' in imp) { + } else if (rtbBid.mtype === MEDIA_TYPES.NATIVE) { prBid.mediaType = NATIVE; prBid.native = { ortb: buildNativeAd(rtbBid.adm) diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index cdfc9795b85..ceb5d029203 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -218,7 +218,8 @@ describe('Adkernel adapter', function () { adm: '', w: 300, h: 250, - dealid: 'deal' + dealid: 'deal', + mtype: 1 }] }], ext: { @@ -234,7 +235,8 @@ describe('Adkernel adapter', function () { price: 0.00145, adid: '158801', nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', - cid: '16855' + cid: '16855', + mtype: 2 }] }], }, usersyncOnlyResponse = { @@ -269,6 +271,7 @@ describe('Adkernel adapter', function () { cat: ['IAB1-4', 'IAB8-16', 'IAB25-5'], cid: '1', crid: '4', + mtype: 4, ext: { 'advertiser_id': 777, 'advertiser_name': 'advertiser', @@ -290,7 +293,8 @@ describe('Adkernel adapter', function () { adid: '158801', adm: '', nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_0&f=nurl', - cid: '16855' + cid: '16855', + mtype: 1 }, { id: 'sZSYq5zYMxo_1', impid: 'Bid_01v__mf', @@ -298,7 +302,8 @@ describe('Adkernel adapter', function () { price: 0.25, adid: '158801', nurl: 'https://rtb.com/win?i=sZSYq5zYMxo_1&f=nurl', - cid: '16855' + cid: '16855', + mtype: 2 }] }], bidid: 'pTuOlf5KHUo', From 08ac74f3ca062fb3a34c42e073d39dadfff94d46 Mon Sep 17 00:00:00 2001 From: Kevin Siow Date: Thu, 18 Apr 2024 18:13:10 +0200 Subject: [PATCH 647/760] Dailymotion Bid Adaptor: initial release (#10970) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Dailymotion Bid Adaptor: initial release * .md file lint issue resolved * Dailymotion Bid Adaptor: build bidder request based on param with fallbacks * Dailymotion Bid Adaptor: support video metadata * Dailymotion Bid Adaptor: add support for sending adUnitCode * Dailymotion Bid Adaptor: add support for sending startDelay * feat(LEO-528): Allow multiple IAB categories in video metadata The same way as we can have an array of IAB categories level 1 in the ORTB request, this PR introduces an array for the IAB categories level 2. To be forward compatible with level [2.2](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.2.tsv) and [3.0](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%203.0.tsv) specifications, the category IDs should be sent as strings. * Dailymotion bid adapter: Clarify the video metadata to provide in each player context * Dailymotion bid adapter: Move API key to bid params * Dailymotion bid adapter: Verify API key is string Co-authored-by: Rumesh * Dailymotion bid adapter: Move API key to bid params (fix tests) * Dailymotion Bid Adaptor: add gpp support and get coppa from request * Dailymotion Bid Adaptor: fix lint error * Dailymotion Bid Adaptor: add iabcat1 and fallback to ortb2 for iabcat2 * Dailymotion Bid Adaptor: get iabcats from ortb2.site.content.data --------- Co-authored-by: Kevin Siow Co-authored-by: Aditi Chaudhary Co-authored-by: Sébastien Millet Co-authored-by: Rumesh --- modules/dailymotionBidAdapter.js | 139 +++++++ modules/dailymotionBidAdapter.md | 140 +++++++ .../modules/dailymotionBidAdapter_spec.js | 369 ++++++++++++++++++ 3 files changed, 648 insertions(+) create mode 100644 modules/dailymotionBidAdapter.js create mode 100644 modules/dailymotionBidAdapter.md create mode 100644 test/spec/modules/dailymotionBidAdapter_spec.js diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js new file mode 100644 index 00000000000..2be5edad78e --- /dev/null +++ b/modules/dailymotionBidAdapter.js @@ -0,0 +1,139 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { deepAccess } from '../src/utils.js'; + +/** + * Get video metadata from bid request + * + * @param {BidRequest} bidRequest A valid bid requests that should be sent to the Server. + * @return video metadata + */ +function getVideoMetadata(bidRequest, bidderRequest) { + const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); + const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + + const videoParams = { + ...videoAdUnit, + ...videoBidderParams, // Bidder Specific overrides + }; + + // Store as object keys to ensure uniqueness + const iabcat1 = {}; + const iabcat2 = {}; + + deepAccess(bidderRequest, 'ortb2.site.content.data', []).forEach((data) => { + if ([4, 5, 6, 7].includes(data?.ext?.segtax)) { + (Array.isArray(data.segment) ? data.segment : []).forEach((segment) => { + if (typeof segment.id === 'string') { + // See https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy + // Only take IAB cats of taxonomy V1 + if (data.ext.segtax === 4) iabcat1[segment.id] = 1; + // Only take IAB cats of taxonomy V2 or higher + if ([5, 6, 7].includes(data.ext.segtax)) iabcat2[segment.id] = 1; + } + }); + } + }); + + const videoMetadata = { + description: videoParams.description || '', + duration: videoParams.duration || 0, + iabcat1: Object.keys(iabcat1), + iabcat2: Array.isArray(videoParams.iabcat2) + ? videoParams.iabcat2 + : Object.keys(iabcat2), + id: videoParams.id || '', + lang: videoParams.lang || '', + private: videoParams.private || false, + tags: videoParams.tags || '', + title: videoParams.title || '', + topics: videoParams.topics || '', + xid: videoParams.xid || '', + }; + + return videoMetadata; +} + +export const spec = { + code: 'dailymotion', + gvlid: 573, + supportedMediaTypes: [VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * The only mandatory parameter for a bid to be valid is the API key. + * Other parameters are optional. + * + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function (bid) { + return typeof bid?.params?.apiKey === 'string' && bid.params.apiKey.length > 10; + }, + + /** + * Make a server request from the list of valid BidRequests (that already passed the isBidRequestValid call) + * + * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. + * @param {BidderRequest} bidderRequest + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: (validBidRequests = [], bidderRequest) => validBidRequests.map(bid => ({ + method: 'POST', + url: 'https://pb.dmxleo.com', + data: { + bidder_request: { + gdprConsent: { + apiVersion: bidderRequest?.gdprConsent?.apiVersion || 1, + consentString: bidderRequest?.gdprConsent?.consentString || '', + // Cast boolean in any case (eg: if value is int) to ensure type + gdprApplies: !!bidderRequest?.gdprConsent?.gdprApplies, + }, + refererInfo: { + page: bidderRequest?.refererInfo?.page || '', + }, + uspConsent: bidderRequest?.uspConsent || '', + gppConsent: { + gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || + deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), + applicableSections: deepAccess(bidderRequest, 'gppConsent.applicableSections') || + deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []), + }, + }, + config: { + api_key: bid.params.apiKey + }, + // Cast boolean in any case (value should be 0 or 1) to ensure type + coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), + request: { + adUnitCode: bid.adUnitCode || '', + auctionId: bid.auctionId || '', + bidId: bid.bidId || '', + mediaTypes: { + video: { + playerSize: bid.mediaTypes?.[VIDEO]?.playerSize || [], + api: bid.mediaTypes?.[VIDEO]?.api || [], + startDelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, + }, + }, + sizes: bid.sizes || [], + }, + video_metadata: getVideoMetadata(bid, bidderRequest), + }, + options: { + withCredentials: true, + crossOrigin: true, + }, + })), + + /** + * Map the response from the server into a list of bids. + * As dailymotion prebid server returns an entry with the correct Prebid structure, + * we directly include it as the only bid in the response. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: serverResponse => serverResponse?.body ? [serverResponse.body] : [], +}; + +registerBidder(spec); diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md new file mode 100644 index 00000000000..795273c9229 --- /dev/null +++ b/modules/dailymotionBidAdapter.md @@ -0,0 +1,140 @@ +# Overview + +``` +Module Name: Dailymotion Bid Adapter +Module Type: Bidder Adapter +Maintainer: ad-leo-engineering@dailymotion.com +``` + +# Description + +Dailymotion prebid adapter. + +# Configuration options + +Before calling this adapter, you need to set at least the API key in the bid parameters: + +```javascript +const adUnits = [ + { + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'fake_api_key' + } + }] + } +]; +``` + +`apiKey` is your publisher API key. For testing purpose, you can use "dailymotion-testing". + +# Test Parameters + +By setting the following bid parameters, you'll get a constant response to any request, to validate your adapter integration: + +```javascript +const adUnits = [ + { + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'dailymotion-testing' + } + }] + } +]; +``` + +Please note that failing to set these will result in the adapter not bidding at all. + +# Sample video AdUnit + +To allow better targeting, you should provide as much context about the video as possible. +There are two ways of doing this depending on if you're using Dailymotion player or a third party one. + +If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example: + +```javascript +const adUnits = [ + { + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'dailymotion-testing' + } + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + api: [2, 7], + context: 'instream', + playerSize: [ [1280, 720] ], + startDelay: 0, + xid: 'x123456' // Dailymotion infrastructure unique video ID + }, + } + } +]; +``` + +This will automatically fetch the most up-to-date information about the video. +If you provide any other metadata in addition to the `xid`, they will be ignored. + +If you are using a third party video player, you should not provide any `xid` and instead fill the following members: + +```javascript +const adUnits = [ + { + bids: [{ + bidder: 'dailymotion', + params: { + apiKey: 'dailymotion-testing', + video: { + description: 'overriden video description' + } + } + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + api: [2, 7], + context: 'instream', + description: 'this is a video description', + duration: 556, + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + playerSize: [ [1280, 720] ], + private: false, + startDelay: 0, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', + }, + } + } +]; +``` + +Each of the following video metadata fields can be added in mediaTypes.video or bids.params.video. +If a field exists in both places, it will be overridden by bids.params.video. + +* `description` - Video description +* `duration` - Video duration in seconds +* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) +* `id` - Video unique ID in host video infrastructure +* `lang` - ISO 639-1 code for main language used in the video +* `private` - True if video is not publicly available +* `tags` - Tags for the video, comma separated +* `title` - Video title +* `topics` - Main topics for the video, comma separated +* `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) + +# Integrating the adapter + +To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. + +You will then be able to use it within the bid parameters before making a bid request. + +This API key will ensure proper identification of your inventory and allow you to get real bids. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js new file mode 100644 index 00000000000..a102c26dca2 --- /dev/null +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -0,0 +1,369 @@ +import { config } from 'src/config.js'; +import { expect } from 'chai'; +import { spec } from 'modules/dailymotionBidAdapter.js'; +import { VIDEO } from '../../../src/mediaTypes'; + +describe('dailymotionBidAdapterTests', () => { + // Validate that isBidRequestValid only validates requests with apiKey + it('validates isBidRequestValid', () => { + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid())).to.be.false; + + const bidWithEmptyApi = { + params: { + apiKey: '', + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyApi))).to.be.false; + + const bidWithApi = { + params: { + apiKey: 'test_api_key', + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithApi))).to.be.true; + }); + + // Validate request generation + it('validates buildRequests', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + playerSize: [[1280, 720]], + api: [2, 7], + description: 'this is a test video', + duration: 300, + iabcat2: ['6', '17'], + lang: 'ENG', + startdelay: 0, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + duration: 556, + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', + xid: 'x123456', + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [{ id: 'IAB-1' }], + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + const { data: reqData } = request; + + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.bidder_request).to.eql({ + refererInfo: bidderRequestData.refererInfo, + uspConsent: bidderRequestData.uspConsent, + gdprConsent: bidderRequestData.gdprConsent, + gppConsent: bidderRequestData.gppConsent, + }); + expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); + expect(reqData.coppa).to.be.true; + expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); + expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); + expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); + expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); + expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.video_metadata).to.eql({ + description: bidRequestData[0].mediaTypes.video.description, + iabcat1: ['IAB-1'], // Taxonomy v2 or higher is excluded + iabcat2: bidRequestData[0].mediaTypes.video.iabcat2, + id: bidRequestData[0].params.video.id, + lang: bidRequestData[0].params.video.lang, + private: bidRequestData[0].params.video.private, + tags: bidRequestData[0].params.video.tags, + title: bidRequestData[0].params.video.title, + topics: bidRequestData[0].params.video.topics, + xid: bidRequestData[0].params.video.xid, + // Overriden through bidder params + duration: bidRequestData[0].params.video.duration, + }); + }); + + it('validates buildRequests with fallback values on ortb2 for gpp & iabcat', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + playerSize: [[1280, 720]], + api: [2, 7], + description: 'this is a test video', + duration: 300, + lang: 'ENG', + startdelay: 0, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + duration: 556, + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', + xid: 'x123456', + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + }, + ortb2: { + regs: { + gpp: 'xxx', + gpp_sid: [5], + coppa: 0, + }, + site: { + content: { + data: [ + undefined, // Undefined to check proper handling of edge cases + {}, // Empty object to check proper handling of edge cases + { ext: {} }, // Empty ext to check proper handling of edge cases + { + name: 'dataprovider.com', + ext: { segtax: 22 }, // Invalid segtax to check proper handling of edge cases + segment: [{ id: '400' }], + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: undefined, // Invalid segment to check proper handling of edge cases + }, + { + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: undefined, // Invalid segment to check proper handling of edge cases + }, + { + name: 'dataprovider.com', + ext: { segtax: 4 }, + segment: [{ id: 2222 }], // Invalid segment id to check proper handling of edge cases + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '6' }], + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '6' }], // Check that same cat won't be duplicated + }, + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '17' }, { id: '20' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + const { data: reqData } = request; + + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.bidder_request).to.eql({ + refererInfo: bidderRequestData.refererInfo, + uspConsent: bidderRequestData.uspConsent, + gdprConsent: bidderRequestData.gdprConsent, + gppConsent: { + gppString: bidderRequestData.ortb2.regs.gpp, + applicableSections: bidderRequestData.ortb2.regs.gpp_sid, + }, + }); + expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); + expect(reqData.coppa).to.be.false; + expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); + expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); + expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); + expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); + expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.video_metadata).to.eql({ + description: bidRequestData[0].mediaTypes.video.description, + // No iabcat1 here because nothing matches taxonomy + iabcat1: [], + iabcat2: ['6', '17', '20'], + id: bidRequestData[0].params.video.id, + lang: bidRequestData[0].params.video.lang, + private: bidRequestData[0].params.video.private, + tags: bidRequestData[0].params.video.tags, + title: bidRequestData[0].params.video.title, + topics: bidRequestData[0].params.video.topics, + xid: bidRequestData[0].params.video.xid, + // Overriden through bidder params + duration: bidRequestData[0].params.video.duration, + }); + }); + + it('validates buildRequests - with default values on empty bid & bidder request', () => { + const bidRequestDataWithApi = [{ + params: { + apiKey: 'test_api_key', + }, + }]; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestDataWithApi, {}), + ); + + const { data: reqData } = request; + + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.config.api_key).to.eql(bidRequestDataWithApi[0].params.apiKey); + expect(reqData.coppa).to.be.false; + + expect(reqData.bidder_request).to.eql({ + gdprConsent: { + apiVersion: 1, + consentString: '', + gdprApplies: false, + }, + refererInfo: { + page: '', + }, + uspConsent: '', + gppConsent: { + gppString: '', + applicableSections: [], + }, + }); + + expect(reqData.request).to.eql({ + auctionId: '', + bidId: '', + adUnitCode: '', + mediaTypes: { + video: { + playerSize: [], + startDelay: 0, + api: [], + }, + }, + sizes: [], + }); + + expect(reqData.video_metadata).to.eql({ + description: '', + duration: 0, + iabcat1: [], + iabcat2: [], + id: '', + lang: '', + private: false, + tags: '', + title: '', + topics: '', + xid: '', + }); + }); + + it('validates buildRequests - with empty/undefined validBidRequests', () => { + expect(spec.buildRequests([], {})).to.have.lengthOf(0); + + expect(spec.buildRequests(undefined, {})).to.have.lengthOf(0); + }); + + it('validates interpretResponse', () => { + const serverResponse = { + body: { + ad: 'https://fakecacheserver/cache?uuid=1234', + cacheId: '1234', + cpm: 20.0, + creativeId: '5678', + currency: 'USD', + dealId: 'deal123', + nurl: 'https://bid/nurl', + requestId: 'test_requestid', + vastUrl: 'https://fakecacheserver/cache?uuid=1234', + }, + }; + + const bids = spec.interpretResponse(serverResponse); + expect(bids).to.have.lengthOf(1); + + const [bid] = bids; + expect(bid).to.eql(serverResponse.body); + }); + + it('validates interpretResponse - with empty/undefined serverResponse', () => { + expect(spec.interpretResponse({})).to.have.lengthOf(0); + + expect(spec.interpretResponse(undefined)).to.have.lengthOf(0); + }); +}); From ed7e942c3c074be9febb16a62e59663a1c017862 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 18 Apr 2024 09:14:54 -0700 Subject: [PATCH 648/760] Core: update creative code and example (#11350) --- integrationExamples/gpt/x-domain/creative.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index bf2bd5f3fad..0d0f63cbf1b 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + + + diff --git a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html index 4f29ef1c406..f3f0c64fb1a 100644 --- a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html +++ b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html @@ -35,6 +35,13 @@ }, // Replace this object to test a new Adapter! bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html index 80ea81d09b6..d0b261043e4 100644 --- a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html +++ b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html @@ -20,6 +20,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html index 663765317b0..c40af32cac2 100644 --- a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html +++ b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html @@ -50,12 +50,17 @@ mediationLayerAdServer: "dfp", bidTimeout: 2000 }, - bidders: [ - { + bidders: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { name: "ix", siteId: "300" - } - ] + }] } } } diff --git a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html index 66eaff26090..75a72ba3501 100644 --- a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html +++ b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html @@ -19,6 +19,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/eventListeners.html b/integrationExamples/videoModule/jwplayer/eventListeners.html index 39acb086107..6f04f37264b 100644 --- a/integrationExamples/videoModule/jwplayer/eventListeners.html +++ b/integrationExamples/videoModule/jwplayer/eventListeners.html @@ -23,6 +23,13 @@ // Replace this object to test a new Adapter! bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/eventsUI.html b/integrationExamples/videoModule/jwplayer/eventsUI.html index 78d72a6153d..cfd1efe7624 100644 --- a/integrationExamples/videoModule/jwplayer/eventsUI.html +++ b/integrationExamples/videoModule/jwplayer/eventsUI.html @@ -22,6 +22,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html index 018c8eba8b2..1f4331785ea 100644 --- a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html +++ b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html @@ -19,6 +19,13 @@ divId: 'player', // required to indicate which player is being used to render this ad unit. }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/mediaMetadata.html b/integrationExamples/videoModule/jwplayer/mediaMetadata.html index 7581af571d3..63e62aa4b82 100644 --- a/integrationExamples/videoModule/jwplayer/mediaMetadata.html +++ b/integrationExamples/videoModule/jwplayer/mediaMetadata.html @@ -20,6 +20,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/jwplayer/playlist.html b/integrationExamples/videoModule/jwplayer/playlist.html index 89efaea3d5c..9e89f606f23 100644 --- a/integrationExamples/videoModule/jwplayer/playlist.html +++ b/integrationExamples/videoModule/jwplayer/playlist.html @@ -20,6 +20,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html index d6656bc0c93..35745ab281f 100644 --- a/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html +++ b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html @@ -35,6 +35,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/bidRequestScheduling.html b/integrationExamples/videoModule/videojs/bidRequestScheduling.html index eb10fda89a2..da6499ca4cc 100644 --- a/integrationExamples/videoModule/videojs/bidRequestScheduling.html +++ b/integrationExamples/videoModule/videojs/bidRequestScheduling.html @@ -37,6 +37,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html index ac8f4163e76..74217ecee2c 100644 --- a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html +++ b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html @@ -34,6 +34,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/eventListeners.html b/integrationExamples/videoModule/videojs/eventListeners.html index 1966f134e02..3fc2ef7137c 100644 --- a/integrationExamples/videoModule/videojs/eventListeners.html +++ b/integrationExamples/videoModule/videojs/eventListeners.html @@ -35,6 +35,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/eventsUI.html b/integrationExamples/videoModule/videojs/eventsUI.html index 9eba09f7a52..215b2de4d25 100644 --- a/integrationExamples/videoModule/videojs/eventsUI.html +++ b/integrationExamples/videoModule/videojs/eventsUI.html @@ -37,6 +37,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/gamAdServerMediation.html b/integrationExamples/videoModule/videojs/gamAdServerMediation.html index 6ffc1a67c03..d6603abbf8f 100644 --- a/integrationExamples/videoModule/videojs/gamAdServerMediation.html +++ b/integrationExamples/videoModule/videojs/gamAdServerMediation.html @@ -34,6 +34,13 @@ divId: 'player', // required to indicate which player is being used to render this ad unit. }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/mediaMetadata.html b/integrationExamples/videoModule/videojs/mediaMetadata.html index ede076fd814..084c597cddd 100644 --- a/integrationExamples/videoModule/videojs/mediaMetadata.html +++ b/integrationExamples/videoModule/videojs/mediaMetadata.html @@ -35,6 +35,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/integrationExamples/videoModule/videojs/playlist.html b/integrationExamples/videoModule/videojs/playlist.html index eb813f095f7..2563717df41 100644 --- a/integrationExamples/videoModule/videojs/playlist.html +++ b/integrationExamples/videoModule/videojs/playlist.html @@ -36,6 +36,13 @@ }, bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }, { bidder: 'ix', params: { siteId: '300', diff --git a/modules/jwplayerBidAdapter.js b/modules/jwplayerBidAdapter.js new file mode 100644 index 00000000000..151d08bf3a6 --- /dev/null +++ b/modules/jwplayerBidAdapter.js @@ -0,0 +1,412 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { isArray, isFn, deepAccess, deepSetValue, getDNT, logError, logWarn } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { hasPurpose1Consent } from '../src/utils/gpdr.js'; + +const BIDDER_CODE = 'jwplayer'; +const BASE_URL = 'https://vpb-server.jwplayer.com/'; +const AUCTION_URL = BASE_URL + 'openrtb2/auction'; +const USER_SYNC_URL = BASE_URL + 'setuid'; +const GVLID = 1046; +const SUPPORTED_AD_TYPES = [VIDEO]; + +const VIDEO_ORTB_PARAMS = [ + 'pos', + 'w', + 'h', + 'playbackend', + 'mimes', + 'minduration', + 'maxduration', + 'protocols', + 'startdelay', + 'placement', + 'plcmt', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity' +]; + +function getBidAdapter() { + function isBidRequestValid(bid) { + const params = bid && bid.params; + if (!params) { + return false; + } + + return !!params.placementId && !!params.publisherId && !!params.siteId; + } + + function buildRequests(bidRequests, bidderRequest) { + if (!bidRequests) { + return; + } + + if (!hasContentUrl(bidderRequest.ortb2)) { + logError(`${BIDDER_CODE}: cannot bid without a valid Content URL. Please populate ortb2.site.content.url`); + return; + } + + const warnings = getWarnings(bidderRequest); + warnings.forEach(warning => { + logWarn(`${BIDDER_CODE}: ${warning}`); + }); + + return bidRequests.map(bidRequest => { + const payload = buildRequest(bidRequest, bidderRequest); + + return { + method: 'POST', + url: AUCTION_URL, + data: payload + } + }); + } + + function interpretResponse(serverResponse) { + const outgoingBidResponses = []; + const serverResponseBody = serverResponse.body; + + logResponseWarnings(serverResponseBody); + + const seatBids = serverResponseBody && serverResponseBody.seatbid; + if (!isArray(seatBids)) { + return outgoingBidResponses; + } + + const cur = serverResponseBody.cur; + + seatBids.forEach(seatBid => { + seatBid.bid.forEach(bid => { + const bidResponse = { + requestId: serverResponseBody.id, + cpm: bid.price, + currency: cur, + width: bid.w, + height: bid.h, + ad: bid.adm, + vastXml: bid.adm, + ttl: bid.ttl || 3600, + netRevenue: false, + creativeId: bid.adid, + dealId: bid.dealid, + meta: { + advertiserDomains: bid.adomain, + mediaType: VIDEO, + primaryCatId: bid.cat, + } + }; + + outgoingBidResponses.push(bidResponse); + }); + }); + + return outgoingBidResponses; + } + + function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (!hasPurpose1Consent(gdprConsent)) { + return []; + } + + const userSyncs = []; + const consentQueryParams = getUserSyncConsentQueryParams(gdprConsent); + const url = `https://ib.adnxs.com/getuid?${USER_SYNC_URL}?bidder=jwplayer&uid=$UID&f=i` + consentQueryParams + + if (syncOptions.iframeEnabled) { + userSyncs.push({ + type: 'iframe', + url + }); + } + + if (syncOptions.pixelEnabled) { + userSyncs.push({ + type: 'image', + url + }); + } + + return userSyncs; + } + + return { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs + } + + function getUserSyncConsentQueryParams(gdprConsent) { + if (!gdprConsent) { + return ''; + } + + const consentString = gdprConsent.consentString; + if (!consentString) { + return ''; + } + + let gdpr = 0; + const gdprApplies = gdprConsent.gdprApplies; + if (typeof gdprApplies === 'boolean') { + gdpr = Number(gdprApplies) + } + + return `&gdpr=${gdpr}&gdpr_consent=${consentString}`; + } + + function buildRequest(bidRequest, bidderRequest) { + const openrtbRequest = { + id: bidRequest.bidId, + imp: getRequestImpressions(bidRequest, bidderRequest), + site: getRequestSite(bidRequest, bidderRequest), + device: getRequestDevice(bidderRequest.ortb2), + user: getRequestUser(bidderRequest.ortb2), + }; + + // GDPR Consent Params + if (bidderRequest.gdprConsent) { + deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); + } + + // CCPA + if (bidderRequest.uspConsent) { + deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (bidRequest.schain) { + deepSetValue(openrtbRequest, 'source.schain', bidRequest.schain); + } + + openrtbRequest.tmax = bidderRequest.timeout || 200; + + return JSON.stringify(openrtbRequest); + } + + function getRequestImpressions(bidRequest) { + const impressionObject = { + id: bidRequest.adUnitCode, + }; + + impressionObject.video = getImpressionVideo(bidRequest); + + const bidFloorData = getBidFloorData(bidRequest); + if (bidFloorData) { + impressionObject.bidfloor = bidFloorData.floor; + impressionObject.bidfloorcur = bidFloorData.currency; + } + + impressionObject.ext = getImpressionExtension(bidRequest); + + return [impressionObject]; + } + + function getImpressionVideo(bidRequest) { + const videoParams = deepAccess(bidRequest, 'mediaTypes.video', {}); + + const video = {}; + + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + video[param] = videoParams[param]; + } + }); + + setPlayerSize(video, videoParams); + + if (!videoParams.plcmt) { + logWarn(`${BIDDER_CODE}: Please set a value to mediaTypes.video.plcmt`); + } + + return video; + } + + function getImpressionExtension(bidRequest) { + return { + prebid: { + bidder: { + jwplayer: { + placementId: bidRequest.params.placementId + } + } + } + }; + } + + function setPlayerSize(videoImp, videoParams) { + if (videoImp.w !== undefined && videoImp.h !== undefined) { + return; + } + + const playerSize = getNormalizedPlayerSize(videoParams.playerSize); + if (!playerSize.length) { + logWarn(logWarn(`${BIDDER_CODE}: Video size has not been set. Please set values in video.h and video.w`)); + return; + } + + if (videoImp.w === undefined) { + videoImp.w = playerSize[0]; + } + + if (videoImp.h === undefined) { + videoImp.h = playerSize[1]; + } + } + + function getNormalizedPlayerSize(playerSize) { + if (!Array.isArray(playerSize)) { + return []; + } + + if (Array.isArray(playerSize[0])) { + playerSize = playerSize[0]; + } + + if (playerSize.length < 2) { + return []; + } + + return playerSize; + } + + function getBidFloorData(bidRequest) { + const { params } = bidRequest; + const currency = params.currency || 'USD'; + + let floorData; + if (isFn(bidRequest.getFloor)) { + const bidFloorRequest = { + currency: currency, + mediaType: VIDEO, + size: '*' + }; + floorData = bidRequest.getFloor(bidFloorRequest); + } else if (params.bidFloor) { + floorData = { floor: params.bidFloor, currency: currency }; + } + + return floorData; + } + + function getRequestSite(bidRequest, bidderRequest) { + const site = bidderRequest.ortb2.site || {}; + + site.domain = site.domain || config.publisherDomain || window.location.hostname; + site.page = site.page || config.pageUrl || window.location.href; + + const referer = bidderRequest.refererInfo && bidderRequest.refererInfo.referer; + if (!site.ref && referer) { + site.ref = referer; + } + + const jwplayerPublisherExtChain = 'publisher.ext.jwplayer.'; + + deepSetValue(site, jwplayerPublisherExtChain + 'publisherId', bidRequest.params.publisherId); + deepSetValue(site, jwplayerPublisherExtChain + 'siteId', bidRequest.params.siteId); + + return site; + } + + function getRequestDevice(ortb2) { + const device = Object.assign({ + h: screen.height, + w: screen.width, + ua: navigator.userAgent, + dnt: getDNT() ? 1 : 0, + js: 1 + }, ortb2.device || {}) + + const language = getLanguage(); + if (!device.language && language) { + device.language = language; + } + + return device; + } + + function getLanguage() { + const navigatorLanguage = navigator.language; + if (!navigatorLanguage) { + return; + } + + const languageCodeSegments = navigatorLanguage.split('-'); + if (!languageCodeSegments.length) { + return; + } + + return languageCodeSegments[0]; + } + + function getRequestUser(ortb2) { + const user = ortb2.user || {}; + if (config.getConfig('coppa') === true) { + user.coppa = true; + } + + return user; + } + + function hasContentUrl(ortb2) { + const site = ortb2.site; + const content = site && site.content; + return !!(content && content.url); + } + + function getWarnings(bidderRequest) { + const content = bidderRequest.ortb2.site.content; + const contentChain = 'ortb2.site.content.'; + const warnings = []; + if (!content.id) { + warnings.push(getMissingFieldMessage(contentChain + 'id')); + } + + if (!content.title) { + warnings.push(getMissingFieldMessage(contentChain + 'title')); + } + + if (!content.ext || !content.ext.description) { + warnings.push(getMissingFieldMessage(contentChain + 'ext.description')); + } + + return warnings; + } + + function getMissingFieldMessage(fieldName) { + return `Optional field ${fieldName} is not populated; we recommend populating for maximum performance.` + } + + function logResponseWarnings(serverResponseBody) { + const warningPayload = deepAccess(serverResponseBody, 'ext.warnings'); + if (!warningPayload) { + return; + } + + const warningCategories = Object.keys(warningPayload); + warningCategories.forEach(category => { + const warnings = warningPayload[category]; + if (!isArray(warnings)) { + return; + } + + warnings.forEach(warning => { + logWarn(`${BIDDER_CODE}: [Bid Response][Warning Code: ${warning.code}] ${warning.message}`); + }); + }); + } +} + +export const spec = getBidAdapter(); + +registerBidder(spec); diff --git a/modules/jwplayerBidAdapter.md b/modules/jwplayerBidAdapter.md new file mode 100644 index 00000000000..620f8657e50 --- /dev/null +++ b/modules/jwplayerBidAdapter.md @@ -0,0 +1,72 @@ +# Overview + +``` +Module Name: JWPlayer Bid Adapter +Module Type: Bidder Adapter +Maintainer: boost-engineering@jwplayer.com +``` + +# Description + +Connects to JWPlayer's demand sources. + +JWPlayer bid adapter supports Video (instream and outstream). + +# Sample Ad Unit + +```markdown +const adUnit = { + code: 'test-ad-unit', + mediaTypes: { + video: { + pos: 0, + w: 640, + h: 480, + mimes : ['video/x-ms-wmv', 'video/mp4'], + minduration : 0, + maxduration: 60, + protocols : [2,3,7,5,6,8], + startdelay: 0, + placement: 1, + plcmt: 1, + skip: 1, + skipafter: 10, + playbackmethod: [3], + api: [2], + linearity: 1 + } + }, + bids: [{ + bidder: 'jwplayer', + params: { + publisherId: 'test-publisher-id', + siteId: 'test-site-id', + placementId: 'test-placement-id' + } + }] +}; +``` + +# Sample ortb2 config + +```markdown +pbjs.setConfig({ + ortb2: { + site: { + publisher: { + id: 'test-publisher-id' + }, + content: { + id: 'test-media-id', + url: 'test.mp4', + title: 'title of my media', + ext: { + description: 'description of my media' + } + }, + domain: 'test-domain.com', + page: 'https://www.test-domain.com/test.html', + } + } +} +``` diff --git a/test/spec/modules/jwplayerBidAdapter_spec.js b/test/spec/modules/jwplayerBidAdapter_spec.js new file mode 100644 index 00000000000..e19790a9670 --- /dev/null +++ b/test/spec/modules/jwplayerBidAdapter_spec.js @@ -0,0 +1,412 @@ +import { expect, assert } from 'chai'; +import { spec } from 'modules/jwplayerBidAdapter.js'; + +describe('jwplayerBidAdapter', function() { + beforeEach(function() { + this.defaultBidderRequest = { + gdprConsent: { + consentString: 'testConsentString', + gdprApplies: true + }, + uspConsent: 'testCCPA', + refererInfo: { + referer: 'https://example.com' + }, + ortb2: { + site: { + domain: 'page.example.com', + page: 'https://examplepage.com', + content: { + url: 'media.mp4', + id: 'testMediaId', + title: 'testTile', + data: [{ + name: 'jwplayer.com', + segment: [{ + id: '00000000' + }, { + id: '88888888' + }, { + id: '80808080' + }], + ext: { + segtax: 502, + cids: ['testMediaId', 'externalTestId'], + } + }], + ext: { + description: 'testDescription' + } + } + } + }, + timeout: 1000 + } + }); + + it('should use jwplayer bidder code', function () { + expect(spec.code).to.equal('jwplayer'); + }); + + it('should use jwplayer GVLID code', function () { + expect(spec.gvlid).to.equal(1046); + }); + + it('should support VIDEO media type only', function () { + expect(spec.supportedMediaTypes).to.have.length(1); + expect(spec.supportedMediaTypes[0]).to.equal('video'); + }); + + describe('isBidRequestValid', function() { + it('should be invalid when bidRequest is undefined', function() { + assert(spec.isBidRequestValid() === false); + }); + + it('should be invalid when bidRequest is null', function() { + assert(spec.isBidRequestValid(null) === false); + }); + + it('should be invalid when the bidRequest has no params', function() { + assert(spec.isBidRequestValid({}) === false); + }); + + it('should be invalid when the bid request only includes a publisher ID', function() { + assert(spec.isBidRequestValid({params: {publisherId: 'foo'}}) === false); + }); + + it('should be invalid when the bid request only includes a placement ID', function() { + assert(spec.isBidRequestValid({params: {placementId: 'foo'}}) === false); + }); + + it('should be invalid when the bid request only includes a site ID', function() { + assert(spec.isBidRequestValid({params: {siteId: 'foo'}}) === false); + }); + + it('should be valid when the bid includes a placement ID, a publisher ID and a site ID', function() { + assert(spec.isBidRequestValid({params: {placementId: 'foo', publisherId: 'bar', siteId: 'siteId '}}) === true); + }); + }); + + describe('buildRequests', function() { + it('should return undefined when bidRequests is undefined', function () { + expect(spec.buildRequests()).to.be.undefined; + }); + + it('should return undefined when bidRequests is null', function () { + expect(spec.buildRequests(null)).to.be.undefined; + }); + + it('should return undefined when ortb site.content.url is absent', function () { + const request = spec.buildRequests({}, { + ortb2: { + site: { + content: { + url: null, + } + } + } + }); + + expect(request).to.be.undefined; + }); + + it('should build a valid request when bid request is complete', function() { + const incomingBidRequests = [ + { + bidder: 'jwplayer', + params: { + placementId: 'testPlacementId', + publisherId: 'testPublisherId', + siteId: 'testSiteId', + bidFloor: 10, + currency: 'EUR', + }, + mediaTypes: { + video: { + pos: 3, + w: 640, + h: 480, + context: 'instream', + mimes: [ + 'video/mp4', + 'application/javascript' + ], + protocols: [2, 3, 5, 6], + maxduration: 60, + minduration: 3, + startdelay: 0, + linearity: 1, + placement: 1, + plcmt: 1, + skip: 1, + skipafter: 4, + minbitrate: 500, + maxbitrate: 1000, + api: [2], + delivery: [2], + playbackmethod: [1], + playbackend: 2 + } + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'publisher.com', + sid: '00001', + hp: 1 + } + ] + }, + bidRequestsCount: 1, + adUnitCode: 'testAdUnitCode', + bidId: 'testBidId' + } + ]; + + const outgoingBidRequests = spec.buildRequests(incomingBidRequests, this.defaultBidderRequest); + + outgoingBidRequests.forEach(serverRequest => { + expect(serverRequest.url).to.equal('https://vpb-server.jwplayer.com/openrtb2/auction'); + expect(serverRequest.method).to.equal('POST'); + + const openrtbRequest = JSON.parse(serverRequest.data); + + expect(openrtbRequest.id).to.equal('testBidId'); + + expect(openrtbRequest.imp[0].id).to.equal('testAdUnitCode'); + expect(openrtbRequest.imp[0].video.w).to.equal(640); + expect(openrtbRequest.imp[0].video.h).to.equal(480); + expect(openrtbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'application/javascript']); + expect(openrtbRequest.imp[0].video.protocols).to.deep.equal([2, 3, 5, 6]); + expect(openrtbRequest.imp[0].video.api).to.deep.equal([2]); + expect(openrtbRequest.imp[0].video.startdelay).to.equal(0); + expect(openrtbRequest.imp[0].video.placement).to.equal(1); + expect(openrtbRequest.imp[0].video.plcmt).to.equal(1); + expect(openrtbRequest.imp[0].video.pos).to.equal(3); + expect(openrtbRequest.imp[0].video.minduration).to.equal(3); + expect(openrtbRequest.imp[0].video.maxduration).to.equal(60); + expect(openrtbRequest.imp[0].video.skip).to.equal(1); + expect(openrtbRequest.imp[0].video.skipafter).to.equal(4); + expect(openrtbRequest.imp[0].video.minbitrate).to.equal(500); + expect(openrtbRequest.imp[0].video.maxbitrate).to.equal(1000); + expect(openrtbRequest.imp[0].video.delivery).to.deep.equal([2]); + expect(openrtbRequest.imp[0].video.playbackmethod).to.deep.equal([1]); + expect(openrtbRequest.imp[0].video.playbackend).to.equal(2); + expect(openrtbRequest.imp[0].video.linearity).to.equal(1); + + expect(openrtbRequest.imp[0].bidfloor).to.equal(10); + expect(openrtbRequest.imp[0].bidfloorcur).to.equal('EUR'); + + expect(openrtbRequest.imp[0].ext.prebid.bidder.jwplayer.placementId).to.equal('testPlacementId'); + + expect(openrtbRequest.site.domain).to.equal('page.example.com'); + expect(openrtbRequest.site.page).to.equal('https://examplepage.com'); + expect(openrtbRequest.site.ref).to.equal('https://example.com'); + + expect(openrtbRequest.site.publisher.ext.jwplayer.publisherId).to.equal('testPublisherId'); + expect(openrtbRequest.site.publisher.ext.jwplayer.siteId).to.equal('testSiteId'); + + expect(openrtbRequest.site.content.url).to.equal('media.mp4'); + expect(openrtbRequest.site.content.id).to.equal('testMediaId'); + expect(openrtbRequest.site.content.title).to.equal('testTile'); + expect(openrtbRequest.site.content.ext.description).to.equal('testDescription'); + expect(openrtbRequest.site.content.data.length).to.equal(1); + const datum = openrtbRequest.site.content.data[0]; + expect(datum.name).to.equal('jwplayer.com'); + expect(datum.segment).to.deep.equal([{ + id: '00000000' + }, { + id: '88888888' + }, { + id: '80808080' + }]); + expect(datum.ext.segtax).to.equal(502); + expect(datum.ext.cids).to.deep.equal(['testMediaId', 'externalTestId']); + + expect(openrtbRequest.device.ua).to.equal(navigator.userAgent); + expect(openrtbRequest.device.w).to.not.be.undefined; + expect(openrtbRequest.device.h).to.not.be.undefined; + expect(openrtbRequest.device.dnt).to.not.be.undefined; + expect(openrtbRequest.device.js).to.equal(1); + expect(openrtbRequest.device.language).to.not.be.undefined; + + expect(openrtbRequest.user.ext.consent).to.equal('testConsentString'); + + expect(openrtbRequest.regs.ext.gdpr).to.equal(1); + expect(openrtbRequest.regs.ext.us_privacy).to.equal('testCCPA'); + + expect(openrtbRequest.source.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'publisher.com', + sid: '00001', + hp: 1 + } + ] + }); + + expect(openrtbRequest.tmax).to.equal(1000); + }); + }); + }); + + describe('interpretResponse', function() { + const serverResponse = { + body: { + id: 'test-request-id', + cur: 'USD', + seatbid: [{ + bid: [{ + id: 'testId', + impid: 'test-imp-id', + price: 5.000, + adid: 'test-creative-id', + adm: 'test-ad-xml', + adomain: ['prebid.com'], + cat: ['test-iab-category'], + w: 200, + h: 150, + dealid: 'test-deal-id' + }], + seat: 1000 + }] + } + }; + + const bidResponses = spec.interpretResponse(serverResponse); + + expect(bidResponses).to.have.length(1); + const bid = bidResponses[0]; + expect(bid.requestId).to.equal('test-request-id'); + expect(bid.cpm).to.equal(5); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(200); + expect(bid.height).to.equal(150); + expect(bid.creativeId).to.equal('test-creative-id'); + expect(bid.vastXml).to.equal('test-ad-xml'); + expect(bid.netRevenue).to.equal(false); + expect(bid.ttl).to.equal(3600); + expect(bid.ad).to.equal('test-ad-xml'); + expect(bid.dealId).to.equal('test-deal-id'); + + expect(bid.meta).to.not.be.undefined; + + expect(bid.meta.advertiserDomains).to.have.length(1); + expect(bid.meta.advertiserDomains[0]).to.equal('prebid.com'); + + expect(bid.meta.mediaType).to.equal('video'); + + expect(bid.meta.primaryCatId).to.have.length(1); + expect(bid.meta.primaryCatId[0]).to.equal('test-iab-category'); + }); + + describe('getUserSyncs', function() { + const consentString = 'test_consent_string'; + const baseGdprConsent = { + gdprApplies: true, + vendorData: { + purpose: { + consents: { + 1: true + } + } + } + }; + + const expectedBaseUrl = 'https://ib.adnxs.com/getuid?https://vpb-server.jwplayer.com/setuid?bidder=jwplayer&uid=$UID&f=i'; + + it('should return empty when Purpose 1 consent is not granted', function() { + expect(spec.getUserSyncs({}, {})).to.be.empty; + expect(spec.getUserSyncs({}, {}, {})).to.be.empty; + expect(spec.getUserSyncs({}, {}, { gdprApplies: false })).to.be.empty; + expect(spec.getUserSyncs({}, {}, { + gdprApplies: true, + vendorData: { + purpose: { + consents: { + 1: false + } + } + } + })).to.be.empty; + }); + + it('should return iframe when enabled', function () { + const userSyncs = spec.getUserSyncs({ iframeEnabled: true }, {}, baseGdprConsent); + expect(userSyncs.length).to.equal(1); + const sync = userSyncs[0]; + expect(sync.type).to.equal('iframe'); + expect(sync.url).to.equal(expectedBaseUrl); + }); + + it('should return image when enabled', function () { + const userSyncs = spec.getUserSyncs({ pixelEnabled: true }, {}, baseGdprConsent); + expect(userSyncs.length).to.equal(1); + const sync = userSyncs[0]; + expect(sync.type).to.equal('image'); + expect(sync.url).to.equal(expectedBaseUrl); + }); + + it('should return both iframe and image when enabled', function () { + const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, baseGdprConsent); + expect(userSyncs.length).to.equal(2); + + const iframeSync = userSyncs[0]; + expect(iframeSync.type).to.equal('iframe'); + expect(iframeSync.url).to.equal(expectedBaseUrl); + + const imageSync = userSyncs[1]; + expect(imageSync.type).to.equal('image'); + expect(imageSync.url).to.equal(expectedBaseUrl); + }); + + it('should include gdpr consent query params in sync redirect url', function () { + const expectedUrl = expectedBaseUrl + '&gdpr=1&gdpr_consent=' + consentString; + const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString }); + const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent); + expect(userSyncs.length).to.equal(2); + + const iframeSync = userSyncs[0]; + expect(iframeSync.type).to.equal('iframe'); + expect(iframeSync.url).to.equal(expectedUrl); + + const imageSync = userSyncs[1]; + expect(imageSync.type).to.equal('image'); + expect(imageSync.url).to.equal(expectedUrl); + }); + + it('should include gdpr 0 in consent query params when gdprApplies is false', function () { + const expectedUrl = expectedBaseUrl + '&gdpr=0&gdpr_consent=' + consentString; + const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString, gdprApplies: false }); + const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent); + expect(userSyncs.length).to.equal(2); + + const iframeSync = userSyncs[0]; + expect(iframeSync.type).to.equal('iframe'); + expect(iframeSync.url).to.equal(expectedUrl); + + const imageSync = userSyncs[1]; + expect(imageSync.type).to.equal('image'); + expect(imageSync.url).to.equal(expectedUrl); + }); + + it('should include gdpr 0 in consent query params when gdprApplies is not a bool', function () { + const expectedUrl = expectedBaseUrl + '&gdpr=0&gdpr_consent=' + consentString; + const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString, gdprApplies: 1 }); + const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent); + expect(userSyncs.length).to.equal(2); + + const iframeSync = userSyncs[0]; + expect(iframeSync.type).to.equal('iframe'); + expect(iframeSync.url).to.equal(expectedUrl); + + const imageSync = userSyncs[1]; + expect(imageSync.type).to.equal('image'); + expect(imageSync.url).to.equal(expectedUrl); + }); + }); +}); From 51bcf81bc6212de26290168a789c6db16d53af90 Mon Sep 17 00:00:00 2001 From: kalidas-alkimi <92875788+kalidas-alkimi@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:54:20 +0100 Subject: [PATCH 665/760] Alkimi Bid Adapter : add handling for user-sync URL and pass custom object with bid request (#11326) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes * change maintainer info * Updated the ad unit params * features support added * transfer adUnitCode * transfer adUnitCode: test * AlkimiBidAdapter getFloor() using * ALK-504 Multi size ad slot support * ALK-504 Multi size ad slot support * Support new OpenRTB parameters * Support new oRTB2 parameters * remove pos parameter * Add gvl_id into Alkimi adapter * Insert keywords into bid-request param * Resolve AUCTION_PRICE macro on prebid-server for VAST ads * Added support for full page auction * Added custom user object * userParams in request object * Handling user-sync url, store user id and passing custom params * Renamed the full_page_auction to fpa * Updated the review comment --------- Co-authored-by: Alexander <32703851+pro-nsk@users.noreply.github.com> Co-authored-by: Alexander Bogdanov Co-authored-by: Alexander Bogdanov Co-authored-by: motors Co-authored-by: mihanikw2g <92710748+mihanikw2g@users.noreply.github.com> Co-authored-by: Nikulin Mikhail Co-authored-by: mik --- modules/alkimiBidAdapter.js | 56 ++++++++++++++++++---- test/spec/modules/alkimiBidAdapter_spec.js | 16 +++++-- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index d4e7cab8ed1..3bd995cc112 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -1,12 +1,15 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {deepAccess, deepClone, getDNT, generateUUID, replaceAuctionPrice} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; import {VIDEO, BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; const BIDDER_CODE = 'alkimi'; const GVLID = 1169; +const USER_ID_KEY = 'alkimiUserID'; export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -43,11 +46,16 @@ export const spec = { bidIds.push(bidRequest.bidId) }) + const ortb2 = bidderRequest.ortb2 + const site = ortb2?.site + + const id = getUserId() const alkimiConfig = config.getConfig('alkimi') - const fullPageAuction = bidderRequest.ortb2?.source?.ext?.full_page_auction - const source = fullPageAuction != undefined ? { ext: { full_page_auction: fullPageAuction } } : undefined + const fpa = ortb2?.source?.ext?.fpa + const source = fpa != undefined ? { ext: { fpa } } : undefined const walletID = alkimiConfig && alkimiConfig.walletID - const user = walletID != undefined ? { ext: { walletID: walletID } } : undefined + const userParams = alkimiConfig && alkimiConfig.userParams + const user = (walletID != undefined || userParams != undefined || id != undefined) ? { id, ext: { walletID, userParams } } : undefined let payload = { requestId: generateUUID(), @@ -66,11 +74,14 @@ export const spec = { source, user, site: { - keywords: bidderRequest.ortb2?.site?.keywords + keywords: site?.keywords, + sectioncat: site?.sectioncat, + pagecat: site?.pagecat, + cat: site?.cat }, - at: bidderRequest.ortb2?.at, - bcat: bidderRequest.ortb2?.bcat, - wseat: bidderRequest.ortb2?.wseat + at: ortb2?.at, + bcat: ortb2?.bcat, + wseat: ortb2?.wseat } } @@ -111,7 +122,7 @@ export const spec = { } const {prebidResponse} = serverBody; - if (!prebidResponse || typeof prebidResponse !== 'object') { + if (!Array.isArray(prebidResponse)) { return []; } @@ -141,6 +152,24 @@ export const spec = { return true; } return false; + }, + + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + if (syncOptions.iframeEnabled && serverResponses.length > 0) { + const serverBody = serverResponses[0].body; + if (!serverBody || typeof serverBody !== 'object') return []; + + const { iframeList } = serverBody; + if (!Array.isArray(iframeList)) return []; + + const urls = []; + iframeList.forEach(url => { + urls.push({type: 'iframe', url}); + }) + + return urls; + } + return []; } } @@ -175,4 +204,15 @@ const getFormatType = bidRequest => { return formats } +const getUserId = () => { + if (storage.localStorageIsEnabled()) { + let userId = storage.getDataFromLocalStorage(USER_ID_KEY) + if (!userId) { + userId = generateUUID() + storage.setDataInLocalStorage(USER_ID_KEY, userId) + } + return userId + } +} + registerBidder(spec); diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js index 90a9e409e69..e551d98fa07 100644 --- a/test/spec/modules/alkimiBidAdapter_spec.js +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -91,11 +91,11 @@ describe('alkimiBidAdapter', function () { }) it('should return false when required params are not passed', function () { - let bid = Object.assign({}, REQUEST) + let bid = JSON.parse(JSON.stringify(REQUEST)) delete bid.params.token expect(spec.isBidRequestValid(bid)).to.equal(false) - bid = Object.assign({}, REQUEST) + bid = JSON.parse(JSON.stringify(REQUEST)) delete bid.params expect(spec.isBidRequestValid(bid)).to.equal(false) }) @@ -115,29 +115,35 @@ describe('alkimiBidAdapter', function () { uspConsent: 'uspConsent', ortb2: { site: { - keywords: 'test1, test2' + keywords: 'test1, test2', + cat: ['IAB2'], + pagecat: ['IAB3'], + sectioncat: ['IAB4'] }, at: 2, bcat: ['BSW1', 'BSW2'], wseat: ['16', '165'] } } - const bidderRequest = spec.buildRequests(bidRequests, requestData) it('should return a properly formatted request with eids defined', function () { + const bidderRequest = spec.buildRequests(bidRequests, requestData) expect(bidderRequest.data.eids).to.deep.equal(REQUEST.userIdAsEids) }) it('should return a properly formatted request with gdpr defined', function () { + const bidderRequest = spec.buildRequests(bidRequests, requestData) expect(bidderRequest.data.gdprConsent.consentRequired).to.equal(true) expect(bidderRequest.data.gdprConsent.consentString).to.equal('test-consent') }) it('should return a properly formatted request with uspConsent defined', function () { + const bidderRequest = spec.buildRequests(bidRequests, requestData) expect(bidderRequest.data.uspConsent).to.equal('uspConsent') }) it('sends bid request to ENDPOINT via POST', function () { + const bidderRequest = spec.buildRequests(bidRequests, requestData) expect(bidderRequest.method).to.equal('POST') expect(bidderRequest.data.requestId).to.not.equal(undefined) expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') @@ -146,7 +152,7 @@ describe('alkimiBidAdapter', function () { expect(bidderRequest.data.signRequest.randomUUID).to.equal(undefined) expect(bidderRequest.data.bidIds).to.deep.contains('456') expect(bidderRequest.data.signature).to.equal(undefined) - expect(bidderRequest.data.ortb2).to.deep.contains({ at: 2, wseat: ['16', '165'], bcat: ['BSW1', 'BSW2'], site: { keywords: 'test1, test2' }, }) + expect(bidderRequest.data.ortb2).to.deep.contains({ at: 2, wseat: ['16', '165'], bcat: ['BSW1', 'BSW2'], site: { keywords: 'test1, test2', cat: ['IAB2'], pagecat: ['IAB3'], sectioncat: ['IAB4'] } }) expect(bidderRequest.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) expect(bidderRequest.options.contentType).to.equal('application/json') expect(bidderRequest.url).to.equal(ENDPOINT) From 0fd106dea29b7cf70be35136c5e567782166cc8c Mon Sep 17 00:00:00 2001 From: skapoorViant <153030693+skapoorViant@users.noreply.github.com> Date: Thu, 25 Apr 2024 09:55:42 -0700 Subject: [PATCH 666/760] Called replaceAuctionMacro in viantOrtbBidAdapter to replace Auction_Price (#11394) * Called replaceAuctionMacro in viantOrtbBidAdapter to replace Auction_Price macro * Fixed changes --- modules/viantOrtbBidAdapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index 0f7953a192a..ee4cbe2d904 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -51,8 +51,10 @@ export const spec = { onBidWon: function (bid) { if (bid.burl) { utils.triggerPixel(bid.burl); + utils.replaceAuctionPrice(bid.burl, bid.price); } else if (bid.nurl) { utils.triggerPixel(bid.nurl); + utils.replaceAuctionPrice(bid.nurl, bid.price); } } } From 49c0d87c2cab1a5ca8a0f2f7f75865c04e77d0b9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 25 Apr 2024 17:43:48 +0000 Subject: [PATCH 667/760] Prebid 8.46.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47383455eaa..5a9f282690f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.46.0-pre", + "version": "8.46.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 2b4d9dd68c1..cd16cf746b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.46.0-pre", + "version": "8.46.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 546595f04f41bdebcec84bb96681ef1cff318a6c Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 25 Apr 2024 17:43:48 +0000 Subject: [PATCH 668/760] Increment version to 8.47.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a9f282690f..6c9bddfaf06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.46.0", + "version": "8.47.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index cd16cf746b6..7236e62b5d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.46.0", + "version": "8.47.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From cbbd524a65817fa58a6377b4d39e3f7dd3a6d055 Mon Sep 17 00:00:00 2001 From: skapoorViant <153030693+skapoorViant@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:32:03 -0700 Subject: [PATCH 669/760] ViantOrtb Bid Adapter : called replaceAuctionMacro to replace Auction_Price (#11398) * Called replaceAuctionMacro in viantOrtbBidAdapter to replace Auction_Price * fixed changes --- modules/viantOrtbBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index ee4cbe2d904..d056dfeb2eb 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -51,10 +51,10 @@ export const spec = { onBidWon: function (bid) { if (bid.burl) { utils.triggerPixel(bid.burl); - utils.replaceAuctionPrice(bid.burl, bid.price); + utils.triggerPixel(utils.replaceAuctionPrice(bid.burl, bid.originalCpm || bid.cpm)); } else if (bid.nurl) { utils.triggerPixel(bid.nurl); - utils.replaceAuctionPrice(bid.nurl, bid.price); + utils.triggerPixel(utils.replaceAuctionPrice(bid.nurl, bid.originalCpm || bid.cpm)); } } } From b557f048770d64371674f08dca21fe3cd8fcc7b3 Mon Sep 17 00:00:00 2001 From: jdelhommeau Date: Thu, 25 Apr 2024 22:57:51 +0200 Subject: [PATCH 670/760] UtiqMtpIdSystem: create the Utiq MTP ID submodule (#11372) * Creating new user ID module utiqMtpIdSystem to provide option for publishers using Utiq to send the martechPass. This is complementary to the UtiqIDSystem module * fixing documentation to reflect correct name utiqMtp instead of utiq-mtp * fixing inline doc and creating unit test for utiqMtpIdSystem * Adding extra documentation to notify publishers to contact Utiq in order to integrate the Utiq script on their site --------- Co-authored-by: root Co-authored-by: jdelhommeau --- modules/.submodules.json | 1 + modules/utiqMtpIdSystem.js | 138 ++++++++++++++++ modules/utiqMtpIdSystem.md | 22 +++ test/spec/modules/utiqMtpIdSystem_spec.js | 188 ++++++++++++++++++++++ 4 files changed, 349 insertions(+) create mode 100644 modules/utiqMtpIdSystem.js create mode 100644 modules/utiqMtpIdSystem.md create mode 100644 test/spec/modules/utiqMtpIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index cfa98b5ab32..3913f3f5734 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -39,6 +39,7 @@ "teadsIdSystem", "tncIdSystem", "utiqSystem", + "utiqMtpIdSystem", "uid2IdSystem", "euidIdSystem", "unifiedIdSystem", diff --git a/modules/utiqMtpIdSystem.js b/modules/utiqMtpIdSystem.js new file mode 100644 index 00000000000..c5d25f27ca5 --- /dev/null +++ b/modules/utiqMtpIdSystem.js @@ -0,0 +1,138 @@ +/** + * This module adds Utiq MTP provided by Utiq SA/NV to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/utiqMtpIdSystem + * @requires module:modules/userId + */ +import { logInfo } from '../src/utils.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; + +const MODULE_NAME = 'utiqMtpId'; +const LOG_PREFIX = 'Utiq MTP module'; + +export const storage = getStorageManager({ + moduleType: MODULE_TYPE_UID, + moduleName: MODULE_NAME, +}); + +/** + * Get the "mtid" from html5 local storage to make it available to the UserId module. + * @param config + * @returns {{utiqMtp: (*|string)}} + */ +function getUtiqFromStorage() { + let utiqPass; + let utiqPassStorage = JSON.parse( + storage.getDataFromLocalStorage('utiqPass') + ); + logInfo( + `${LOG_PREFIX}: Local storage utiqPass: ${JSON.stringify( + utiqPassStorage + )}` + ); + + if ( + utiqPassStorage && + utiqPassStorage.connectId && + Array.isArray(utiqPassStorage.connectId.idGraph) && + utiqPassStorage.connectId.idGraph.length > 0 + ) { + utiqPass = utiqPassStorage.connectId.idGraph[0]; + } + logInfo( + `${LOG_PREFIX}: Graph of utiqPass: ${JSON.stringify( + utiqPass + )}` + ); + + return { + utiqMtp: + utiqPass && utiqPass.mtid + ? utiqPass.mtid + : null, + }; +} + +/** @type {Submodule} */ +export const utiqMtpIdSubmodule = { + /** + * Used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + /** + * Decodes the stored id value for passing to bid requests. + * @function + * @returns {{utiqMtp: string} | null} + */ + decode(bidId) { + logInfo(`${LOG_PREFIX}: Decoded ID value ${JSON.stringify(bidId)}`); + return bidId.utiqMtp ? bidId : null; + }, + /** + * Get the id from helper function and initiate a new user sync. + * @param config + * @returns {{callback: result}|{id: {utiqMtp: string}}} + */ + getId: function (config) { + const data = getUtiqFromStorage(); + if (data.utiqMtp) { + logInfo(`${LOG_PREFIX}: Local storage ID value ${JSON.stringify(data)}`); + return { id: { utiqMtp: data.utiqMtp } }; + } else { + if (!config) { + config = {}; + } + if (!config.params) { + config.params = {}; + } + if ( + typeof config.params.maxDelayTime === 'undefined' || + config.params.maxDelayTime === null + ) { + config.params.maxDelayTime = 1000; + } + // Current delay and delay step in milliseconds + let currentDelay = 0; + const delayStep = 50; + const result = (callback) => { + const data = getUtiqFromStorage(); + if (!data.utiqMtp) { + if (currentDelay > config.params.maxDelayTime) { + logInfo( + `${LOG_PREFIX}: No utiq value set after ${config.params.maxDelayTime} max allowed delay time` + ); + callback(null); + } else { + currentDelay += delayStep; + setTimeout(() => { + result(callback); + }, delayStep); + } + } else { + const dataToReturn = { utiqMtp: data.utiqMtp }; + logInfo( + `${LOG_PREFIX}: Returning ID value data of ${JSON.stringify( + dataToReturn + )}` + ); + callback(dataToReturn); + } + }; + return { callback: result }; + } + }, + eids: { + 'utiqMtp': { + source: 'utiq-mtp.com', + atype: 1, + getValue: function (data) { + return data; + }, + }, + } +}; + +submodule('userId', utiqMtpIdSubmodule); diff --git a/modules/utiqMtpIdSystem.md b/modules/utiqMtpIdSystem.md new file mode 100644 index 00000000000..9b738152969 --- /dev/null +++ b/modules/utiqMtpIdSystem.md @@ -0,0 +1,22 @@ +## Utiq User ID Submodule + +Utiq MTP ID Module. + +### Utiq installation ### + +In order to use utiq in your prebid setup, you must first integrate utiq solution on your website as per https://docs.utiq.com/ +If you are interested in using Utiq on your website, please contact Utiq on https://utiq.com/contact/ + +### Prebid integration ### + +First, make sure to add the utiq MTP submodule to your Prebid.js package with: + +``` +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqMtpIdSystem +``` + +## Parameter Descriptions + +| Params under userSync.userIds[] | Type | Description | Example | +| ------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------- | +| name | String | The name of the module | `"utiqMtpId"` | diff --git a/test/spec/modules/utiqMtpIdSystem_spec.js b/test/spec/modules/utiqMtpIdSystem_spec.js new file mode 100644 index 00000000000..0456d485875 --- /dev/null +++ b/test/spec/modules/utiqMtpIdSystem_spec.js @@ -0,0 +1,188 @@ +import { expect } from 'chai'; +import { utiqMtpIdSubmodule } from 'modules/utiqMtpIdSystem.js'; +import { storage } from 'modules/utiqMtpIdSystem.js'; + +describe('utiqMtpIdSystem', () => { + const utiqPassKey = 'utiqPass'; + + const getStorageData = (idGraph) => { + if (!idGraph) { + idGraph = {id: 501, domain: ''}; + } + return { + 'connectId': { + 'idGraph': [idGraph], + } + } + }; + + it('should have the correct module name declared', () => { + expect(utiqMtpIdSubmodule.name).to.equal('utiqMtpId'); + }); + + describe('utiqMtpId getId()', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(utiqPassKey); + }); + + it('it should return object with key callback', () => { + expect(utiqMtpIdSubmodule.getId()).to.have.property('callback'); + }); + + it('should return object with key callback with value type - function', () => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); + expect(utiqMtpIdSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqMtpIdSubmodule.getId().callback).to.be.equal('function'); + }); + + it('tests if localstorage & JSON works properly ', () => { + const idGraph = { + 'domain': 'domainValue', + 'mtid': 'mtidValue', + }; + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + expect(JSON.parse(storage.getDataFromLocalStorage(utiqPassKey))).to.have.property('connectId'); + }); + + it('returns {id: {utiq: data.utiq}} if we have the right data stored in the localstorage ', () => { + const idGraph = { + 'domain': 'test.domain', + 'mtid': 'mtidValue', + }; + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + const response = utiqMtpIdSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('utiqMtp'); + expect(response.id.utiqMtp).to.be.equal('mtidValue'); + }); + + it('returns {utiqMtp: data.utiqMtp} if we have the right data stored in the localstorage right after the callback is called', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'mtid': 'mtidValue', + }; + const response = utiqMtpIdSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('utiqMtp'); + expect(result.utiqMtp).to.be.equal('mtidValue'); + done() + }) + } + }); + + it('returns {utiqMtp: data.utiqMtp} if we have the right data stored in the localstorage right after 500ms delay', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'mtid': 'mtidValue', + }; + + const response = utiqMtpIdSubmodule.getId(); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.not.be.null; + expect(result).to.have.property('utiqMtp'); + expect(result.utiqMtp).to.be.equal('mtidValue'); + done() + }) + } + }); + + it('returns null if we have the data stored in the localstorage after 500ms delay and the max (waiting) delay is only 200ms ', (done) => { + const idGraph = { + 'domain': 'test.domain', + 'mtid': 'mtidValue', + }; + + const response = utiqMtpIdSubmodule.getId({params: {maxDelayTime: 200}}); + expect(response).to.have.property('callback'); + expect(response.callback.toString()).contain('result(callback)'); + + if (typeof response.callback === 'function') { + setTimeout(() => { + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + }, 500); + response.callback(function (result) { + expect(result).to.be.null; + done() + }) + } + }); + }); + + describe('utiq decode()', () => { + const VALID_API_RESPONSES = [ + { + expected: '32a97f612', + payload: { + utiqMtp: '32a97f612' + } + }, + { + expected: '32a97f61', + payload: { + utiqMtp: '32a97f61', + } + }, + ]; + VALID_API_RESPONSES.forEach(responseData => { + it('should return a newly constructed object with the utiqMtp for a payload with {utiqMtp: value}', () => { + expect(utiqMtpIdSubmodule.decode(responseData.payload)).to.deep.equal( + {utiqMtp: responseData.expected} + ); + }); + }); + + [{}, '', {foo: 'bar'}].forEach((response) => { + it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { + expect(utiqMtpIdSubmodule.decode(response)).to.be.null; + }); + }); + }); + + describe('utiq messageHandler', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(utiqPassKey); + }); + + const domains = [ + 'domain1', + 'domain2', + 'domain3', + ]; + + domains.forEach(domain => { + it(`correctly sets utiq value for domain name ${domain}`, (done) => { + const idGraph = { + 'domain': domain, + 'mtid': 'mtidValue', + }; + + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); + + const eventData = { + data: `{\"msgType\":\"MNOSELECTOR\",\"body\":{\"url\":\"https://${domain}/some/path\"}}` + }; + + window.dispatchEvent(new MessageEvent('message', eventData)); + + const response = utiqMtpIdSubmodule.getId(); + expect(response).to.have.property('id'); + expect(response.id).to.have.property('utiqMtp'); + expect(response.id.utiqMtp).to.be.equal('mtidValue'); + done(); + }); + }); + }); +}); From 2be3f2933f4cdc0de545f6ac67a0ce8db8def638 Mon Sep 17 00:00:00 2001 From: Pavlo Kyrylenko Date: Fri, 26 Apr 2024 14:51:21 +0300 Subject: [PATCH 671/760] Rename idWardRtdProvider to anonymisedRtdProvider (#10176) * rename idWardRtdProvider to anonymisedRtdProvider * ANON-4689: bidders param added. Support appnexus * updated readme file * updated readme description * pr review: restored idWardRtdProvider * Module renamed notification added * added Warning decoration * reverter needless changes * Renamed "ID Ward" mentions to "Anonymised" * 'bidders' param optional. Reusing shared code * corrected indentions * minor correction * clean up --------- Co-authored-by: Pavlo --- .../gpt/anonymised_segments_example.html | 112 +++++++++ modules/anonymisedRtdProvider.js | 122 ++++++++++ modules/anonymisedRtdProvider.md | 54 +++++ modules/idWardRtdProvider.js | 103 +-------- modules/idWardRtdProvider.md | 9 +- .../modules/anonymisedRtdProvider_spec.js | 214 ++++++++++++++++++ test/spec/modules/idWardRtdProvider_spec.js | 4 +- 7 files changed, 515 insertions(+), 103 deletions(-) create mode 100644 integrationExamples/gpt/anonymised_segments_example.html create mode 100644 modules/anonymisedRtdProvider.js create mode 100644 modules/anonymisedRtdProvider.md create mode 100644 test/spec/modules/anonymisedRtdProvider_spec.js diff --git a/integrationExamples/gpt/anonymised_segments_example.html b/integrationExamples/gpt/anonymised_segments_example.html new file mode 100644 index 00000000000..16f3f879636 --- /dev/null +++ b/integrationExamples/gpt/anonymised_segments_example.html @@ -0,0 +1,112 @@ + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+
First Party Data (ortb2) Sent to Bidding Adapter
+
+ + diff --git a/modules/anonymisedRtdProvider.js b/modules/anonymisedRtdProvider.js new file mode 100644 index 00000000000..48ac649f002 --- /dev/null +++ b/modules/anonymisedRtdProvider.js @@ -0,0 +1,122 @@ +/** + * This module adds the Anonymised RTD provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will populate real-time data from Anonymised + * @module modules/anonymisedRtdProvider + * @requires module:modules/realTimeData + */ +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; + +export function createRtdProvider(moduleName) { + const MODULE_NAME = 'realTimeData'; + const SUBMODULE_NAME = moduleName; + + const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); + /** + * Add real-time data & merge segments. + * @param ortb2 object to merge into + * @param {Object} rtd + */ + function addRealTimeData(ortb2, rtd) { + if (isPlainObject(rtd.ortb2)) { + logMessage(`${SUBMODULE_NAME}RtdProvider: merging original: `, ortb2); + logMessage(`${SUBMODULE_NAME}RtdProvider: merging in: `, rtd.ortb2); + mergeDeep(ortb2, rtd.ortb2); + } + } + /** + * Try parsing stringified array of segment IDs. + * @param {String} data + */ + function tryParse(data) { + try { + return JSON.parse(data); + } catch (err) { + logError(`${SUBMODULE_NAME}RtdProvider: failed to parse json:`, data); + return null; + } + } + /** + * Real-time data retrieval from Anonymised + * @param {Object} reqBidsConfigObj + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ + function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { + if (rtdConfig && isPlainObject(rtdConfig.params)) { + const cohortStorageKey = rtdConfig.params.cohortStorageKey; + const bidders = rtdConfig.params.bidders; + + if (cohortStorageKey !== 'cohort_ids') { + logError(`${SUBMODULE_NAME}RtdProvider: 'cohortStorageKey' should be 'cohort_ids'`) + return; + } + + const jsonData = storage.getDataFromLocalStorage(cohortStorageKey); + if (!jsonData) { + return; + } + + const segments = tryParse(jsonData); + + if (segments) { + const udSegment = { + name: 'anonymised.io', + ext: { + segtax: rtdConfig.params.segtax + }, + segment: segments.map(x => ({id: x})) + } + + logMessage(`${SUBMODULE_NAME}RtdProvider: user.data.segment: `, udSegment); + const data = { + rtd: { + ortb2: { + user: { + data: [ + udSegment + ] + } + } + } + }; + + if (bidders?.includes('appnexus')) { + data.rtd.ortb2.user.keywords = segments.map(x => `perid=${x}`).join(','); + } + + addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data.rtd); + onDone(); + } + } + } + /** + * Module init + * @param {Object} provider + * @param {Object} userConsent + * @return {boolean} + */ + function init(provider, userConsent) { + return true; + } + /** @type {RtdSubmodule} */ + const rtdSubmodule = { + name: SUBMODULE_NAME, + getBidRequestData: getRealTimeData, + init: init + }; + + submodule(MODULE_NAME, rtdSubmodule); + + return { + getRealTimeData, + rtdSubmodule, + storage + }; +} + +export const { getRealTimeData, rtdSubmodule: anonymisedRtdSubmodule, storage } = createRtdProvider('anonymised'); diff --git a/modules/anonymisedRtdProvider.md b/modules/anonymisedRtdProvider.md new file mode 100644 index 00000000000..2ff2597690b --- /dev/null +++ b/modules/anonymisedRtdProvider.md @@ -0,0 +1,54 @@ +### Overview + +Anonymised is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. +Anonymised’s Real-time Data Provider automatically obtains segment IDs from the Anonymised on-domain script (via localStorage) and passes them to the bid-stream. + +### Integration + + - Build the anonymisedRtd module into the Prebid.js package with: + + ```bash + gulp build --modules=anonymisedRtdProvider,... + ``` + + - Use `setConfig` to instruct Prebid.js to initilaize the anonymisedRtdProvider module, as specified below. + +### Configuration + +```javascript + pbjs.setConfig({ + realTimeData: { + dataProviders: [ + { + name: "anonymised", + waitForIt: true, + params: { + cohortStorageKey: "cohort_ids", + bidders: ["smartadserver", "appnexus"], + segtax: 1000 + } + } + ] + } + }); + ``` + + ### Config Syntax details +| Name |Type | Description | Notes | +| :------------ | :------------ | :------------ |:------------ | +| name | String | Anonymised Rtd module name | 'anonymised' always| +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params.cohortStorageKey | String | the `localStorage` key, under which Anonymised Marketing Tag stores the segment IDs | 'cohort_ids' always | +| params.bidders | Array | Bidders with which to share segment information | Optional | +| params.segtax | Integer | The taxonomy for Anonymised | '1000' always | + +Please note that anonymisedRtdProvider should be integrated into the publisher website along with the [Anonymised Marketing Tag](https://support.anonymised.io/integrate/marketing-tag). +Please reach out to Anonymised [representative](mailto:support@anonymised.io) if you have any questions or need further help to integrate Prebid, anonymisedRtdProvider, and Anonymised Marketing Tag + +### Testing +To view an example of available segments returned by Anonymised: +```bash +gulp serve --modules=rtdModule,anonymisedRtdProvider,pubmaticBidAdapter +``` +And then point your browser at: +"http://localhost:9999/integrationExamples/gpt/anonymised_segments_example.html" diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js index dd08a132b2d..8e6e3c20a64 100644 --- a/modules/idWardRtdProvider.js +++ b/modules/idWardRtdProvider.js @@ -1,107 +1,10 @@ /** * This module adds the ID Ward RTD provider to the real time data module * The {@link module:modules/realTimeData} module is required - * The module will poulate real-time data from ID Ward + * The module will populate real-time data from ID Ward * @module modules/idWardRtdProvider * @requires module:modules/realTimeData */ -import {getStorageManager} from '../src/storageManager.js'; -import {submodule} from '../src/hook.js'; -import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.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 = 'idWard'; - -export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); -/** - * Add real-time data & merge segments. - * @param ortb2 object to merge into - * @param {Object} rtd - */ -function addRealTimeData(ortb2, rtd) { - if (isPlainObject(rtd.ortb2)) { - logMessage('idWardRtdProvider: merging original: ', ortb2); - logMessage('idWardRtdProvider: merging in: ', rtd.ortb2); - mergeDeep(ortb2, rtd.ortb2); - } -} - -/** - * Try parsing stringified array of segment IDs. - * @param {String} data - */ -function tryParse(data) { - try { - return JSON.parse(data); - } catch (err) { - logError(`idWardRtdProvider: failed to parse json:`, data); - return null; - } -} - -/** - * Real-time data retrieval from ID Ward - * @param {Object} reqBidsConfigObj - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - */ -export function getRealTimeData(reqBidsConfigObj, onDone, rtdConfig, userConsent) { - if (rtdConfig && isPlainObject(rtdConfig.params)) { - const jsonData = storage.getDataFromLocalStorage(rtdConfig.params.cohortStorageKey) - - if (!jsonData) { - return; - } - - const segments = tryParse(jsonData); - - if (segments) { - const udSegment = { - name: 'id-ward.com', - ext: { - segtax: rtdConfig.params.segtax - }, - segment: segments.map(x => ({id: x})) - } - - logMessage('idWardRtdProvider: user.data.segment: ', udSegment); - const data = { - rtd: { - ortb2: { - user: { - data: [ - udSegment - ] - } - } - } - }; - addRealTimeData(reqBidsConfigObj.ortb2Fragments?.global, data.rtd); - onDone(); - } - } -} - -/** - * Module init - * @param {Object} provider - * @param {Object} userConsent - * @return {boolean} - */ -function init(provider, userConsent) { - return true; -} - -/** @type {RtdSubmodule} */ -export const idWardRtdSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getRealTimeData, - init: init -}; +import { createRtdProvider } from './anonymisedRtdProvider.js';/* eslint prebid/validate-imports: "off" */ -submodule(MODULE_NAME, idWardRtdSubmodule); +export const { getRealTimeData, rtdSubmodule: idWardRtdSubmodule, storage } = createRtdProvider('idWard'); diff --git a/modules/idWardRtdProvider.md b/modules/idWardRtdProvider.md index 5a44bfa49f3..1c9f0654de6 100644 --- a/modules/idWardRtdProvider.md +++ b/modules/idWardRtdProvider.md @@ -1,3 +1,10 @@ +> **Warning!** +> +> The **idWardRtdProvider** module has been renamed to [anonymisedRtdProvider](anonymisedRtdProvider.md) in light of the company's rebranding. +> **idWardRtdProvider** module is maintained for backward compatibility until the next major Prebid release. +> +> Please use anonymisedRtdProvider instead of idWardRtdProvider in your Prebid integration. + ### Overview ID Ward is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. @@ -41,4 +48,4 @@ To view an example of available segments returned by Id Ward: ‘gulp serve --modules=rtdModule,idWardRtdProvider,pubmaticBidAdapter ``` and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" +"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" \ No newline at end of file diff --git a/test/spec/modules/anonymisedRtdProvider_spec.js b/test/spec/modules/anonymisedRtdProvider_spec.js new file mode 100644 index 00000000000..89115e5e740 --- /dev/null +++ b/test/spec/modules/anonymisedRtdProvider_spec.js @@ -0,0 +1,214 @@ +import {config} from 'src/config.js'; +import {getRealTimeData, anonymisedRtdSubmodule, storage} from 'modules/anonymisedRtdProvider.js'; + +describe('anonymisedRtdProvider', function() { + let getDataFromLocalStorageStub; + + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['bid1', 'bid2'] + } + ] + }; + + const onDone = function() { return true }; + + const cmoduleConfig = { + 'name': 'anonymised', + 'params': { + 'cohortStorageKey': 'cohort_ids' + } + } + + beforeEach(function() { + config.resetConfig(); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') + }); + + afterEach(function () { + getDataFromLocalStorageStub.restore(); + }); + + describe('anonymisedRtdSubmodule', function() { + it('successfully instantiates', function () { + expect(anonymisedRtdSubmodule.init()).to.equal(true); + }); + }); + + describe('Get Real-Time Data', function() { + it('gets rtd from local storage and set to ortb2.user.data', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + bidders: ['smartadserver'], + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.be.undefined; + }); + + it('gets rtd from local storage and set to ortb2.user.keywords for appnexus bidders parameter', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + bidders: ['smartadserver', 'appnexus'], + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.include('perid=TCZPQOWPEJG3MJOTUQUF793A'); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.include('perid=93SUG3H540WBJMYNT03KX8N3'); + }); + + it('gets rtd from local storage and set to ortb2.user.data if `bidders` parameter undefined', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + const rtdUserObj1 = { + name: 'anonymised.io', + ext: { + segtax: 503 + }, + segment: [ + { + id: 'TCZPQOWPEJG3MJOTUQUF793A' + }, + { + id: '93SUG3H540WBJMYNT03KX8N3' + } + ] + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + expect(bidConfig.ortb2Fragments.global.user.keywords).to.be.undefined; + }); + + it('do not set rtd if `cohortStorageKey` parameter undefined', function() { + const rtdConfig = { + params: { + bidders: ['smartadserver'] + } + }; + + const bidConfig = { + ortb2Fragments: { + global: {} + } + }; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(JSON.stringify(['randomsegmentid'])); + + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(bidConfig.ortb2Fragments.global.user).to.be.undefined; + }); + + it('do not set rtd if local storage empty', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns(null); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('do not set rtd if local storage has incorrect value', function() { + const rtdConfig = { + params: { + cohortStorageKey: 'cohort_ids', + segtax: 503 + } + }; + + const bidConfig = {}; + + getDataFromLocalStorageStub.withArgs('cohort_ids') + .returns('wrong cohort ids value'); + + expect(config.getConfig().ortb2).to.be.undefined; + getRealTimeData(bidConfig, () => {}, rtdConfig, {}); + expect(config.getConfig().ortb2).to.be.undefined; + }); + + it('should initialize and return with config', function () { + expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) + }); + }); +}); diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js index 924a3794c7b..d1601f058ff 100644 --- a/test/spec/modules/idWardRtdProvider_spec.js +++ b/test/spec/modules/idWardRtdProvider_spec.js @@ -52,7 +52,7 @@ describe('idWardRtdProvider', function() { }; const rtdUserObj1 = { - name: 'id-ward.com', + name: 'anonymised.io', ext: { segtax: 503 }, @@ -109,7 +109,7 @@ describe('idWardRtdProvider', function() { expect(config.getConfig().ortb2).to.be.undefined; }); - it('should initalise and return with config', function () { + it('should initialize and return with config', function () { expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) }); }); From c8353255cda3a0b3fa69ca879b98d5735e588bb6 Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Fri, 26 Apr 2024 17:14:14 +0400 Subject: [PATCH 672/760] Sovrn Bid Adapter: Fledge module integration (#11382) * fledge init * minor test fix * additional check and test fixes --- modules/sovrnBidAdapter.js | 36 ++++++- test/spec/modules/sovrnBidAdapter_spec.js | 121 ++++++++++++++++++++++ 2 files changed, 153 insertions(+), 4 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 6dc5a3dc2ef..3df025b1619 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -137,6 +137,17 @@ export const spec = { imp.ext = imp.ext || {} imp.ext.deals = segmentsString.split(',').map(deal => deal.trim()) } + + const auctionEnvironment = bid?.ortb2Imp?.ext?.ae + if (bidderRequest.fledgeEnabled && isInteger(auctionEnvironment)) { + imp.ext = imp.ext || {} + imp.ext.ae = auctionEnvironment + } else { + if (imp.ext?.ae) { + delete imp.ext.ae + } + } + sovrnImps.push(imp) }) @@ -209,14 +220,14 @@ export const spec = { /** * Format Sovrn responses as Prebid bid responses - * @param {id, seatbid} sovrnResponse A successful response from Sovrn. - * @return {Bid[]} An array of formatted bids. + * @param {id, seatbid, ext} sovrnResponse A successful response from Sovrn. + * @return An array of formatted bids (+ fledgeAuctionConfigs if available) */ - interpretResponse: function({ body: {id, seatbid} }) { + interpretResponse: function({ body: {id, seatbid, ext} }) { if (!id || !seatbid || !Array.isArray(seatbid)) return [] try { - return seatbid + let bids = seatbid .filter(seat => seat) .map(seat => seat.bid.map(sovrnBid => { const bid = { @@ -242,6 +253,23 @@ export const spec = { return bid })) .flat() + + let fledgeAuctionConfigs = deepAccess(ext, 'fledge_auction_configs'); + if (fledgeAuctionConfigs) { + fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { + return { + bidId, + config: Object.assign({ + auctionSignals: {}, + }, cfg) + } + }); + return { + bids, + fledgeAuctionConfigs, + } + } + return bids } catch (e) { logError('Could not interpret bidresponse, error details:', e) return e diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index ad195b6924a..9c7f433ef96 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -240,6 +240,53 @@ describe('sovrnBidAdapter', function() { expect(payload.imp[0]?.ext?.tid).to.equal('1a2c032473f4983') }) + it('when FLEDGE is enabled, should send ortb2imp.ext.ae', function () { + const bidderRequest = { + ...baseBidderRequest, + fledgeEnabled: true + } + const bidRequest = { + ...baseBidRequest, + ortb2Imp: { + ext: { + ae: 1 + } + }, + } + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data) + expect(payload.imp[0].ext.ae).to.equal(1) + }) + + it('when FLEDGE is not enabled, should not send ortb2imp.ext.ae', function () { + const bidRequest = { + ...baseBidRequest, + ortb2Imp: { + ext: { + ae: 1 + } + }, + } + const payload = JSON.parse(spec.buildRequests([bidRequest], baseBidderRequest).data) + expect(payload.imp[0].ext.ae).to.be.undefined + }) + + it('when FLEDGE is enabled, but env is malformed, should not send ortb2imp.ext.ae', function () { + const bidderRequest = { + ...baseBidderRequest, + fledgeEnabled: true + } + const bidRequest = { + ...baseBidRequest, + ortb2Imp: { + ext: { + ae: 'malformed' + } + }, + } + const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data) + expect(payload.imp[0].ext.ae).to.be.undefined + }) + it('includes the ad unit code in the request', function() { const impression = payload.imp[0] expect(impression.adunitcode).to.equal('adunit-code') @@ -780,6 +827,80 @@ describe('sovrnBidAdapter', function() { }) }) + describe('fledge response', function () { + let fledgeResponse = { + body: { + id: '37386aade21a71', + seatbid: [{ + bid: [{ + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: '', + h: 90, + w: 728 + }] + }], + ext: { + fledge_auction_configs: { + 'test_bid_id': { + seller: 'ap.lijit.com', + interestGroupBuyers: ['dsp1.com'], + sellerTimeout: 0, + perBuyerSignals: { + 'dsp1.com': { + bid_macros: 0.1, + disallowed_adv_ids: [ + '8765', + '4321' + ], + } + } + } + } + } + } + } + let invalidFledgeResponse = { + body: { + id: '37386aade21a71', + seatbid: [{ + bid: [{ + id: 'a_403370_332fdb9b064040ddbec05891bd13ab28', + crid: 'creativelycreatedcreativecreative', + impid: '263c448586f5a1', + price: 0.45882675, + nurl: '', + adm: '', + h: 90, + w: 728 + }] + }], + ext: { + fledge_auction_configs: { + } + } + } + } + it('should return fledge auction configs alongside bids', function () { + const result = spec.interpretResponse(fledgeResponse) + expect(result).to.have.property('bids') + expect(result).to.have.property('fledgeAuctionConfigs') + expect(result.fledgeAuctionConfigs.length).to.equal(1) + expect(result.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id') + expect(result.fledgeAuctionConfigs[0].config).to.not.be.undefined + expect(result.fledgeAuctionConfigs[0].config).to.contain.keys('seller', 'interestGroupBuyers', 'sellerTimeout', 'perBuyerSignals') + }) + it('should ignore invalid fledge auction configs', function () { + const result = spec.interpretResponse(invalidFledgeResponse) + expect(result).to.have.property('bids') + expect(result).to.have.property('fledgeAuctionConfigs') + expect(result.fledgeAuctionConfigs.length).to.equal(0) + }) + }) + describe('interpretResponse video', function () { let videoResponse const bidAdm = 'key%3Dvalue' From 6c0648b6d84a8ad6682b964029081bd83078442d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 26 Apr 2024 15:49:43 +0200 Subject: [PATCH 673/760] Skip response processing if empty (#11376) --- modules/adyoulikeBidAdapter.js | 4 ++++ test/spec/modules/adyoulikeBidAdapter_spec.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index ad1c0af039e..146e1d3b24a 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -160,6 +160,10 @@ export const spec = { const bidResponses = []; var bidRequests = {}; + if (!serverResponse || !serverResponse.body) { + return bidResponses; + } + try { bidRequests = JSON.parse(request.data).Bids; } catch (err) { diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index ffd6729397a..bafa031cd25 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -812,6 +812,12 @@ describe('Adyoulike Adapter', function () { } }); + it('handles 204 responses', function () { + serverResponse.body = ''; + let result = spec.interpretResponse(serverResponse, []); + expect(result).deep.equal([]); + }); + it('handles nobid responses', function () { let response = [{ BidID: '123dfsdf', From 4deba897f7ecefa695ac160332e05772644d425a Mon Sep 17 00:00:00 2001 From: Dmitry Sinev Date: Fri, 26 Apr 2024 18:08:59 +0300 Subject: [PATCH 674/760] Appnexus Bid Adapter: add support for ast_override_div debug feature (#11390) --- modules/appnexusBidAdapter.js | 12 +++ test/spec/modules/appnexusBidAdapter_spec.js | 82 ++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 5210d6f7562..bb80b6a8588 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -774,6 +774,18 @@ function bidToTag(bid) { } else { tag.code = bid.params.inv_code; } + // Xandr expects GET variable to be in a following format: + // page.html?ast_override_div=divId:creativeId,divId2:creativeId2 + const overrides = getParameterByName('ast_override_div'); + if (isStr(overrides) && overrides !== '') { + const adUnitOverride = overrides.split(',').find((pair) => pair.startsWith(`${bid.adUnitCode}:`)); + if (adUnitOverride) { + const forceCreativeId = adUnitOverride.split(':')[1]; + if (forceCreativeId) { + tag.force_creative_id = parseInt(forceCreativeId, 10); + } + } + } tag.allow_smaller_sizes = bid.params.allow_smaller_sizes || false; tag.use_pmt_rule = (typeof bid.params.use_payment_rule === 'boolean') ? bid.params.use_payment_rule : (typeof bid.params.use_pmt_rule === 'boolean') ? bid.params.use_pmt_rule : false; diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index cc86a8a0aaa..2a277cf1ba7 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1442,6 +1442,88 @@ describe('AppNexusAdapter', function () { config.getConfig.restore(); }); + describe('ast_override_div', function () { + let getParamStub; + let bidRequest = Object.assign({}, bidRequests[0]); + let bidRequest2 = deepClone(bidRequests[0]); + bidRequest2.adUnitCode = 'adUnit_code_2'; + let bidRequest3 = deepClone(bidRequests[0]); + bidRequest3.adUnitCode = 'adUnit_code_3'; + + before(function () { + getParamStub = sinon.stub(utils, 'getParameterByName'); + }); + + it('should set forced creative id if one adUnitCode passed', function () { + getParamStub.callsFake(function(par) { + if (par === 'ast_override_div') return 'adunit-code:1234'; + return ''; + }); + + const request = spec.buildRequests([bidRequest, bidRequest2]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].force_creative_id).to.deep.equal(1234); + expect(payload.tags[1].force_creative_id).to.not.exist; + }); + + it('should set forced creative id if `ast_override_div` is set to override multiple adUnitCode', function () { + getParamStub.callsFake(function(par) { + if (par === 'ast_override_div') return 'adunit-code:1234,adUnit_code_2:5678'; + return ''; + }); + + const request = spec.buildRequests([bidRequest, bidRequest2, bidRequest3]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].force_creative_id).to.deep.equal(1234); + expect(payload.tags[1].force_creative_id).to.deep.equal(5678); + expect(payload.tags[2].force_creative_id).to.not.exist; + }); + + it('should not set forced creative id if `ast_override_div` is missing creativeId', function () { + getParamStub.callsFake(function(par) { + if (par === 'ast_override_div') return 'adunit-code'; + return ''; + }); + + const request = spec.buildRequests([bidRequest, bidRequest2]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].force_creative_id).to.not.exist; + expect(payload.tags[1].force_creative_id).to.not.exist; + }); + + it('should not set forced creative id if `ast_override_div` is in the wrong format', function () { + getParamStub.callsFake(function(par) { + if (par === 'ast_override_div') return 'adunit-code;adUnit_code_2:5678'; + return ''; + }); ; + + const request = spec.buildRequests([bidRequest, bidRequest2]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].force_creative_id).to.not.exist; + expect(payload.tags[1].force_creative_id).to.not.exist; + }); + + it('should not set forced creative id if `ast_override_div` is missing', function () { + getParamStub.callsFake(function(par) { + return ''; + }); ; + + const request = spec.buildRequests([bidRequest, bidRequest2]); + const payload = JSON.parse(request.data); + + expect(payload.tags[0].force_creative_id).to.not.exist; + expect(payload.tags[1].force_creative_id).to.not.exist; + }); + + after(function () { + getParamStub.restore(); + }); + }); + it('should set the X-Is-Test customHeader if test flag is enabled', function () { let bidRequest = Object.assign({}, bidRequests[0]); sinon.stub(config, 'getConfig') From 4db5ae6578d9509f6a484c7008e5b1d87826cf7d Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 26 Apr 2024 12:53:12 -0400 Subject: [PATCH 675/760] ConsentManagementGpp module: throw error on some invalid sections (#11385) * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update consentManagementGpp.js * Update modules/consentManagementGpp.js Co-authored-by: Demetrio Girardi --------- Co-authored-by: Demetrio Girardi --- modules/consentManagementGpp.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index 416430fb1c9..a7bbca62205 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -423,6 +423,11 @@ function processCmpData(consentData) { ) { throw new GPPError('CMP returned unexpected value during lookup process.', consentData); } + ['usnatv1', 'uscav1'].forEach(section => { + if (consentData?.parsedSections?.[section]) { + logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData) + } + }) return storeConsentData(consentData); } From 1243f21f88795386d8a1e9c065a8739acbd1e619 Mon Sep 17 00:00:00 2001 From: jsnellbaker <31102355+jsnellbaker@users.noreply.github.com> Date: Sat, 27 Apr 2024 21:03:24 -0400 Subject: [PATCH 676/760] appnexusBidAdapter - video plcmt logic fix (#11403) --- modules/appnexusBidAdapter.js | 2 +- test/spec/modules/appnexusBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index bb80b6a8588..551bf5401e2 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -1030,7 +1030,7 @@ function getContextFromPlcmt(ortbPlcmt, ortbStartDelay) { } if (ortbPlcmt === 2) { - if (!ortbStartDelay) { + if (typeof ortbStartDelay === 'undefined') { return; } if (ortbStartDelay === 0) { diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 2a277cf1ba7..c2da2f36223 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -411,7 +411,7 @@ describe('AppNexusAdapter', function () { playerSize: [640, 480], context: 'outstream', plcmt: 2, - startdelay: -1, + startdelay: 0, mimes: ['video/mp4'], skip: 1, minduration: 5, @@ -427,7 +427,7 @@ describe('AppNexusAdapter', function () { minduration: 5, playback_method: 2, skippable: true, - context: 9 + context: 8 }); expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); From 7b77515a69b61bee1019f47ebdb93b4164d0a36c Mon Sep 17 00:00:00 2001 From: samuel-palmer-relevant-digital <77437973+samuel-palmer-relevant-digital@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:52:20 +0200 Subject: [PATCH 677/760] Remove 'transformBidParams' from relevantdigitalBidAdapter (#11412) --- modules/relevantdigitalBidAdapter.js | 18 ----- .../modules/relevantdigitalBidAdapter_spec.js | 78 +------------------ 2 files changed, 1 insertion(+), 95 deletions(-) diff --git a/modules/relevantdigitalBidAdapter.js b/modules/relevantdigitalBidAdapter.js index 8d1265075f9..c776022749d 100644 --- a/modules/relevantdigitalBidAdapter.js +++ b/modules/relevantdigitalBidAdapter.js @@ -200,24 +200,6 @@ export const spec = { }); return syncs; }, - - /** If server side, transform bid params if needed */ - transformBidParams(params, isOrtb, adUnit, bidRequests) { - if (!params.placementId) { - return; - } - const bid = bidRequests.flatMap(req => req.adUnitsS2SCopy || []).flatMap((adUnit) => adUnit.bids).find((bid) => bid.params?.placementId === params.placementId); - if (!bid) { - return; - } - const cfg = getBidderConfig([bid]); - FIELDS.forEach(({ name }) => { - if (cfg[name] && !params[name]) { - params[name] = cfg[name]; - } - }); - return params; - }, }; registerBidder(spec); diff --git a/test/spec/modules/relevantdigitalBidAdapter_spec.js b/test/spec/modules/relevantdigitalBidAdapter_spec.js index 1575c5c6b94..45a84d5991d 100644 --- a/test/spec/modules/relevantdigitalBidAdapter_spec.js +++ b/test/spec/modules/relevantdigitalBidAdapter_spec.js @@ -1,10 +1,5 @@ import {spec, resetBidderConfigs} from 'modules/relevantdigitalBidAdapter.js'; -import { parseUrl, deepClone } from 'src/utils.js'; -import { config } from 'src/config.js'; -import { S2S } from 'src/constants.js'; - -import adapterManager, { -} from 'src/adapterManager.js'; +import { parseUrl } from 'src/utils.js'; const expect = require('chai').expect; @@ -13,17 +8,6 @@ const PLACEMENT_ID = 'example_placement_id'; const ACCOUNT_ID = 'example_account_id'; const TEST_DOMAIN = 'example.com'; const TEST_PAGE = `https://${TEST_DOMAIN}/page.html`; - -const CONFIG = { - enabled: true, - endpoint: S2S.DEFAULT_ENDPOINT, - timeout: 1000, - maxBids: 1, - adapter: 'prebidServer', - bidders: ['relevantdigital'], - accountId: 'abc' -}; - const ADUNIT_CODE = '/19968336/header-bid-tag-0'; const BID_PARAMS = { @@ -312,64 +296,4 @@ describe('Relevant Digital Bid Adaper', function () { expect(allSyncs).to.deep.equal(expectedResult) }); }); - describe('transformBidParams', function () { - beforeEach(() => { - config.setConfig({ - s2sConfig: CONFIG, - }); - }); - afterEach(() => { - config.resetConfig(); - }); - - const adUnit = (params) => ({ - code: ADUNIT_CODE, - bids: [ - { - bidder: 'relevantdigital', - adUnitCode: ADUNIT_CODE, - params, - } - ] - }); - - const request = (params) => adapterManager.makeBidRequests([adUnit(params)], 123, 'auction-id', 123, [], {})[0]; - - it('transforms adunit bid params and config params correctly', function () { - config.setConfig({ - relevantdigital: { - pbsHost: PBS_HOST, - accountId: ACCOUNT_ID, - }, - }); - const adUnitParams = { placementId: PLACEMENT_ID }; - const expextedTransformedBidParams = { - ...BID_PARAMS.params, pbsHost: `https://${BID_PARAMS.params.pbsHost}`, 'pbsBufferMs': 250 - }; - expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); - }); - it('transforms adunit bid params correctly', function () { - const adUnitParams = { ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 }; - const expextedTransformedBidParams = { - ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 - }; - expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); - }); - it('transforms adunit bid params correctly', function () { - const adUnitParams = { ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 }; - const expextedTransformedBidParams = { - ...BID_PARAMS.params, pbsHost: 'host.relevant-digital.com', pbsBufferMs: 500 - }; - expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.deep.equal(expextedTransformedBidParams); - }); - it('does not transform bid params if placementId is missing', function () { - const adUnitParams = { ...BID_PARAMS.params, placementId: null }; - expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.equal(undefined); - }); - it('does not transform bid params s2s config is missing', function () { - config.resetConfig(); - const adUnitParams = BID_PARAMS.params; - expect(spec.transformBidParams(adUnitParams, null, null, [request(adUnitParams)])).to.equal(undefined); - }); - }) }); From 428ae3bc61044ab23f1553d02ec35351d2914553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Mon, 29 Apr 2024 20:35:49 +0200 Subject: [PATCH 678/760] Criteo Bid Adapter: fix issue where bidder conf pubid param is not sent in the proper field (#11416) --- modules/criteoBidAdapter.js | 9 ++++++--- test/spec/modules/criteoBidAdapter_spec.js | 13 +++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index defe2c081e3..faaa7883b42 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -621,9 +621,6 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (networkId) { request.publisher.networkid = networkId; } - if (pubid) { - request.publisher.id = pubid; - } request.source = { tid: bidderRequest.ortb2?.source?.tid @@ -637,6 +634,12 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { request.user = bidderRequest.ortb2?.user || {}; request.site = bidderRequest.ortb2?.site || {}; request.app = bidderRequest.ortb2?.app || {}; + + if (pubid) { + request.site.publisher = {...request.site.publisher, ...{ id: pubid }}; + request.app.publisher = {...request.app.publisher, ...{ id: pubid }}; + } + request.device = bidderRequest.ortb2?.device || {}; if (bidderRequest && bidderRequest.ceh) { request.user.ceh = bidderRequest.ceh; diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index c460b451193..f0fc9dadda8 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -2009,7 +2009,15 @@ describe('The Criteo bidding adapter', function () { }); it('should properly transmit the pubid and slot uid if available', function () { - const bidderRequest = {}; + const bidderRequest = { + ortb2: { + site: { + publisher: { + id: 'pub-777' + } + } + } + }; const bidRequests = [ { bidder: 'criteo', @@ -2050,7 +2058,8 @@ describe('The Criteo bidding adapter', function () { ]; const request = spec.buildRequests(bidRequests, bidderRequest); const ortbRequest = request.data; - expect(ortbRequest.publisher.id).to.equal('pub-888'); + expect(ortbRequest.publisher.id).to.be.undefined; + expect(ortbRequest.site.publisher.id).to.equal('pub-888'); expect(request.data.slots[0].ext.bidder).to.be.undefined; expect(request.data.slots[1].ext.bidder.uid).to.equal(888); }); From ee509d362a3adda6fd3d5e040eb549bd4b6d9c95 Mon Sep 17 00:00:00 2001 From: pm-azhar-mulla <75726247+pm-azhar-mulla@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:29:03 +0530 Subject: [PATCH 679/760] Using origbidid instead of piid (#11415) Co-authored-by: pm-azhar-mulla --- modules/pubmaticAnalyticsAdapter.js | 4 +- .../modules/pubmaticAnalyticsAdapter_spec.js | 66 ++++++++++++++++--- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 753b706e363..9e1fa49fef2 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -294,6 +294,7 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, 'bidid': bid.bidId || bidId, + 'origbidid': bid?.bidResponse?.partnerImpId || bid?.bidResponse?.prebidBidId || bid.bidId || bidId, 'db': bid.bidResponse ? 0 : 1, 'kgpv': getValueForKgpv(bid, adUnitId), 'kgpsv': bid.params && bid.params.kgpv ? bid.params.kgpv : adUnitId, @@ -313,7 +314,6 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { 'af': bid.bidResponse ? (bid.bidResponse.mediaType || undefined) : undefined, 'ocpm': bid.bidResponse ? (bid.bidResponse.originalCpm || 0) : 0, '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, 'pb': pg || undefined @@ -487,7 +487,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&en=' + enc(winningBid.bidResponse.bidPriceUSD); pixelURL += '&eg=' + enc(winningBid.bidResponse.bidGrossCpmUSD); pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); - pixelURL += '&piid=' + enc(winningBid.bidResponse.partnerImpId || EMPTY_STRING); + pixelURL += '&origbidid=' + enc(winningBid?.bidResponse?.partnerImpId || winningBid?.bidResponse?.prebidBidId || winningBid.bidId); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); pixelURL += '&pb=' + enc(pg); diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index cb13a4db234..002b7fb3063 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -585,7 +585,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); - expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); expect(data.s[0].ps[0].db).to.equal(0); expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].kgpsv).to.equal('/19968336/header-bid-tag-0'); @@ -618,7 +618,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); - expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); @@ -659,7 +659,7 @@ describe('pubmatic analytics adapter', function () { expect(data.bc).to.equal('pubmatic'); expect(data.eg).to.equal('1.23'); expect(data.en).to.equal('1.23'); - expect(data.piid).to.equal('partnerImpressionID-1'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); expect(data.plt).to.equal('1'); expect(data.psz).to.equal('640x480'); expect(data.tgid).to.equal('15'); @@ -1372,7 +1372,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); - expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); @@ -1453,7 +1453,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('pubmatic_alias'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); - expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); expect(data.s[0].ps[0].db).to.equal(0); expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].kgpsv).to.equal('/19968336/header-bid-tag-0'); @@ -1487,7 +1487,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); - expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); @@ -1528,7 +1528,7 @@ describe('pubmatic analytics adapter', function () { expect(data.bc).to.equal('pubmatic_alias'); expect(data.eg).to.equal('1.23'); expect(data.en).to.equal('1.23'); - expect(data.piid).to.equal('partnerImpressionID-1'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); }); it('Logger: best case + win tracker in case of GroupM as alternate bidder', function() { @@ -1585,7 +1585,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[0].ps[0].pn).to.equal('pubmatic'); expect(data.s[0].ps[0].bc).to.equal('groupm'); expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); - expect(data.s[0].ps[0].piid).to.equal('partnerImpressionID-1'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); expect(data.s[0].ps[0].db).to.equal(0); expect(data.s[0].ps[0].kgpv).to.equal('/19968336/header-bid-tag-0'); expect(data.s[0].ps[0].kgpsv).to.equal('/19968336/header-bid-tag-0'); @@ -1615,7 +1615,7 @@ describe('pubmatic analytics adapter', function () { expect(data.s[1].ps[0].pn).to.equal('pubmatic'); expect(data.s[1].ps[0].bc).to.equal('pubmatic'); expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); - expect(data.s[1].ps[0].piid).to.equal('partnerImpressionID-2'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); expect(data.s[1].ps[0].db).to.equal(0); expect(data.s[1].ps[0].kgpv).to.equal('this-is-a-kgpv'); expect(data.s[1].ps[0].kgpsv).to.equal('this-is-a-kgpv'); @@ -1654,7 +1654,53 @@ describe('pubmatic analytics adapter', function () { expect(data.bc).to.equal('groupm'); expect(data.eg).to.equal('1.23'); expect(data.en).to.equal('1.23'); - expect(data.piid).to.equal('partnerImpressionID-1'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); + }); + + it('Logger: best case + win tracker. Log bidId when partnerimpressionid is missing', function() { + delete MOCK.BID_RESPONSE[1]['partnerImpId']; + MOCK.BID_RESPONSE[1]['prebidBidId'] = 'Prebid-bid-id-1'; + sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { + return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] + }); + + config.setConfig({ + testGroupId: 15 + }); + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[0]); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE[1]); + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + events.emit(SET_TARGETING, MOCK.SET_TARGETING); + events.emit(BID_WON, MOCK.BID_WON[0]); + events.emit(BID_WON, MOCK.BID_WON[1]); + + clock.tick(2000 + 1000); + expect(requests.length).to.equal(3); // 1 logger and 2 win-tracker + let request = requests[2]; // logger is executed late, trackers execute first + expect(request.url).to.equal('https://t.pubmatic.com/wl?pubid=9999'); + let data = getLoggerJsonFromRequest(request.requestBody); + expect(data.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + + // slot 1 + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); + + // slot 2 + expect(data.s[1].ps[0].bidid).to.equal('3bd4ebb1c900e2'); + expect(data.s[1].ps[0].origbidid).to.equal('3bd4ebb1c900e2'); + + // tracker slot1 + let firstTracker = requests[0].url; + expect(firstTracker.split('?')[0]).to.equal('https://t.pubmatic.com/wt'); + data = {}; + firstTracker.split('?')[1].split('&').map(e => e.split('=')).forEach(e => data[e[0]] = e[1]); + expect(data.bidid).to.equal('2ecff0db240757'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); }); }); From b38c0a6b8b54ba02f8d724c1f59acfa8770303ea Mon Sep 17 00:00:00 2001 From: redaguermas Date: Tue, 30 Apr 2024 05:05:26 -0700 Subject: [PATCH 680/760] NoBid Bid Adapter : update version 1.3.4 (#11420) * Enable supplyChain support * Added support for COPPA * rebuilt * Added support for Extended User IDs. * Added support for the "meta" attribute in bid response. * Delete nobidBidAdapter.js.orig * Delete a * Delete .jsdtscope * Delete org.eclipse.wst.jsdt.ui.superType.container * Delete org.eclipse.wst.jsdt.ui.superType.name * Delete .project * Renamed the Analytics Adapter to "nobid" otherwise it wouldn't work with the wrapper builder on the Prebid.js website and added Prebid version "pbver" to the bidding and analytics requests. --------- Co-authored-by: Reda Guermas --- modules/nobidAnalyticsAdapter.js | 6 +++--- modules/nobidBidAdapter.js | 6 ++++-- test/spec/modules/nobidAnalyticsAdapter_spec.js | 1 + test/spec/modules/nobidBidAdapter_spec.js | 3 +-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index 5ae77f0cdc8..a0aa5ee4989 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -6,7 +6,7 @@ import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; -const VERSION = '2.0.1'; +const VERSION = '2.0.2'; const MODULE_NAME = 'nobidAnalyticsAdapter'; const ANALYTICS_OPT_FLUSH_TIMEOUT_SECONDS = 5 * 1000; const RETENTION_SECONDS = 1 * 24 * 3600; @@ -53,6 +53,7 @@ function sendEvent (event, eventType) { } try { event.version = VERSION; + event.pbver = '$prebid.version$'; const endpoint = `${resolveEndpoint()}/event/${eventType}?pubid=${nobidAnalytics.initOptions.siteId}`; ajax(endpoint, function (response) { @@ -175,10 +176,9 @@ nobidAnalytics = { ANALYTICS_DATA_NAME: 'analytics.nobid.io', ANALYTICS_OPT_NAME: 'analytics.nobid.io.optData' } - adapterManager.registerAnalyticsAdapter({ adapter: nobidAnalytics, - code: 'nobidAnalytics', + code: 'nobid', gvlid: GVLID }); nobidAnalytics.originalAdUnits = {}; diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 28fb38e14e5..77e5a1c6fed 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -17,7 +17,7 @@ import { hasPurpose1Consent } from '../src/utils/gpdr.js'; const GVLID = 816; const BIDDER_CODE = 'nobid'; const storage = getStorageManager({bidderCode: BIDDER_CODE}); -window.nobidVersion = '1.3.3'; +window.nobidVersion = '1.3.4'; window.nobid = window.nobid || {}; window.nobid.bidResponses = window.nobid.bidResponses || {}; window.nobid.timeoutTotal = 0; @@ -161,6 +161,7 @@ function nobidBuildRequests(bids, bidderRequest) { state['gdpr'] = gdprConsent(bidderRequest); state['usp'] = uspConsent(bidderRequest); state['pjbdr'] = (bidderRequest && bidderRequest.bidderCode) ? bidderRequest.bidderCode : 'nobid'; + state['pbver'] = '$prebid.version$'; const sch = schain(bids); if (sch) state['schain'] = sch; const cop = coppa(); @@ -383,7 +384,8 @@ export const spec = { */ isBidRequestValid: function(bid) { log('isBidRequestValid', bid); - return !!bid.params.siteId; + if (bid?.params?.siteId) return true; + return false; }, /** * Make a server request from the list of BidRequests. diff --git a/test/spec/modules/nobidAnalyticsAdapter_spec.js b/test/spec/modules/nobidAnalyticsAdapter_spec.js index 07823d3536f..81b78dc14d6 100644 --- a/test/spec/modules/nobidAnalyticsAdapter_spec.js +++ b/test/spec/modules/nobidAnalyticsAdapter_spec.js @@ -399,6 +399,7 @@ describe('NoBid Prebid Analytic', function () { expect(server.requests).to.have.length(1); const auctionEndRequest = JSON.parse(server.requests[0].requestBody); expect(auctionEndRequest).to.have.property('version', nobidAnalyticsVersion); + expect(auctionEndRequest).to.have.property('pbver', '$prebid.version$'); expect(auctionEndRequest).to.have.property('auctionId', expectedOutgoingRequest.auctionId); expect(auctionEndRequest.bidderRequests).to.have.length(1); expect(auctionEndRequest.bidderRequests[0].bidderCode).to.equal(expectedOutgoingRequest.bidderRequests[0].bidderCode); diff --git a/test/spec/modules/nobidBidAdapter_spec.js b/test/spec/modules/nobidBidAdapter_spec.js index b1e303bde6e..2f2c97eae51 100644 --- a/test/spec/modules/nobidBidAdapter_spec.js +++ b/test/spec/modules/nobidBidAdapter_spec.js @@ -62,7 +62,6 @@ describe('Nobid Adapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); delete bid.params; bid.params = { 'siteId': 2 @@ -72,7 +71,6 @@ describe('Nobid Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); delete bid.params; bid.params = { 'siteId': 0 @@ -399,6 +397,7 @@ describe('Nobid Adapter', function () { const payload = JSON.parse(request.data); expect(payload.sid).to.equal(SITE_ID); expect(payload.pjbdr).to.equal('nobid'); + expect(payload.pbver).to.equal('$prebid.version$'); expect(payload.l).to.exist.and.to.equal(encodeURIComponent(REFERER)); expect(payload.a).to.exist; expect(payload.t).to.exist; From 51532c683630642150fd12e6c25a9ffb104af002 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Tue, 30 Apr 2024 16:16:13 +0400 Subject: [PATCH 681/760] Limelight Digital Bid Adapter: add sua field (#11422) --- modules/limelightDigitalBidAdapter.js | 7 ++++--- .../limelightDigitalBidAdapter_spec.js | 21 +++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 5cccf5300b3..acc6876b822 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -62,7 +62,7 @@ export const spec = { } const placements = groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host') return Object.keys(placements) - .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit))); + .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit), bidderRequest)); }, /** @@ -119,7 +119,7 @@ export const spec = { registerBidder(spec); -function buildRequest(winTop, host, adUnits) { +function buildRequest(winTop, host, adUnits, bidderRequest) { return { method: 'POST', url: `https://${host}/hb`, @@ -127,7 +127,8 @@ function buildRequest(winTop, host, adUnits) { secure: (location.protocol === 'https:'), deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, - adUnits: adUnits + adUnits: adUnits, + sua: bidderRequest?.ortb2?.device?.sua } } } diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 6348d6a1ac6..9e8a00959d4 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -208,7 +208,19 @@ describe('limelightDigitalAdapter', function () { } describe('buildRequests', function () { - const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4]) + const bidderRequest = { + ortb2: { + device: { + sua: { + browsers: [], + platform: [], + mobile: 1, + architecture: 'arm' + } + } + } + } + const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4], bidderRequest) it('Creates two ServerRequests', function() { expect(serverRequests).to.exist expect(serverRequests).to.have.lengthOf(2) @@ -230,7 +242,8 @@ describe('limelightDigitalAdapter', function () { 'deviceWidth', 'deviceHeight', 'secure', - 'adUnits' + 'adUnits', + 'sua' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -266,6 +279,10 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom5).to.be.a('string'); expect(adUnit.page).to.be.a('string'); }) + expect(data.sua.browsers).to.be.a('array'); + expect(data.sua.platform).to.be.a('array'); + expect(data.sua.mobile).to.be.a('number'); + expect(data.sua.architecture).to.be.a('string'); }) }) it('Returns valid URL', function () { From a6aba0ef79c92fdbb08419959d96cbf739d5749a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9onard=20Labat?= Date: Tue, 30 Apr 2024 15:47:33 +0200 Subject: [PATCH 682/760] Criteo Bid Adapter: Add missing tmax along the request send to backend (#11423) --- modules/criteoBidAdapter.js | 1 + test/spec/modules/criteoBidAdapter_spec.js | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index faaa7883b42..c95cbf7af73 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -674,6 +674,7 @@ function buildCdbRequest(context, bidRequests, bidderRequest) { if (bidderRequest && bidderRequest.ortb2?.bapp) { request.bapp = bidderRequest.ortb2.bapp; } + request.tmax = bidderRequest.timeout; return request; } diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index f0fc9dadda8..def35b13955 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -675,6 +675,25 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.source.tid).to.equal('abc'); }); + it('should properly transmit tmax if available', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + transactionId: 'transaction-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); + }); + it('should properly transmit bidId if available', function () { const bidderRequest = { ortb2: { From 14ef0c15f989049c00d81980a0ada247fe991575 Mon Sep 17 00:00:00 2001 From: "yuu.t" Date: Tue, 30 Apr 2024 17:38:34 +0200 Subject: [PATCH 683/760] yieldlabBidAdapter support topics (#11421) --- modules/yieldlabBidAdapter.js | 27 +++++++- test/spec/modules/yieldlabBidAdapter_spec.js | 70 ++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 13910d688e8..23ff94f62a1 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, deepAccess, isArray, isFn, isPlainObject, timestamp } from '../src/utils.js'; +import { _each, deepAccess, isArray, isEmptyStr, isFn, isPlainObject, timestamp } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { find } from '../src/polyfill.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -130,6 +130,13 @@ export const spec = { } } } + + const topics = getGoogleTopics(bidderRequest); + if (topics) { + assignIfNotUndefined(query, 'segtax', topics.segtax); + assignIfNotUndefined(query, 'segclass', topics.segclass); + assignIfNotUndefined(query, 'segments', topics.segments); + } } const adslots = adslotIds.join(','); @@ -607,4 +614,22 @@ function assignIfNotUndefined(obj, key, value) { } } +function getGoogleTopics(bid) { + const userData = deepAccess(bid, 'ortb2.user.data') || []; + const validData = userData.filter(dataObj => + dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0 && + dataObj.segment.every(seg => (seg.id && !isEmptyStr(seg.id) && isFinite(seg.id))) + )[0]; + + if (validData) { + return { + segtax: validData.ext?.segtax, + segclass: validData.ext?.segclass, + segments: validData.segment.map(seg => Number(seg.id)).join(','), + }; + } + + return undefined; +} + registerBidder(spec); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 751dff4fe33..225186dd326 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -585,6 +585,76 @@ describe('yieldlabBidAdapter', () => { expect(request.url).to.not.include('dsaparams'); }); }); + + describe('google topics handling', () => { + afterEach(() => { + config.resetConfig(); + }); + + it('does pass segtax, segclass, segments for google topics data', () => { + const GOOGLE_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + {id: '717'}, {id: '808'}, + ] + } + ] + }, + }, + } + config.setConfig(GOOGLE_TOPICS_DATA); + const request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...GOOGLE_TOPICS_DATA }); + expect(request.url).to.include('segtax=600&segclass=v1&segments=717%2C808'); + }); + + it('does not pass topics params for invalid topics data', () => { + const INVALID_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + segment: [] + }, + { + segment: [{id: ''}] + }, + { + segment: [{id: null}] + }, + { + segment: [{id: 'dummy'}, {id: '123'}] + }, + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + name: 'dummy' + } + ] + }, + ] + } + } + }; + + config.setConfig(INVALID_TOPICS_DATA); + let request = spec.buildRequests([DEFAULT_REQUEST()], { ...REQPARAMS, ...INVALID_TOPICS_DATA }); + + expect(request.url).to.not.include('segtax'); + expect(request.url).to.not.include('segclass'); + expect(request.url).to.not.include('segments'); + }); + }); }); describe('interpretResponse', () => { From ecb92186a8b48ad8b57c2d4d869dba55c45504f1 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Tue, 30 Apr 2024 12:13:53 -0400 Subject: [PATCH 684/760] Kargo Bid Adapter: test spec overhaul (#11417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * KRKPD-1083: Refactor and expand testing (#31) * Refactor the Kargo adapter testing to be more comprehensive and include testing of invalid inputs * Fix the window size test to pull data from the browser instead of hardcoded values * Remove a duplicate test * unhook triggerPixel after tests * fixes circleci failure * Update the localStorage non-functional test In FireFox, having localStorage.getItem throw an error isn't working so the test was updated to not have a valid localStorage value to pass on FireFox. * Restore the bidderSettings following testing --------- Co-authored-by: William Klausmeyer Co-authored-by: “Nick <“nick.llerandi”@kargo.com> Co-authored-by: William Klausmeyer --- test/spec/modules/kargoBidAdapter_spec.js | 2659 +++++++++++++-------- 1 file changed, 1725 insertions(+), 934 deletions(-) diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index eb8f310201d..7aa853ad902 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1,461 +1,1485 @@ -import { expect, assert } from 'chai'; +import { expect } from 'chai'; import { spec } from 'modules/kargoBidAdapter.js'; import { config } from 'src/config.js'; const utils = require('src/utils'); -describe('kargo adapter tests', function () { - var sandbox, clock, frozenNow = new Date(); - const testSchain = { - complete: 1, - nodes: [ +describe('kargo adapter tests', function() { + let bid, outstreamBid, testBids, sandbox, clock, frozenNow = new Date(), oldBidderSettings; + + const topUrl = 'https://random.com/this/is/a/url'; + const domain = 'random.com'; + const referer = 'https://random.com/'; + + const defaultBidParams = Object.freeze({ + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + getFloor: () => {}, + ortb2: { + device: { + w: 1720, + h: 1000, + dnt: 0, + language: 'en', + ua: 'Mozilla/5.0' + }, + site: { + domain: domain, + mobile: 0, + page: topUrl, + publisher: { + domain: domain + }, + ref: referer + }, + source: { + tid: 'random-tid' + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: '/1234/prebid/slot/path' + }, + gpid: '/1234/prebid/slot/path', + } + }, + userId: { + tdid: 'random-tdid' + }, + userIdAsEids: [ + { + source: 'adquery.io', + uids: [ { + id: 'adquery-id', + atype: 1 + } ] + }, + { + source: 'criteo.com', + uids: [ { + id: 'criteo-id', + atype: 1 + } ] + }, { - 'asi': 'test-page.com', - 'hp': 1, - 'rid': '57bdd953-6e57-4d5b-9351-ed67ca238890', - 'sid': '8190248274' + source: 'adserver.org', + uids: [ { + id: 'adserver-id', + atype: 1, + ext: { rtiPartner: 'TDID' } + } ] + }, + ], + floorData: { + floorMin: 1 + }, + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ { + asi: 'indirectseller.com', + sid: '00001', + hp: 1, + } ] + } + }, + }); + + const minimumBidParams = Object.freeze({ + params: { + placementId: 'foobar' + }, + mediaTypes: { + banner: { sizes: [ [1, 1] ] } + } + }); + + const validCrbIds = { + 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', + 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', + '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + }; + const validCrbIdsLs = { + 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', + 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', + 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', + '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', + '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + }; + function buildCrbValue(isCookie, withIds, withTdid, withLexId, withClientId, optOut) { + let value = { + expireTime: Date.now() + 60000, + lastSyncedAt: Date.now() - 60000, + optOut, + }; + const locationString = isCookie ? 'cookie' : 'localstorage'; + + if (withIds) { + if (isCookie) { + value.syncIds = validCrbIds; + } else { + value.syncIds = validCrbIdsLs; } - ] + } + if (withTdid) { + value.tdID = `test-tdid-cerberus-${locationString}`; + } + if (withLexId) { + value.lexId = `test-lexid-cerberus-${locationString}`; + } + if (withClientId) { + value.clientId = `test-clientid-cerberus-${locationString}`; + } + + const b64Value = btoa(JSON.stringify(value)); + if (isCookie) { + return JSON.stringify({ v: b64Value }); + } + return b64Value; } - beforeEach(function () { + beforeEach(function() { + oldBidderSettings = $$PREBID_GLOBAL$$.bidderSettings; + $$PREBID_GLOBAL$$.bidderSettings = { + kargo: { storageAllowed: true } + }; + + bid = { + ...defaultBidParams, + bidder: 'kargo', + params: { + placementId: 'displayPlacement' + }, + mediaTypes: { + banner: { + sizes: [ [970, 250], [1, 1] ] + } + }, + adUnitCode: 'displayAdunitCode', + sizes: [ [300, 250], [300, 600] ], + bidId: 'randomBidId', + bidderRequestId: 'randomBidderRequestId', + auctionId: 'randomAuctionId' + }; + + outstreamBid = { + ...defaultBidParams, + bidder: 'kargo', + params: { + placementId: 'instreamPlacement' + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + api: [ 1, 2 ], + linearity: 1, + maxduration: 60, + mimes: [ 'video/mp4', 'video/webm', 'application/javascript' ], + minduration: 0, + placement: 1, + playbackmethod: [ 1, 2, 3 ], + plcmt: 1, + protocols: [ 2, 3, 5 ], + skip: 1 + } + }, + adUnitCode: 'instreamAdunitCode', + sizes: [ [300, 250], [300, 600] ], + bidId: 'randomBidId2', + bidderRequestId: 'randomBidderRequestId2', + auctionId: 'randomAuctionId2' + }; + + testBids = [{ ...minimumBidParams }]; + sandbox = sinon.sandbox.create(); clock = sinon.useFakeTimers(frozenNow.getTime()); }); - afterEach(function () { + afterEach(function() { sandbox.restore(); clock.restore(); + $$PREBID_GLOBAL$$.bidderSettings = oldBidderSettings; }); - describe('bid request validity', function () { - it('passes when the bid includes a placement ID', function () { - assert(spec.isBidRequestValid({ params: { placementId: 'foo' } }) === true); + describe('gvlid', function() { + it('exposes the gvlid (global vendor list ID) of 972', function() { + expect(spec.gvlid).to.exist.and.equal(972); + }); + }); + + describe('code', function() { + it('exposes the code kargo', function() { + expect(spec.code).to.exist.and.equal('kargo'); + }); + }); + + describe('isBidRequestValid', function() { + it('fails when the bid object is undefined', function() { + expect(spec.isBidRequestValid()).to.equal(false); }); - it('fails when the bid does not include a placement ID', function () { - assert(spec.isBidRequestValid({ params: {} }) === false); + it('fails when the bid object has no keys at all', function() { + expect(spec.isBidRequestValid({})).to.equal(false); }); - it('fails when bid is falsey', function () { - assert(spec.isBidRequestValid() === false); + it('fails when the bid includes params but not a placement ID', function() { + expect(spec.isBidRequestValid({ params: {} })).to.equal(false); }); - it('fails when the bid has no params at all', function () { - assert(spec.isBidRequestValid({}) === false); + it('passes when the bid includes a placement ID', function () { + expect(spec.isBidRequestValid({ params: { placementId: 'foo' } })).to.equal(true); + }); + + it('passes when the bid includes a placement ID and other keys', function() { + expect(spec.isBidRequestValid({ params: { placementId: 'foo', other: 'value' } })).to.equal(true); + }); + + it('passes when the full bid information is provided', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(outstreamBid)).to.equal(true); }); }); - describe('build request', function () { - var bids, undefinedCurrency, noAdServerCurrency, nonUSDAdServerCurrency, cookies = [], localStorageItems = [], sessionIds = [], requestCount = 0; + describe('buildRequests', function() { + let bids, + bidderRequest, + undefinedCurrency, + noAdServerCurrency, + nonUSDAdServerCurrency, + cookies = [], + localStorageItems = [], + session_id = null; + + before(function() { + sinon.spy(spec, 'buildRequests'); + }); - beforeEach(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - kargo: { - storageAllowed: true - } - }; + beforeEach(function() { undefinedCurrency = false; noAdServerCurrency = false; nonUSDAdServerCurrency = false; sandbox.stub(config, 'getConfig').callsFake(function (key) { if (key === 'currency') { - if (undefinedCurrency) { - return undefined; - } - if (noAdServerCurrency) { - return {}; - } - if (nonUSDAdServerCurrency) { - return { adServerCurrency: 'EUR' }; - } + if (undefinedCurrency) return undefined; + + if (noAdServerCurrency) return {}; + + if (nonUSDAdServerCurrency) return { adServerCurrency: 'EUR' }; + return { adServerCurrency: 'USD' }; } + if (key === 'debug') return true; if (key === 'deviceAccess') return true; - throw new Error(`Config stub incomplete! Missing key "${key}"`) + throw new Error(`Config stub incomplete, missing key "${key}"`); }); bids = [ - { - params: { - placementId: 'foo', - socialCanvas: { - segments: ['segment_1', 'segment_2', 'segment_3'], - url: 'https://socan.url' - } - }, - auctionId: '1234098', - bidId: '1', - adUnitCode: '101', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - banner: { - sizes: [[320, 50], [300, 50]] - } - }, - bidRequestsCount: 1, - bidderRequestsCount: 2, - bidderWinsCount: 3, - schain: testSchain, - userId: { - tdid: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c' + bid, + outstreamBid + ]; + + bidderRequest = { + bidderCode: 'kargo', + auctionId: 'test-auction-id', + bidderRequestId: 'test-request-id', + bids, + ortb2: defaultBidParams.ortb2, + refererInfo: { + canonicalUrl: topUrl, + domain, + isAmp: false, + location: topUrl, + numIframs: 0, + page: topUrl, + reachedTop: true, + ref: referer, + stack: [ topUrl ], + topmostLocation: topUrl, + }, + start: Date.now(), + timeout: 2500, + }; + }); + afterEach(function() { + cookies.forEach(key => document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/`); + cookies = []; + + localStorageItems.forEach(key => localStorage.removeItem(key)); + localStorageItems = []; + }); + + function getPayloadFromTestBids(testBids) { + const request = spec.buildRequests(testBids, { ...bidderRequest, bids: testBids }); + const payload = request.data; + if (session_id === null) session_id = spec._getSessionId(); + + expect(payload).to.exist; + expect(payload.imp).to.have.length(testBids.length); + expect(payload.requestCount).to.equal(spec.buildRequests.callCount - 1); + expect(payload.sid).to.equal(session_id); + + return payload; + } + + function setCookieValue(name, value) { + cookies.push(name); + document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; max-age-in-seconds=1000; path=/`; + } + + function setLocalStorageValue(name, value) { + localStorageItems.push(name); + localStorage.setItem(name, value); + } + + const crbValues = { + valid: buildCrbValue(true, true, true, true, true, false), + validLs: buildCrbValue(false, true, true, true, true, false), + invalidB64: 'crbValue', + invalidB64Ls: 'crbValueLs', + invalidJson: 'eyJ0ZXN0OiJ2YWx1ZSJ9', + invalidJsonLs: 'eyJ0ZXN0OiJ2YWx1ZSJ9', + }; + function setCrb( + setCookie = 'valid', + setLocalStorage = 'valid' + ) { + if (crbValues.hasOwnProperty(setCookie)) { + setCookieValue('krg_crb', crbValues[setCookie]); + } + if (crbValues.hasOwnProperty(`${setLocalStorage}Ls`)) { + setLocalStorageValue('krg_crb', crbValues[`${setLocalStorage}Ls`]); + } + } + + it('exists and produces an object', function() { + const request = spec.buildRequests(bids, bidderRequest); + expect(request).to.exist.and.to.be.an('object'); + }); + + it('produces a POST request with a payload', function() { + const request = spec.buildRequests(bids, bidderRequest); + expect(request.method).to.exist.and.equal('POST'); + expect(request.url).to.exist.and.equal('https://krk2.kargo.com/api/v1/prebid'); + + const payload = request.data; + expect(payload).to.exist; + expect(payload.imp).to.have.length(2); + + // Display bid + const bidImp = payload.imp[0]; + expect(bidImp.id).to.equal('randomBidId'); + expect(bidImp.banner).to.deep.equal({ sizes: [ [970, 250], [1, 1] ] }); + expect(bidImp.video).to.be.undefined; + expect(bidImp.bidRequestCount).to.equal(1); + expect(bidImp.bidderRequestCount).to.equal(1); + expect(bidImp.code).to.equal('displayAdunitCode'); + expect(bidImp.ext.ortb2Imp).to.deep.equal(defaultBidParams.ortb2Imp); + expect(bidImp.fpd).to.deep.equal({ gpid: '/1234/prebid/slot/path' }); + expect(bidImp.pid).to.equal('displayPlacement'); + + // Video bid + const videoBidImp = payload.imp[1]; + expect(videoBidImp.id).to.equal('randomBidId2'); + expect(videoBidImp.banner).to.be.undefined; + expect(videoBidImp.video).to.deep.equal(outstreamBid.mediaTypes.video); + expect(videoBidImp.bidRequestCount).to.equal(1); + expect(videoBidImp.bidderRequestCount).to.equal(1); + expect(videoBidImp.code).to.equal('instreamAdunitCode'); + expect(videoBidImp.ext.ortb2Imp).to.deep.equal(defaultBidParams.ortb2Imp); + expect(videoBidImp.fpd).to.deep.equal({ gpid: '/1234/prebid/slot/path' }); + expect(videoBidImp.pid).to.equal('instreamPlacement'); + + // User + expect(payload.user).to.be.an('object'); + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.data).to.deep.equal([]); + expect(payload.user.sharedIDEids).to.deep.equal(defaultBidParams.userIdAsEids); + + // General keys + expect(payload.aid).to.equal('randomAuctionId'); + expect(payload.device).to.deep.equal({ size: [ window.screen.width, window.screen.height ] }); + expect(payload.ext.ortb2).to.deep.equal(defaultBidParams.ortb2); + expect(payload.pbv).to.equal('$prebid.version$'); + expect(payload.requestCount).to.equal(spec.buildRequests.callCount - 1); + expect(payload.sid).to.be.a('string').with.length(36); + expect(payload.timeout).to.equal(2500); + expect(payload.url).to.equal(topUrl); + expect(payload.ts).to.be.a('number'); + }); + + it('copies the ortb2 object from the first bid request if present', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { + user: { + key: 'value' + } + } + }]); + expect(payload.ext).to.deep.equal({ ortb2: { + user: { key: 'value' } + }}); + + payload = getPayloadFromTestBids(testBids); + expect(payload.ext).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { + user: { + key: 'value' + } + } + }, { + ...minimumBidParams, + ortb2: { + site: { + key2: 'value2' + } + } + }]); + expect(payload.ext).to.deep.equal({ortb2: { + user: { key: 'value' } + }}); + }); + + it('pulls the site category from the first bids ortb2 object', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat' } } + }]); + expect(payload.site).to.deep.equal({ cat: 'test-cat' }); + + payload = getPayloadFromTestBids(testBids); + expect(payload.site).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat' } } + }, { + ...minimumBidParams, + ortb2: { site: { cat: 'test-cat-2' } } + }]); + expect(payload.site).to.deep.equal({ cat: 'test-cat' }); + }); + + it('pulls the schain from the first bid if it is populated', function() { + let payload; + payload = getPayloadFromTestBids(testBids); + expect(payload.schain).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + schain: {} + }, { + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + } + }]); + expect(payload.schain).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + } + }, { + ...minimumBidParams, + schain: { + complete: 1, + nodes: [{ + asi: 'test-page-2.com', + hp: 1, + rid: 'other-rid', + sid: 'other-sid' + }] + } + }]); + expect(payload.schain).to.deep.equal({ + complete: 1, + nodes: [{ + asi: 'test-page.com', + hp: 1, + rid: '57bdd953-6e57-4d5b-9351-ed67ca238890', + sid: '8190248274' + }] + }); + }); + + it('does not send currency if it is not defined', function() { + undefinedCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('does not send currency if it is missing', function() { + noAdServerCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('does not send currency if it is USD', function() { + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.be.undefined; + }); + + it('provides the currency if it is not USD', function() { + nonUSDAdServerCurrency = true; + let payload = getPayloadFromTestBids(testBids); + expect(payload.cur).to.equal('EUR'); + }); + + it('provides the social canvas segments and URL if provided', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }]); + expect(payload.socan).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: null + } + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }]); + expect(payload.socan).to.be.undefined; + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + } + } + }, { + ...minimumBidParams, + params: { + ...minimumBidParams.params, + socialCanvas: { + segments: ['segment_4', 'segment_5', 'segment_6'], + url: 'https://socan.url/new' + } + } + }]); + expect(payload.socan).to.deep.equal({ + segments: ['segment_1', 'segment_2', 'segment_3'], + url: 'https://socan.url' + }); + }); + + describe('imp', function() { + it('handles slots with different combinations of formats', function() { + const testBids = [ + // Banner and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] + }, + banner: { + sizes: [ [970, 250], [1, 1] ] + } + }, + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - userIdAsEids: [ - { - 'source': 'adserver.org', - 'uids': [ - { - 'id': 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] + // Banner and Native + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } }, - { - 'source': 'adquery.io', - 'uids': [ - { - 'id': 'adqueryId-123', - 'atype': 1 - } - ] + mediaTypes: { + banner: { + sizes: [ [970, 250], [1, 1] ] + }, + native: {} }, - { - 'source': 'criteo.com', - 'uids': [ - { - 'id': 'criteoId-456', - 'atype': 1 - } - ] - } - ], - floorData: { - floorMin: 1 + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - 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, + // Native and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 } }, - site: { - id: '1234', - name: 'SiteName', - cat: ['IAB1', 'IAB2', 'IAB3'] + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] + }, + native: {}, }, - user: { - data: [ - { - name: 'prebid.org', - ext: { - segtax: 600, - segclass: 'v1', - }, - segment: [ - { - id: '133' - }, - ] - }, - ] - } + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - ortb2Imp: { - ext: { - tid: '10101', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + // Banner and Native and Outstream + { + ...bid, + params: { + inventoryCode: 'banner_outstream_test', + floor: 1.0, + video: { + mimes: [ 'video/mp4' ], + maxduration: 30, + minduration: 6, + w: 640, + h: 480 + } + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 380] }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' - } - } - }, - { - params: { - placementId: 'bar' + banner: { + sizes: [ [970, 250], [1, 1] ] + }, + native: {}, + }, + adUnitCode: 'adunit-code-banner-outstream', + sizes: [ [300, 250], [300, 600], [1, 1, 1], ['flex'] ], + bidId: 'banner-outstream-bid-id', + bidderRequestId: 'kargo-request-id', + auctionId: 'kargo-auction-id', }, - bidId: '2', - adUnitCode: '202', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - video: { - sizes: [[320, 50], [300, 50]] + ]; + + const payload = getPayloadFromTestBids(testBids); + + const bannerImp = { + sizes: [ [970, 250], [1, 1] ] + }; + const videoImp = { + context: 'outstream', + playerSize: [640, 380] + }; + const nativeImp = {}; + + // Banner and Outstream + expect(payload.imp[0].banner).to.deep.equal(bannerImp); + expect(payload.imp[0].video).to.deep.equal(videoImp); + expect(payload.imp[0].native).to.be.undefined; + // Banner and Native + expect(payload.imp[1].banner).to.deep.equal(bannerImp); + expect(payload.imp[1].video).to.be.undefined; + expect(payload.imp[1].native).to.deep.equal(nativeImp); + // Native and Outstream + expect(payload.imp[2].banner).to.be.undefined; + expect(payload.imp[2].video).to.deep.equal(videoImp); + expect(payload.imp[2].native).to.deep.equal(nativeImp); + // Banner and Native and Outstream + expect(payload.imp[3].banner).to.deep.equal(bannerImp); + expect(payload.imp[3].video).to.deep.equal(videoImp); + expect(payload.imp[3].native).to.deep.equal(nativeImp); + }); + + it('pulls gpid from ortb2Imp.ext.gpid then ortb2Imp.ext.data.pbadslot', function () { + const gpidGpid = 'ortb2Imp.ext.gpid-gpid'; + const gpidPbadslot = 'ortb2Imp.ext.data.pbadslot-gpid' + const testBids = [ + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: gpidGpid, + data: { + pbadslot: gpidPbadslot + } + } } }, - bidRequestsCount: 0, - bidderRequestsCount: 0, - bidderWinsCount: 0, - ortb2Imp: { - ext: { - tid: '20202', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' - }, - pbadslot: '/22558409563,18834096/dfy_mobile_adhesion' + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: gpidGpid, + data: {}, } } - } - }, - { - params: { - placementId: 'bar' }, - bidId: '3', - adUnitCode: '303', - sizes: [[320, 50], [300, 250], [300, 600]], - mediaTypes: { - native: { - sizes: [[320, 50], [300, 50]] + { + ...minimumBidParams, + ortb2Imp: { + ext: { + data: { + pbadslot: gpidPbadslot + } + } } }, - ortb2Imp: { - ext: { - tid: '30303', - data: { - adServer: { - name: 'gam', - adslot: '/22558409563,18834096/dfy_mobile_adhesion' + { + ...minimumBidParams, + ortb2Imp: { + ext: { + gpid: null, + data: { + pbadslot: null } - }, - gpid: '/22558409563,18834096/dfy_mobile_adhesion' + } } - } + }, + { ...minimumBidParams } + ]; + + const payload = getPayloadFromTestBids(testBids); + + // Both present + expect(payload.imp[0].fpd).to.deep.equal({ gpid: gpidGpid }); + // Only ext.gpid + expect(payload.imp[1].fpd).to.deep.equal({ gpid: gpidGpid }); + // Only ext.data.pbadslot + expect(payload.imp[2].fpd).to.deep.equal({ gpid: gpidPbadslot }); + // Neither present + expect(payload.imp[3].fpd).to.be.undefined; + expect(payload.imp[4].fpd).to.be.undefined; + }); + + it('includes bidRequestCount, bidderRequestCount, and bidderWinsCount if they are greater than 0', function() { + const testBids = [ + { + ...minimumBidParams, + bidRequestsCount: 1, + bidderRequestsCount: 0, + bidderWinsCount: 0, + }, + { + ...minimumBidParams, + bidRequestsCount: 0, + bidderRequestsCount: 2, + bidderWinsCount: 0, + }, + { + ...minimumBidParams, + bidRequestsCount: 0, + bidderRequestsCount: 0, + bidderWinsCount: 3, + }, + { + ...minimumBidParams, + bidRequestsCount: 4, + bidderRequestsCount: 1, + bidderWinsCount: 3, + }, + ]; + + [ 0, null, false, 'foobar' ].forEach(value => testBids.push({ + ...minimumBidParams, + bidRequestsCount: value, + bidderRequestsCount: value, + bidderWinsCount: value + })); + + const payload = getPayloadFromTestBids(testBids); + + // bidRequestCount + expect(payload.imp[0].bidRequestCount).to.equal(1); + expect(payload.imp[0].bidderRequestCount).to.be.undefined; + expect(payload.imp[0].bidderWinCount).to.be.undefined; + // bidderRequestCount + expect(payload.imp[1].bidRequestCount).to.be.undefined; + expect(payload.imp[1].bidderRequestCount).to.equal(2); + expect(payload.imp[1].bidderWinCount).to.be.undefined; + // bidderWinCount + expect(payload.imp[2].bidRequestCount).to.be.undefined; + expect(payload.imp[2].bidderRequestCount).to.be.undefined; + expect(payload.imp[2].bidderWinCount).to.equal(3); + // all + expect(payload.imp[3].bidRequestCount).to.equal(4); + expect(payload.imp[3].bidderRequestCount).to.equal(1); + expect(payload.imp[3].bidderWinCount).to.equal(3); + // none + for (let i = 4; i < payload.imp.length; i++) { + expect(payload.imp[i].bidRequestCount).to.be.undefined; + expect(payload.imp[i].bidderRequestCount).to.be.undefined; + expect(payload.imp[i].bidderWinCount).to.be.undefined; } - ]; - }); + }); - afterEach(function () { - for (let key in cookies) { - let cookie = cookies[key]; - removeCookie(cookie); - } + it('queries the getFloor function to retrieve the floor and validates it', function() { + const testBids = []; + + [ + { currency: 'USD', floor: 1.99 }, + { currency: 'USD', floor: '1.99' }, + { currency: 'EUR', floor: 1.99 }, + { currency: 'USD', floor: 'foo' }, + { currency: 'USD', floor: null }, + { currency: 'USD', floor: true }, + { currency: 'USD', floor: false }, + { currency: 'USD', floor: {} }, + { currency: 'USD', floor: [] }, + ].forEach(floorValue => testBids.push({ + ...minimumBidParams, + getFloor: () => floorValue + })); + + const payload = getPayloadFromTestBids(testBids); + + // Valid floor + expect(payload.imp[0].floor).to.equal(1.99); + // Valid floor but string + expect(payload.imp[1].floor).to.equal('1.99'); // @TODO - convert this to a number? + // Non-USD valid floor + expect(payload.imp[2].floor).to.be.undefined; + // Invalid floor + for (let i = 3; i < payload.imp.length; i++) { + expect(payload.imp[i].floor).to.be.undefined; + } + }); - for (let key in localStorageItems) { - let localStorageItem = localStorageItems[key]; - localStorage.removeItem(localStorageItem); - } + it('calls getFloor with the right values', function() { + const testBids = [ + { + ...minimumBidParams, + getFloor: () => ({ currency: 'USD', value: 0.5 }) + } + ]; + sinon.spy(testBids[0], 'getFloor'); - cookies.length = 0; - localStorageItems.length = 0; - $$PREBID_GLOBAL$$.bidderSettings = {}; + getPayloadFromTestBids(testBids); + + expect(testBids[0].getFloor.calledWith({ + currency: 'USD', + mediaType: '*', + size: '*' + })).to.be.true; + }); }); - function setCookie(cname, cvalue, exdays = 1) { - _setCookie(cname, cvalue, exdays); - cookies.push(cname); - } + describe('cerberus', function() { + it('retrieves CRB from localStorage and cookies', function() { + setCrb('valid', 'valid'); - function removeCookie(cname) { - _setCookie(cname, '', -1); - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function _setCookie(cname, cvalue, exdays = 1) { - var d = new Date(), - expires; + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.equal(crbValues.validLs); + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + }); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - expires = `expires=${d.toUTCString()}`; - document.cookie = `${cname}=${cvalue};${expires};path=/`; - } + it('retrieves CRB from localStorage if cookie is missing', function() { + setCrb(false, 'valid'); - function setLocalStorageItem(name, val) { - localStorage.setItem(name, val); - localStorageItems.push(name); - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function simulateNoLocalStorage() { - return sandbox.stub(localStorage, 'getItem').throws(); - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.equal(crbValues.validLs); + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + }); - function simulateNoCurrencyObject() { - undefinedCurrency = true; - noAdServerCurrency = false; - nonUSDAdServerCurrency = false; - } + it('retrieves CRB from cookies if localstorage is missing', function() { + setCrb('valid', false); - function simulateNoAdServerCurrency() { - undefinedCurrency = false; - noAdServerCurrency = true; - nonUSDAdServerCurrency = false; - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function simulateNonUSDAdServerCurrency() { - undefinedCurrency = false; - noAdServerCurrency = false; - nonUSDAdServerCurrency = true; - } + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + }); - function generateGDPR(applies, haveConsent) { - var data = { - consentString: 'gdprconsentstring', - gdprApplies: applies, - }; - return data; - } + it('retrieves CRB from cookies if localstorage is not functional', function() { + // Note: this does not cause localStorage to throw an error in Firefox so in that browser this + // test is not 100% true to its name + sandbox.stub(localStorage, 'getItem').throws(); + setCrb('valid', 'invalid'); + + const payload = getPayloadFromTestBids(testBids, bidderRequest); + + expect(payload.rawCRB).to.equal(crbValues.valid); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + }); - function generateGDPRExpect(applies, haveConsent) { - return { - consent: 'gdprconsentstring', - applies: applies, - }; - } + it('does not fail if CRB is missing', function() { + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function generatePageView() { - return { - id: '112233', - timestamp: frozenNow.getTime(), - url: 'http://pageview.url' - } - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - function generateRawCRB(rawCRB, rawCRBLocalStorage) { - if (rawCRB == null && rawCRBLocalStorage == null) { - return null - } + it('fails gracefully if the CRB is invalid base 64 cookie', function() { + setCrb('invalidB64', false); - let result = {} + const payload = getPayloadFromTestBids(testBids, bidderRequest); - if (rawCRB != null) { - result.rawCRB = rawCRB - } + expect(payload.rawCRB).to.equal(crbValues.invalidB64); + expect(payload.rawCRBLocalStorage).to.be.undefined; + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - if (rawCRBLocalStorage != null) { - result.rawCRBLocalStorage = rawCRBLocalStorage - } + it('fails gracefully if the CRB is invalid base 64 localStorage', function() { + setCrb(false, 'invalidB64'); - return result - } + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getKrgCrb() { - return 'eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0='; - } + expect(payload.rawCRB).to.be.undefined; + expect(payload.rawCRBLocalStorage).to.equal(crbValues.invalidB64Ls); + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + }); - function getKrgCrbOldStyle() { - return '{"v":"eyJzeW5jSWRzIjp7IjIiOiI4MmZhMjU1NS01OTY5LTQ2MTQtYjRjZS00ZGNmMTA4MGU5ZjkiLCIxNiI6IlZveElrOEFvSnowQUFFZENleUFBQUFDMiY1MDIiLCIyMyI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjI0IjoiVm94SWs4QW9KejBBQUVkQ2V5QUFBQUMyJjUwMiIsIjI1IjoiNWVlMjQxMzgtNWUwMy00YjlkLWE5NTMtMzhlODMzZjI4NDlmIiwiMl84MCI6ImQyYTg1NWE1LTFiMWMtNDMwMC05NDBlLWE3MDhmYTFmMWJkZSIsIjJfOTMiOiI1ZWUyNDEzOC01ZTAzLTRiOWQtYTk1My0zOGU4MzNmMjg0OWYifSwibGV4SWQiOiI1ZjEwODgzMS0zMDJkLTExZTctYmY2Yi00NTk1YWNkM2JmNmMiLCJjbGllbnRJZCI6IjI0MTBkOGYyLWMxMTEtNDgxMS04OGE1LTdiNWUxOTBlNDc1ZiIsIm9wdE91dCI6ZmFsc2UsImV4cGlyZVRpbWUiOjE0OTc0NDkzODI2NjgsImxhc3RTeW5jZWRBdCI6MTQ5NzM2Mjk3OTAxMn0="}'; - } + [ + [ 'valid', 'invalidB64', 'cookie' ], + [ 'valid', 'invalidJson', 'cookie' ], + [ 'invalidB64', 'invalidJson', 'none' ], + [ 'invalidB64', 'invalidB64', 'none' ], + [ 'invalidB64', 'valid', 'localStorage' ], + [ 'invalidJson', 'invalidJson', 'none' ], + [ 'invalidJson', 'invalidB64', 'none' ], + [ 'invalidJson', 'valid', 'localStorage' ], + ].forEach(config => { + it(`uses ${config[2]} if the cookie is ${config[0]} and localStorage is ${config[1]}`, function() { + setCrb(config[0], config[1]); + const payload = getPayloadFromTestBids(testBids, bidderRequest); + + expect(payload.rawCRB).to.equal(crbValues[config[0]]); + expect(payload.rawCRBLocalStorage).to.equal(crbValues[`${config[1]}Ls`]); + if (config[2] === 'cookie') { + expect(payload.user.crbIDs).to.deep.equal(validCrbIds); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-cookie'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-cookie'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-cookie'); + expect(payload.user.optOut).to.equal(false); + } else if (config[2] === 'localStorage') { + expect(payload.user.crbIDs).to.deep.equal(validCrbIdsLs); + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + expect(payload.user.optOut).to.equal(false); + } else { + expect(payload.user.crbIDs).to.deep.equal({}); + expect(payload.user.tdID).to.be.undefined; + expect(payload.user.kargoID).to.be.undefined; + expect(payload.user.clientID).to.be.undefined; + expect(payload.user.optOut).to.be.undefined; + } + }); + }); - function initializeKrgCrb(cookieOnly) { - if (!cookieOnly) { - setLocalStorageItem('krg_crb', getKrgCrb()); - } - setCookie('krg_crb', getKrgCrbOldStyle()); - } + it('uses the tdID from cerberus to populate the tdID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getInvalidKrgCrbType1() { - return 'invalid-krg-crb'; - } + expect(payload.user.tdID).to.equal('test-tdid-cerberus-localstorage'); + }); - function initializeInvalidKrgCrbType1() { - setLocalStorageItem('krg_crb', getInvalidKrgCrbType1()); - } + it('uses the lexId from cerberus to populate the kargoID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType1Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType1()); - } + expect(payload.user.kargoID).to.equal('test-lexid-cerberus-localstorage'); + }); - function getInvalidKrgCrbType2() { - return 'Ly8v'; - } + it('uses the clientId from cerberus to populate the clientID field', function() { + setCrb('valid', 'valid'); + const payload = getPayloadFromTestBids(testBids, bidderRequest); - function getInvalidKrgCrbType2OldStyle() { - return '{"v":"&&&&&&"}'; - } + expect(payload.user.clientID).to.equal('test-clientid-cerberus-localstorage'); + }); - function initializeInvalidKrgCrbType2() { - setLocalStorageItem('krg_crb', getInvalidKrgCrbType2()); - } + it('uses the optOut from cerberus to populate the clientID field', function() { + setCrb('valid', 'valid'); + let payload; + payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType2Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType2OldStyle()); - } + expect(payload.user.optOut).to.equal(false); - function getInvalidKrgCrbType3OldStyle() { - return '{"v":"Ly8v"}'; - } + setLocalStorageValue('krg_crb', buildCrbValue(false, true, true, true, true, true)); + payload = getPayloadFromTestBids(testBids, bidderRequest); - function initializeInvalidKrgCrbType3Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType3OldStyle()); - } + expect(payload.user.optOut).to.equal(true); + }); + }); - function getInvalidKrgCrbType4OldStyle() { - return '{"v":"bnVsbA=="}'; - } + describe('user', function() { + it('fetches the trade desk id from the adapter if present', function() { + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + userId: { + tdid: 'test-tdid-module' + } + }]); - function initializeInvalidKrgCrbType4Cookie() { - setCookie('krg_crb', getInvalidKrgCrbType4OldStyle()); - } + expect(payload.user.tdID).to.equal('test-tdid-module'); + }); - function getEmptyKrgCrb() { - return 'eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9'; - } + it('fetches the trade desk id from cerberus if present', function() { + setLocalStorageValue('krg_crb', btoa(JSON.stringify({ tdID: 'test-tdid-crb' }))); - function getEmptyKrgCrbOldStyle() { - return '{"v":"eyJleHBpcmVUaW1lIjoxNDk3NDQ5MzgyNjY4LCJsYXN0U3luY2VkQXQiOjE0OTczNjI5NzkwMTJ9"}'; - } + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }]); - function initializeEmptyKrgCrb() { - setLocalStorageItem('krg_crb', getEmptyKrgCrb()); - } + expect(payload.user.tdID).to.equal('test-tdid-crb'); + }); - function initializePageView() { - setLocalStorageItem('pageViewId', 112233); - setLocalStorageItem('pageViewTimestamp', frozenNow.getTime()); - setLocalStorageItem('pageViewUrl', 'http://pageview.url'); - } + it('fetches the trade desk id from the adapter if adapter and cerberus are present', function() { + setLocalStorageValue('krg_crb', buildCrbValue(false, true, true, true, true, false)); - function initializeEmptyKrgCrbCookie() { - setCookie('krg_crb', getEmptyKrgCrbOldStyle()); - } + const payload = getPayloadFromTestBids([{ + ...minimumBidParams, + userId: { + tdid: 'test-tdid-module' + } + }]); + + expect(payload.user.tdID).to.equal('test-tdid-module'); + }); + + it('does not set kargoId if it is not present', function() { + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.lexId).to.be.undefined; + }); + + it('does not populate usp, gdpr, or gpp if they are not present', function() { + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.usp).to.be.undefined; + expect(payload.user.gdpr).to.be.undefined; + expect(payload.user.gpp).to.be.undefined; + }); + + it('fetches usp from the bidder request if present', function() { + bidderRequest.uspConsent = '1---'; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.usp).to.equal('1---'); + }); + + it('fetches gpp from the bidder request if present', function() { + bidderRequest.gppConsent = { + consentString: 'gppString', + applicableSections: [-1] + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gpp).to.deep.equal({ + gppString: 'gppString', + applicableSections: [-1] + }); + }); + + it('does not send empty gpp values', function() { + bidderRequest.gppConsent = { + consentString: '', + applicableSections: '' + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gpp).to.be.undefined; + }); + + it('fetches gdpr consent from the bidder request if present', function() { + bidderRequest.gdprConsent = { + consentString: 'gdpr-consent-string', + gdprApplies: true + }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + + expect(payload.user.gdpr).to.deep.equal({ + consent: 'gdpr-consent-string', + applies: true + }); + }); + + it('handles malformed gdpr applies from the bidder request', function() { + [ + ['true', true], + ['false', true], + ['1', true], + [1, true], + [0, false], + ['0', true], + ['y', true], + ['yes', true], + ['n', true], + ['no', true], + [null, false], + [{}, true], + ].forEach(testValue => { + bidderRequest.gdprConsent = { gdprApplies: testValue[0] }; + const payload = getPayloadFromTestBids([{ ...minimumBidParams }]); + expect(payload.user.gdpr, `Value - ${JSON.stringify(testValue[0])}`).to.deep.equal({ + consent: '', + applies: testValue[1] + }); + }); + }); + + it('passes the user.data from the first bid request if availabale', function() { + let payload; + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + }, { + ...minimumBidParams, + ortb2: { user: { data: { test: 'value' } } } + }]); + expect(payload.user.data).to.deep.equal([]); + + payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { user: { data: { test: 'value' } } } + }, { + ...minimumBidParams, + ortb2: { user: { data: { test2: 'value2' } } } + }]); + expect(payload.user.data).to.deep.equal({ + test: 'value' + }); + }); + + it('fails gracefully if there is no localStorage', function() { + sandbox.stub(localStorage, 'getItem').throws(); + let payload = getPayloadFromTestBids(testBids); + expect(payload.user).to.deep.equal({ + crbIDs: {}, + data: [] + }); + }); + }); + + describe('sua', function() { + it('is not provided if not present in the first valid bid', function() { + let payload = getPayloadFromTestBids([ + ...testBids, + { + ...minimumBidParams, + 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, + } } } + } + ]); + expect(payload.device.sua).to.be.undefined; + }); + + it('is provided if present in the first valid bid', function() { + let payload = getPayloadFromTestBids([ + { + ...minimumBidParams, + 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, + } } } + }, + { + ...minimumBidParams, + ortb2: { device: { sua: { + platform: { + brand: 'macOS2', + version: ['122', '6', '0'] + }, + browsers: [ + { + brand: 'Chromium2', + version: ['1062', '0', '5249', '119'] + }, + { + brand: 'Google Chrome2', + version: ['102', '0', '5249', '119'] + }, + { + brand: 'Not;A=Brand2', + version: ['992', '0', '0', '0'] + } + ], + mobile: 2, + model: 'model2', + source: 2, + } } } + } + ]); + expect(payload.device.sua).to.deep.equal({ + 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, + }); + }); + + it('does not send non-mapped attributes', function() { + let payload = getPayloadFromTestBids([{...minimumBidParams, + ortb2: { device: { sua: { + other: 'value', + objectMissing: { + key: 'value' + }, + 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, + } } } + }]); + expect(payload.device.sua).to.deep.equal({ + 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, + }); + }); - function getSessionId() { - return spec._getSessionId(); - } + it('does not send non-truthy values or empty strings', function() { + [ + false, + 0, + null, + '', + ' ', + ' ', + ].forEach(value => { + let payload = getPayloadFromTestBids([{...minimumBidParams, + ortb2: { device: { sua: { + platform: value, + 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, + } } } + }]); + expect(payload.device.sua, `Value - ${JSON.stringify(value)}`).to.deep.equal({ + 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, + }); + }); + }); - function getExpectedKrakenParams(expectedCRB, expectedPage, excludeUserIds, expectedGDPR, currency) { - var base = { - pbv: '$prebid.version$', - aid: '1234098', - requestCount: 0, - sid: getSessionId(), - url: 'https://www.prebid.org', - timeout: 200, - ts: frozenNow.getTime(), - schain: testSchain, - device: { - size: [ - screen.width, - screen.height - ], - sua: { + it('does not send 0 for mobile or source', function() { + let payload = getPayloadFromTestBids([{ + ...minimumBidParams, + ortb2: { device: { sua: { platform: { brand: 'macOS', version: ['12', '6', '0'] @@ -474,506 +1498,217 @@ describe('kargo adapter tests', function () { version: ['99', '0', '0', '0'] } ], - mobile: 1, + mobile: 0, model: 'model', - source: 1 - }, - }, - site: { - cat: ['IAB1', 'IAB2', 'IAB3'] - }, - imp: [ - { - code: '101', - id: '1', - pid: 'foo', - tid: '10101', - banner: { - sizes: [[320, 50], [300, 50]] - }, - bidRequestCount: 1, - bidderRequestCount: 2, - bidderWinCount: 3, - 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' - } - } - } - }, - { - code: '202', - id: '2', - pid: 'bar', - tid: '20202', - video: { - sizes: [[320, 50], [300, 50]] - }, - 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' - } - } - } - } - }, - { - code: '303', - id: '3', - pid: 'bar', - tid: '30303', - native: { - sizes: [[320, 50], [300, 50]] - }, - 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' - } - } - } - } - ], - socan: { - segments: ['segment_1', 'segment_2', 'segment_3'], - url: 'https://socan.url' - }, - user: { - kargoID: '5f108831-302d-11e7-bf6b-4595acd3bf6c', - clientID: '2410d8f2-c111-4811-88a5-7b5e190e475f', - tdID: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - crbIDs: { - 2: '82fa2555-5969-4614-b4ce-4dcf1080e9f9', - 16: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 23: 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - 24: 'VoxIk8AoJz0AAEdCeyAAAAC2&502', - 25: '5ee24138-5e03-4b9d-a953-38e833f2849f', - '2_80': 'd2a855a5-1b1c-4300-940e-a708fa1f1bde', - '2_93': '5ee24138-5e03-4b9d-a953-38e833f2849f' + source: 0, + } } } + }]); + expect(payload.device.sua).to.deep.equal({ + platform: { + brand: 'macOS', + version: ['12', '6', '0'] }, - optOut: false, - usp: '1---', - sharedIDEids: [ + browsers: [ { - source: 'adserver.org', - uids: [ - { - id: 'ed1562d5-e52b-406f-8e65-e5ab3ed5583c', - atype: 1, - ext: { - rtiPartner: 'TDID' - } - } - ] + brand: 'Chromium', + version: ['106', '0', '5249', '119'] }, { - source: 'adquery.io', - uids: [ - { - id: 'adqueryId-123', - atype: 1 - } - ] + brand: 'Google Chrome', + version: ['106', '0', '5249', '119'] }, { - source: 'criteo.com', - uids: [ - { - id: 'criteoId-456', - atype: 1 - } - ] + brand: 'Not;A=Brand', + version: ['99', '0', '0', '0'] } ], - data: [ - { - name: 'prebid.org', - ext: { - segtax: 600, - segclass: 'v1', - }, - segment: [ - { - id: '133' - } - ] - } - ] - }, - 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' - }, - ] - }, - ] - } - } - } - }; - - if (excludeUserIds) { - base.user.crbIDs = {}; - delete base.user.clientID; - delete base.user.kargoID; - delete base.user.optOut; - } - - if (expectedGDPR) { - base.user.gdpr = expectedGDPR; - } - - if (expectedPage) { - base.page = expectedPage; - } + model: 'model', + }); + }); + }); - if (currency) { - base.cur = currency; - } + describe('page', function() { + it('pulls the page ID from localStorage', function() { + setLocalStorageValue('pageViewId', 'test-page-id'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + id: 'test-page-id' + }); + }); - const reqCount = requestCount++; - base.requestCount = reqCount + it('pulls the page timestamp from localStorage', function() { + setLocalStorageValue('pageViewTimestamp', '123456789'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + timestamp: 123456789 + }); + }); - if (expectedCRB != null) { - if (expectedCRB.rawCRB != null) { - base.rawCRB = expectedCRB.rawCRB - } - if (expectedCRB.rawCRBLocalStorage != null) { - base.rawCRBLocalStorage = expectedCRB.rawCRBLocalStorage - } - } + it('pulls the page ID from localStorage', function() { + setLocalStorageValue('pageViewUrl', 'https://test-url.com'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + url: 'https://test-url.com' + }); + }); - return base; - } + it('pulls all 3 together', function() { + setLocalStorageValue('pageViewId', 'test-page-id'); + setLocalStorageValue('pageViewTimestamp', '123456789'); + setLocalStorageValue('pageViewUrl', 'https://test-url.com'); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.deep.equal({ + id: 'test-page-id', + timestamp: 123456789, + url: 'https://test-url.com' + }); + }); - function testBuildRequests(expected, gdpr) { - var clonedBids = JSON.parse(JSON.stringify(bids)); + it('fails gracefully without localStorage', function() { + sandbox.stub(localStorage, 'getItem').throws(); + let payload = getPayloadFromTestBids(testBids); + expect(payload.page).to.be.undefined; + }); + }); + }); - var payload = { - timeout: 200, - uspConsent: '1---', - refererInfo: { - page: 'https://www.prebid.org', + describe('interpretResponse', function() { + const response = Object.freeze({ body: { + 1: { + id: 'foo', + cpm: 3, + adm: '
', + width: 320, + height: 50, + metadata: {}, + creativeID: 'bar' + }, + 2: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + targetingCustom: 'dmpmptest1234', + metadata: { + landingPageDomain: ['https://foobar.com'] }, - }; - - if (gdpr) { - payload['gdprConsent'] = gdpr + creativeID: 'foo' + }, + 3: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + creativeID: 'foo' + }, + 4: { + id: 'bar', + cpm: 2.5, + adm: '
', + width: 300, + height: 250, + mediaType: 'banner', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' + }, + 5: { + id: 'bar', + cpm: 2.5, + adm: '', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' + }, + 6: { + id: 'bar', + cpm: 2.5, + adm: '', + admUrl: 'https://foobar.com/vast_adm', + width: 300, + height: 250, + mediaType: 'video', + metadata: {}, + creativeID: 'foo', + currency: 'EUR' } - - 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 }); + }}); + const bidderRequest = Object.freeze({ + currency: 'USD', + bids: [{ + bidId: 1, + params: { + placementId: 'foo' } - }); - - var request = spec.buildRequests(clonedBids, payload); - var krakenParams = request.data; - - expect(request.url).to.equal('https://krk2.kargo.com/api/v1/prebid'); - expect(request.method).to.equal('POST'); - expect(request.timeout).to.equal(200); - expect(krakenParams).to.deep.equal(expected); - - // Make sure session ID stays the same across requests simulating multiple auctions on one page load - for (let i in sessionIds) { - if (i == 0) { - continue; + }, { + bidId: 2, + params: { + placementId: 'bar' } - let sessionId = sessionIds[i]; - expect(sessionIds[0]).to.equal(sessionId); - } - } - - it('works when all params and localstorage and cookies are correctly set', function () { - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('works when all params and cookies are correctly set but no localstorage', function () { - initializeKrgCrb(true); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle()))); - }); - - it('gracefully handles nothing being set', function () { - testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); - }); - - it('gracefully handles browsers without localStorage', function () { - simulateNoLocalStorage(); - testBuildRequests(getExpectedKrakenParams(undefined, undefined, true)); - }); - - it('handles empty yet valid Kargo CRB', function () { - initializeEmptyKrgCrb(); - initializeEmptyKrgCrbCookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getEmptyKrgCrbOldStyle(), getEmptyKrgCrb()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where base64 encoding is invalid', function () { - initializeInvalidKrgCrbType1(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType1()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where top level JSON is invalid on cookie', function () { - initializeInvalidKrgCrbType1Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType1()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where decoded JSON is invalid', function () { - initializeInvalidKrgCrbType2(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(undefined, getInvalidKrgCrbType2()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner base 64 is invalid on cookie', function () { - initializeInvalidKrgCrbType2Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType2OldStyle()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner JSON is invalid on cookie', function () { - initializeInvalidKrgCrbType3Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType3OldStyle()), generatePageView(), true)); - }); - - it('handles broken Kargo CRBs where inner JSON is falsey', function () { - initializeInvalidKrgCrbType4Cookie(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getInvalidKrgCrbType4OldStyle()), generatePageView(), true)); - }); - - it('handles a non-existant currency object on the config', function () { - simulateNoCurrencyObject(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('handles no ad server currency being set on the currency object in the config', function () { - simulateNoAdServerCurrency(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView())); - }); - - it('handles non-USD ad server currency being set on the currency object in the config', function () { - simulateNonUSDAdServerCurrency(); - initializeKrgCrb(); - initializePageView(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), generatePageView(), undefined, undefined, 'EUR')); + }, { + bidId: 3, + params: { + placementId: 'bar' + } + }, { + bidId: 4, + params: { + placementId: 'bar' + } + }, { + bidId: 5, + params: { + placementId: 'bar' + } + }, { + bidId: 6, + params: { + placementId: 'bar' + } + }] }); - it('sends gdpr consent', function () { - initializeKrgCrb(); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(true, true)), generateGDPR(true, true)); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, true)), generateGDPR(false, true)); - testBuildRequests(getExpectedKrakenParams(generateRawCRB(getKrgCrbOldStyle(), getKrgCrb()), undefined, false, generateGDPRExpect(false, false)), generateGDPR(false, false)); + it('returns an empty array if the response body is empty or not an object', function() { + [ + '', + undefined, + false, + true, + null, + [], + {}, + 1234, + ].forEach(value => { + let bids = spec.interpretResponse({ body: value }, bidderRequest); + expect(bids, `Value - ${JSON.stringify(value)}`).to.deep.equal([]); + }); }); - }); - describe('response handler', function () { - it('handles bid responses', function () { - var resp = spec.interpretResponse({ - body: { - 1: { - id: 'foo', - cpm: 3, - adm: '
', - width: 320, - height: 50, - metadata: {}, - creativeID: 'bar' - }, - 2: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - targetingCustom: 'dmpmptest1234', - metadata: { - landingPageDomain: ['https://foobar.com'] - }, - creativeID: 'foo' - }, - 3: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - creativeID: 'foo' - }, - 4: { - id: 'bar', - cpm: 2.5, - adm: '
', - width: 300, - height: 250, - mediaType: 'banner', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - }, - 5: { - id: 'bar', - cpm: 2.5, - adm: '', - width: 300, - height: 250, - mediaType: 'video', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - }, - 6: { - id: 'bar', - cpm: 2.5, - adm: '', - admUrl: 'https://foobar.com/vast_adm', - width: 300, - height: 250, - mediaType: 'video', - metadata: {}, - creativeID: 'foo', - currency: 'EUR' - } - } - }, { - currency: 'USD', - bids: [{ - bidId: 1, - params: { - placementId: 'foo' - } - }, { - bidId: 2, - params: { - placementId: 'bar' - } - }, { - bidId: 3, - params: { - placementId: 'bar' - } - }, { - bidId: 4, - params: { - placementId: 'bar' - } - }, { - bidId: 5, - params: { - placementId: 'bar' - } - }, { - bidId: 6, - params: { - placementId: 'bar' - } - }] - }); - var expectation = [{ + it('returns bid response for various objects', function() { + let bids = spec.interpretResponse(response, bidderRequest); + expect(bids).to.have.length(Object.keys(response.body).length); + expect(bids[0]).to.deep.equal({ ad: '
', - requestId: '1', cpm: 3, - width: 320, - height: 50, - ttl: 300, creativeId: 'bar', - dealId: undefined, - netRevenue: true, currency: 'USD', + dealId: undefined, + height: 50, mediaType: 'banner', meta: { mediaType: 'banner' - } - }, { + }, + netRevenue: true, + requestId: '1', + ttl: 300, + width: 320, + }); + expect(bids[1]).to.deep.equal({ requestId: '2', ad: '
', cpm: 2.5, @@ -990,7 +1725,8 @@ describe('kargo adapter tests', function () { clickUrl: 'https://foobar.com', advertiserDomains: ['https://foobar.com'] } - }, { + }); + expect(bids[2]).to.deep.equal({ requestId: '3', ad: '
', cpm: 2.5, @@ -1005,7 +1741,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'banner' } - }, { + }); + expect(bids[3]).to.deep.equal({ requestId: '4', ad: '
', cpm: 2.5, @@ -1020,7 +1757,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'banner' } - }, { + }); + expect(bids[4]).to.deep.equal({ requestId: '5', cpm: 2.5, width: 300, @@ -1035,7 +1773,8 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } - }, { + }); + expect(bids[5]).to.deep.equal({ requestId: '6', cpm: 2.5, width: 300, @@ -1050,148 +1789,200 @@ describe('kargo adapter tests', function () { meta: { mediaType: 'video' } - }]; - expect(resp).to.deep.equal(expectation); + }); }); - }); - - describe('user sync handler', function () { - const clientId = '74c81cbb-7d07-46d9-be9b-68ccb291c949'; - var shouldSimulateOutdatedBrowser, crb, isActuallyOutdatedBrowser; - beforeEach(() => { - $$PREBID_GLOBAL$$.bidderSettings = { - kargo: { - storageAllowed: true + it('adds landingPageDomain data', function() { + let response = spec.interpretResponse({ body: { 0: { + metadata: { + landingPageDomain: [ + 'https://foo.com', + 'https://bar.com' + ] } - }; - crb = {}; - shouldSimulateOutdatedBrowser = false; - isActuallyOutdatedBrowser = false; - - // IE11 fails these tests in the Prebid test suite. Since this - // browser won't support any of this stuff we expect all user - // syncing to fail gracefully. Kargo is mobile only, so this - // doesn't really matter. - if (!window.crypto) { - isActuallyOutdatedBrowser = true; - } else { - sandbox.stub(crypto, 'getRandomValues').callsFake(function (buf) { - if (shouldSimulateOutdatedBrowser) { - throw new Error('Could not generate random values'); - } - var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0]; - for (var i = 0; i < bytes.length; i++) { - buf[i] = bytes[i]; - } - return buf; - }); - } - - sandbox.stub(spec, '_getCrb').callsFake(function () { - return crb; + } } }, {}); + expect(response[0].meta).to.deep.equal({ + mediaType: 'banner', + clickUrl: 'https://foo.com', + advertiserDomains: [ 'https://foo.com', 'https://bar.com' ] }); }); + }); - function getUserSyncsWhenAllowed(gdprConsent, usPrivacy, gppConsent) { - return spec.getUserSyncs({ iframeEnabled: true }, null, gdprConsent, usPrivacy, gppConsent); - } + describe('getUserSyncs', function() { + let crb = {}; + const clientId = 'random-client-id-string'; + const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; + + function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { + let syncs = []; + for (let i = 0; i < 5; i++) { + syncs.push({ + type: 'iframe', + url: baseUrl.replace(/idx=\d+&/, `idx=${i}&`), + }); + } - function getUserSyncsWhenForbidden() { - return spec.getUserSyncs({}); + return syncs; } - function turnOnClientId() { - crb.clientId = clientId; + function getUserSyncs(gdpr, usp, gpp) { + return spec.getUserSyncs( + { iframeEnabled: true }, + null, + gdpr, + usp, + gpp + ); } - function simulateOutdatedBrowser() { - shouldSimulateOutdatedBrowser = true; - } + beforeEach(function() { + crb = { clientId }; + sandbox.stub(spec, '_getCrb').callsFake(function() { return crb; }); - function getSyncUrl(index, gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { - return { - type: 'iframe', - url: `https://crb.kargo.com/api/v1/initsyncrnd/${clientId}?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=${index}&gdpr=${gdprApplies}&gdpr_consent=${gdprConsentString}&us_privacy=${usPrivacy}&gpp=${gpp}&gpp_sid=${gppSid}` - }; - } + // Makes the seed in the URLs predictable + sandbox.stub(crypto, 'getRandomValues').callsFake(function (buf) { + var bytes = [50, 5, 232, 133, 141, 55, 49, 57, 244, 126, 248, 44, 255, 38, 128, 0]; + for (var i = 0; i < bytes.length; i++) { + buf[i] = bytes[i]; + } + return buf; + }); + }); - function getSyncUrls(gdprApplies, gdprConsentString, usPrivacy, gpp, gppSid) { - var syncs = []; - for (var i = 0; i < 5; i++) { - syncs[i] = getSyncUrl(i, gdprApplies || 0, gdprConsentString || '', usPrivacy || '', gpp || '', gppSid || ''); - } - return syncs; - } + it('returns user syncs when an ID is present', function() { + expect(getUserSyncs()).to.deep.equal(buildSyncUrls()); + }); - function safelyRun(runExpectation) { - if (isActuallyOutdatedBrowser) { - expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty; - } else { - runExpectation(); - } - } + it('returns no syncs if there is no user ID', function() { + delete crb.clientId; + expect(getUserSyncs()).to.deep.equal([]); + }); - it('handles user syncs when there is a client id', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.deep.equal(getSyncUrls())); + it('returns no syncs if there is no usp consent', function() { + expect(getUserSyncs(undefined, '1YYY')).to.deep.equal([]); }); - it('no user syncs when there is no client id', function () { - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + it('returns no syncs if iframe syncing is not allowed', function() { + expect(spec.getUserSyncs({ iframeEnabled: false }, null, undefined, undefined, undefined)) + .to.deep.equal([]); + expect(spec.getUserSyncs({}, null, undefined, undefined, undefined)) + .to.deep.equal([]); }); - it('no user syncs when there is no us privacy consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YYY')).to.be.an('array').that.is.empty); + it('includes the US privacy string in the sync URL if present', function() { + [ + '0---', + '1---', + '1NNN', + 'invalid', + 1234, + ].forEach(value => expect(getUserSyncs(undefined, value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl.replace(/us_privacy=/, `us_privacy=${value}`)))); }); - it('pass through us privacy consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, '1YNY')).to.deep.equal(getSyncUrls(0, '', '1YNY'))); + it('includes gdpr information if provided', function() { + [ + { gdprApplies: true, consentString: 'test-consent-string', ga: '1', cs: 'test-consent-string' }, + { gdprApplies: false, consentString: 'test-consent-string', ga: '0', cs: 'test-consent-string' }, + { gdprApplies: true, ga: '1', cs: '' }, + { gdprApplies: false, ga: '0', cs: '' }, + { ga: '0', cs: '' }, + ].forEach(value => expect(getUserSyncs(value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl + .replace(/gdpr=\d/, `gdpr=${value.ga}`) + .replace(/gdpr_consent=/, `gdpr_consent=${value.cs}`)))); }); - it('pass through gdpr consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed({ gdprApplies: true, consentString: 'consentstring' })).to.deep.equal(getSyncUrls(1, 'consentstring', ''))); + it('handles malformed gdpr information', function() { + [ + null, + false, + true, + 1, + '1', + 'test-applies', + [], + {} + ].forEach(value => expect(getUserSyncs(value), `Value - ${JSON.stringify(value)}`) + .to.deep.equal(buildSyncUrls(baseUrl))); }); - it('pass through gpp consent', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenAllowed(null, null, { consentString: 'gppString', applicableSections: [-1] })).to.deep.equal(getSyncUrls('', '', '', 'gppString', '-1'))); + it('includes gpp information if provided', function() { + [ + { applicableSections: [-1], consentString: 'test-consent-string', as: '-1', cs: 'test-consent-string' }, + { applicableSections: [1, 2, 3], consentString: 'test-consent-string', as: '1,2,3', cs: 'test-consent-string' }, + { applicableSections: [-1], as: '-1', cs: '' }, + { applicableSections: false, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: null, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: {}, consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { applicableSections: [], consentString: 'test-consent-string', as: '', cs: 'test-consent-string' }, + { as: '', cs: '' }, + ].forEach(value => expect(getUserSyncs(undefined, undefined, value), `Value - ${value}`) + .to.deep.equal(buildSyncUrls(baseUrl + .replace(/gpp=/, `gpp=${value.cs}`) + .replace(/gpp_sid=/, `gpp_sid=${value.as}`)))); }); - it('no user syncs when there is outdated browser', function () { - turnOnClientId(); - simulateOutdatedBrowser(); - safelyRun(() => expect(getUserSyncsWhenAllowed()).to.be.an('array').that.is.empty); + it('handles malformed gpp information', function() { + [ + null, + false, + true, + 1, + '1', + 'test-applies', + [], + {} + ].forEach(value => expect(getUserSyncs(undefined, undefined, value), `Value - ${JSON.stringify(value)}`) + .to.deep.equal(buildSyncUrls(baseUrl))); }); - it('no user syncs when no iframe syncing allowed', function () { - turnOnClientId(); - safelyRun(() => expect(getUserSyncsWhenForbidden()).to.be.an('array').that.is.empty); + it('includes all 3 if provided', function() { + expect(getUserSyncs( + { gdprApplies: true, consentString: 'test-gdpr-consent' }, + '1---', + { applicableSections: [1, 2, 3], consentString: 'test-gpp-consent' } + )).to.deep.equal(buildSyncUrls(baseUrl + .replace(/gdpr=\d/, 'gdpr=1') + .replace(/gdpr_consent=/, 'gdpr_consent=test-gdpr-consent') + .replace(/us_privacy=/, 'us_privacy=1---') + .replace(/gpp=/, 'gpp=test-gpp-consent') + .replace(/gpp_sid=/, 'gpp_sid=1,2,3'))); }); }); - describe('timeout pixel trigger', function () { - let triggerPixelStub; + describe('supportedMediaTypes', function() { + it('exposes video and banner', function() { + expect(spec.supportedMediaTypes).to.deep.equal([ 'banner', 'video' ]); + }); + }); + describe('onTimeout', function() { beforeEach(function () { - triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + sinon.stub(utils, 'triggerPixel'); }); afterEach(function () { utils.triggerPixel.restore(); }); - it('should call triggerPixel utils function when timed out is filled', function () { - spec.onTimeout(); - expect(triggerPixelStub.getCall(0)).to.be.null; - spec.onTimeout([{ auctionId: '1234', timeout: 2000 }]); - expect(triggerPixelStub.getCall(0)).to.not.be.null; - expect(triggerPixelStub.getCall(0).args[0]).to.exist.and.to.include('https://krk2.kargo.com/api/v1/event/timeout'); - expect(triggerPixelStub.getCall(0).args[0]).to.include('aid=1234'); - expect(triggerPixelStub.getCall(0).args[0]).to.include('ato=2000'); + it('does not call triggerPixel if timeout data is not provided', function() { + spec.onTimeout(null); + expect(utils.triggerPixel.callCount).to.equal(0); + }); + + it('calls triggerPixel if any timeout data is provided', function() { + spec.onTimeout([ + {auctionId: 'test-auction-id', timeout: 400}, + {auctionId: 'test-auction-id-2', timeout: 100}, + {auctionId: 'test-auction-id-3', timeout: 450}, + {auctionId: 'test-auction-id-4', timeout: 500}, + ]); + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450')).to.be.true; + expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500')).to.be.true; }); }); }); From bf6de067a4a333c906ef5908b30e7fa455746c1a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 30 Apr 2024 10:59:19 -0700 Subject: [PATCH 685/760] Build system: fix standalone debugging & inclusion of node dependencies (#11375) --- gulpfile.js | 87 ++++++++++++++++++++++++-------------------- webpack.conf.js | 10 ----- webpack.debugging.js | 30 +++++++++++++++ 3 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 webpack.debugging.js diff --git a/gulpfile.js b/gulpfile.js index 4dbd92587fe..86c1b7fe509 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,6 +11,7 @@ var webpackStream = require('webpack-stream'); var gulpClean = require('gulp-clean'); var opens = require('opn'); var webpackConfig = require('./webpack.conf.js'); +const standaloneDebuggingConfig = require('./webpack.debugging.js'); var helpers = require('./gulpHelpers.js'); var concat = require('gulp-concat'); var replace = require('gulp-replace'); @@ -127,35 +128,56 @@ function viewReview(done) { viewReview.displayName = 'view-review'; -function makeDevpackPkg() { - var cloned = _.cloneDeep(webpackConfig); - Object.assign(cloned, { - devtool: 'source-map', - mode: 'development' - }) +function makeVerbose(config = webpackConfig) { + return _.merge({}, config, { + optimization: { + minimizer: [ + new TerserPlugin({ + parallel: true, + terserOptions: { + mangle: false, + format: { + comments: 'all' + } + }, + extractComments: false, + }), + ], + } + }); +} - const babelConfig = require('./babelConfig.js')({disableFeatures: helpers.getDisabledFeatures(), prebidDistUrlBase: argv.distUrlBase || '/build/dev/'}); +function makeDevpackPkg(config = webpackConfig) { + return function() { + var cloned = _.cloneDeep(config); + Object.assign(cloned, { + devtool: 'source-map', + mode: 'development' + }) + + const babelConfig = require('./babelConfig.js')({disableFeatures: helpers.getDisabledFeatures(), prebidDistUrlBase: argv.distUrlBase || '/build/dev/'}); - // update babel config to set local dist url - cloned.module.rules - .flatMap((rule) => rule.use) - .filter((use) => use.loader === 'babel-loader') - .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); + // update babel config to set local dist url + cloned.module.rules + .flatMap((rule) => rule.use) + .filter((use) => use.loader === 'babel-loader') + .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); - var externalModules = helpers.getArgModules(); + var externalModules = helpers.getArgModules(); - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); + const analyticsSources = helpers.getAnalyticsSources(); + const moduleSources = helpers.getModulePaths(externalModules); - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) - .pipe(gulp.dest('build/dev')) - .pipe(connect.reload()); + return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) + .pipe(helpers.nameModules(externalModules)) + .pipe(webpackStream(cloned, webpack)) + .pipe(gulp.dest('build/dev')) + .pipe(connect.reload()); + } } -function makeWebpackPkg(extraConfig = {}) { - var cloned = _.merge(_.cloneDeep(webpackConfig), extraConfig); +function makeWebpackPkg(config = webpackConfig) { + var cloned = _.cloneDeep(config) if (!argv.sourceMaps) { delete cloned.devtool; } @@ -495,26 +517,11 @@ gulp.task(escapePostbidConfig); gulp.task('build-creative-dev', gulp.series(buildCreative(argv.creativeDev ? 'development' : 'production'), updateCreativeRenderers)); gulp.task('build-creative-prod', gulp.series(buildCreative(), updateCreativeRenderers)); -gulp.task('build-bundle-dev', gulp.series('build-creative-dev', makeDevpackPkg, gulpBundle.bind(null, true))); -gulp.task('build-bundle-prod', gulp.series('build-creative-prod', makeWebpackPkg(), gulpBundle.bind(null, false))); +gulp.task('build-bundle-dev', gulp.series('build-creative-dev', makeDevpackPkg(standaloneDebuggingConfig), makeDevpackPkg(), gulpBundle.bind(null, true))); +gulp.task('build-bundle-prod', gulp.series('build-creative-prod', makeWebpackPkg(standaloneDebuggingConfig), makeWebpackPkg(), gulpBundle.bind(null, false))); // build-bundle-verbose - prod bundle except names and comments are preserved. Use this to see the effects // of dead code elimination. -gulp.task('build-bundle-verbose', gulp.series(makeWebpackPkg({ - optimization: { - minimizer: [ - new TerserPlugin({ - parallel: true, - terserOptions: { - mangle: false, - format: { - comments: 'all' - } - }, - extractComments: false, - }), - ], - } -}), gulpBundle.bind(null, false))); +gulp.task('build-bundle-verbose', gulp.series('build-creative-dev', makeWebpackPkg(makeVerbose(standaloneDebuggingConfig)), makeWebpackPkg(makeVerbose()), gulpBundle.bind(null, true))); // public tasks (dependencies are needed for each task since they can be ran on their own) gulp.task('test-only', test); diff --git a/webpack.conf.js b/webpack.conf.js index c934b09d439..1035e985b22 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -51,9 +51,6 @@ module.exports = { 'prebid-core': { import: './src/prebid.js' }, - 'debugging-standalone': { - import: './modules/debugging/standalone.js' - } }; const selectedModules = new Set(helpers.getArgModules()); @@ -130,15 +127,8 @@ module.exports = { ); const core = path.resolve('./src'); const paapiMod = path.resolve('./modules/paapi.js'); - const nodeModules = path.resolve('./node_modules'); return Object.assign(libraries, { - common_deps: { - name: 'common_deps', - test(module) { - return module.resource && module.resource.startsWith(nodeModules); - } - }, core: { name: 'chunk-core', test: (module) => { diff --git a/webpack.debugging.js b/webpack.debugging.js new file mode 100644 index 00000000000..3952649c557 --- /dev/null +++ b/webpack.debugging.js @@ -0,0 +1,30 @@ +var path = require('path'); + +module.exports = { + mode: 'production', + devtool: 'source-map', + resolve: { + modules: [ + path.resolve('.'), + 'node_modules' + ], + }, + entry: { + 'debugging-standalone': { + import: './modules/debugging/standalone.js', + } + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: path.resolve('./node_modules'), // required to prevent loader from choking non-Prebid.js node_modules + use: [ + { + loader: 'babel-loader' + } + ] + }, + ] + } +}; From 51c63b633e007968d9ddcb117aef5cfab169dc88 Mon Sep 17 00:00:00 2001 From: Pruthvi Muga <143863123+pruthvimuga@users.noreply.github.com> Date: Wed, 1 May 2024 12:47:55 +0530 Subject: [PATCH 686/760] Pubx.ai RTD Provider - Initial Release (#11300) * Add pubx RTD module * Add pubx RTD module documentation * Add basic tests for pubx RTD module * Fix the failing tests * Added logic to fetch floors and set to auction * Updated setDataToConfig to attach floors data to bidder requests object * pubx rtd module: * fetch floor rules * timeout defaults * floors config customizations * pubx on/off from floors response * move fetch floors to init and also wait for it before auction * TESTS: update * replace createFloorsDataForAuction to avoid circular dep - WIP * reset default floors when no floor response is received * TESTS: add test * tag integration - fetch floors api events * * Remove coonsole.log * move __pubxFloorRulesPromise__ to window * Remove unused var error * TESTS: add getBidRequestData, refactor stubs * TESTS: setFloorsApiStatus * TESTS: add fetchFloorRules * remove useRtd * make endpoint potional and take pubxId form provider config * TESTS: use xhr to fakeServer instead of sinon * make default data optional * update README * TEST: update tests * add integration example * remove floorProvider from default config * fix linting * update readme.md --------- Co-authored-by: Phaneendra Hegde Co-authored-by: Phaneendra Hegde --- .../gpt/pubxaiRtdProvider_example.html | 113 +++++ modules/.submodules.json | 1 + modules/pubxaiRtdProvider.js | 146 +++++++ modules/pubxaiRtdProvider.md | 68 +++ test/spec/modules/pubxaiRtdProvider_spec.js | 397 ++++++++++++++++++ 5 files changed, 725 insertions(+) create mode 100644 integrationExamples/gpt/pubxaiRtdProvider_example.html create mode 100644 modules/pubxaiRtdProvider.js create mode 100644 modules/pubxaiRtdProvider.md create mode 100644 test/spec/modules/pubxaiRtdProvider_spec.js diff --git a/integrationExamples/gpt/pubxaiRtdProvider_example.html b/integrationExamples/gpt/pubxaiRtdProvider_example.html new file mode 100644 index 00000000000..566cfe0c928 --- /dev/null +++ b/integrationExamples/gpt/pubxaiRtdProvider_example.html @@ -0,0 +1,113 @@ + + + Individual Ad Unit Refresh Example + + + + + + + + +

Individual Ad Unit Refresh Example

+
Div-1
+

+
+ +
+ + diff --git a/modules/.submodules.json b/modules/.submodules.json index 3913f3f5734..3b7f84af245 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -90,6 +90,7 @@ "optimeraRtdProvider", "oxxionRtdProvider", "permutiveRtdProvider", + "pubxaiRtdProvider", "qortexRtdProvider", "reconciliationRtdProvider", "relevadRtdProvider", diff --git a/modules/pubxaiRtdProvider.js b/modules/pubxaiRtdProvider.js new file mode 100644 index 00000000000..b958856df00 --- /dev/null +++ b/modules/pubxaiRtdProvider.js @@ -0,0 +1,146 @@ +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { submodule } from '../src/hook.js'; +import { deepAccess } from '../src/utils.js'; +/** + * This RTD module has a dependency on the priceFloors module. + * We utilize the createFloorsDataForAuction function from the priceFloors module to incorporate price floors data into the current auction. + */ +import { createFloorsDataForAuction } from './priceFloors.js'; // eslint-disable-line prebid/validate-imports + +const MODULE_NAME = 'realTimeData'; +const SUBMODULE_NAME = 'pubxai'; +window.__pubxFloorRulesPromise__ = null; +export const FloorsApiStatus = Object.freeze({ + IN_PROGRESS: 'IN_PROGRESS', + SUCCESS: 'SUCCESS', + ERROR: 'ERROR', +}); +export const FLOORS_EVENT_HANDLE = 'floorsApi'; +export const FLOORS_END_POINT = 'https://floor.pbxai.com/'; +export const FLOOR_PROVIDER = 'PubxFloorProvider'; + +export const getFloorsConfig = (provider, floorsResponse) => { + const floorsConfig = { + floors: { + enforcement: { floorDeals: true }, + data: floorsResponse, + }, + }; + const { floorMin, enforcement } = deepAccess(provider, 'params'); + if (floorMin) { + floorsConfig.floors.floorMin = floorMin; + } + if (enforcement) { + floorsConfig.floors.enforcement = enforcement; + } + return floorsConfig; +}; + +export const setFloorsConfig = (provider, data) => { + if (data) { + const floorsConfig = getFloorsConfig(provider, data); + config.setConfig(floorsConfig); + window.__pubxLoaded__ = true; + window.__pubxFloorsConfig__ = floorsConfig; + } else { + config.setConfig({ floors: window.__pubxPrevFloorsConfig__ }); + window.__pubxLoaded__ = false; + window.__pubxFloorsConfig__ = null; + } +}; + +export const setDefaultPriceFloors = (provider) => { + const { data } = deepAccess(provider, 'params'); + if (data !== undefined) { + data.floorProvider = FLOOR_PROVIDER; + setFloorsConfig(provider, data); + } +}; + +export const setPriceFloors = async (provider) => { + window.__pubxPrevFloorsConfig__ = config.getConfig('floors'); + setDefaultPriceFloors(provider); + return fetchFloorRules(provider) + .then((floorsResponse) => { + setFloorsConfig(provider, floorsResponse); + setFloorsApiStatus(FloorsApiStatus.SUCCESS); + }) + .catch((_) => { + setFloorsApiStatus(FloorsApiStatus.ERROR); + }); +}; + +export const setFloorsApiStatus = (status) => { + window.__pubxFloorsApiStatus__ = status; + window.dispatchEvent( + new CustomEvent(FLOORS_EVENT_HANDLE, { detail: { status } }) + ); +}; + +export const getUrl = (provider) => { + const { pubxId, endpoint } = deepAccess(provider, 'params'); + return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${ + window.location.href + }`; +}; + +export const fetchFloorRules = async (provider) => { + return new Promise((resolve, reject) => { + setFloorsApiStatus(FloorsApiStatus.IN_PROGRESS); + ajax(getUrl(provider), { + success: (responseText, response) => { + try { + if (response && response.response) { + const floorsResponse = JSON.parse(response.response); + resolve(floorsResponse); + } else { + resolve(null); + } + } catch (error) { + reject(error); + } + }, + error: (responseText, response) => { + reject(response); + }, + }); + }); +}; + +const init = (provider) => { + window.__pubxFloorRulesPromise__ = setPriceFloors(provider); + return true; +}; + +const getBidRequestData = (() => { + let floorsAttached = false; + return (reqBidsConfigObj, onDone) => { + if (!floorsAttached) { + createFloorsDataForAuction( + reqBidsConfigObj.adUnits, + reqBidsConfigObj.auctionId + ); + window.__pubxFloorRulesPromise__.then(() => { + createFloorsDataForAuction( + reqBidsConfigObj.adUnits, + reqBidsConfigObj.auctionId + ); + onDone(); + }); + floorsAttached = true; + } + }; +})(); + +export const pubxaiSubmodule = { + name: SUBMODULE_NAME, + init, + getBidRequestData, +}; + +export const beforeInit = () => { + submodule(MODULE_NAME, pubxaiSubmodule); +}; + +beforeInit(); diff --git a/modules/pubxaiRtdProvider.md b/modules/pubxaiRtdProvider.md new file mode 100644 index 00000000000..d7d89857c62 --- /dev/null +++ b/modules/pubxaiRtdProvider.md @@ -0,0 +1,68 @@ +## Overview + +- Module Name: pubX.ai RTD Provider +- Module Type: RTD Adapter +- Maintainer: phaneendra@pubx.ai + +## Description + +This RTD module, provided by pubx.ai, is used to set dynamic floors within Prebid. + +## Usage + +Ensure that the following modules are listed when building Prebid: `priceFloors`. +For example: + +```shell +gulp build --modules=priceFloors +``` + +To compile the RTD module into your Prebid build: + +```shell +gulp build --modules=rtdModule,pubxaiRtdProvider +``` + +To utilize the pubX.ai RTD module, add `realTimeData` with the parameters mentioned below to the Prebid config. + +```js +const AUCTION_DELAY = 100; +pbjs.setConfig({ + // rest of the config + ..., + realTimeData: { + auctionDelay: AUCTION_DELAY, + dataProviders: { + name: "pubxai", + waitForIt: true, + params: { + pubxId: ``, + endpoint: ``, // (optional) + floorMin: ``, // (optional) + enforcement: ``, // (optional) + data: `` // (optional) + } + } + } + // rest of the config + ..., +}); +``` + +## Parameters + +| Name | Type | Description | Default | +| :----------------- | :------ | :------------------------------------------------------------- | :------------------------- | +| name | String | Name of the real-time data module | Always `pubxai` | +| waitForIt | Boolean | Should be `true` if an `auctionDelay` is defined (optional) | `false` | +| params | Object | | | +| params.pubxId | String | Publisher ID | | +| params.endpoint | String | URL to retrieve floor data (optional) | `https://floor.pbxai.com/` | +| params.floorMin | Number | Minimum CPM floor (optional) | `None` | +| params.enforcement | Object | Enforcement behavior within the Price Floors Module (optional) | `None` | +| params.data | Object | Default floor data provided by pubX.ai (optional) | `None` | + +## What Should Change in the Bid Request? + +There are no direct changes in the bid request due to our RTD module, but floor configuration will be set using the price floors module. These changes will be reflected in adunit bids or bidder requests as floor data. + diff --git a/test/spec/modules/pubxaiRtdProvider_spec.js b/test/spec/modules/pubxaiRtdProvider_spec.js new file mode 100644 index 00000000000..b645b830246 --- /dev/null +++ b/test/spec/modules/pubxaiRtdProvider_spec.js @@ -0,0 +1,397 @@ +import * as priceFloors from '../../../modules/priceFloors'; +import { + FLOORS_END_POINT, + FLOORS_EVENT_HANDLE, + FloorsApiStatus, + beforeInit, + fetchFloorRules, + getFloorsConfig, + getUrl, + pubxaiSubmodule, + setDefaultPriceFloors, + setFloorsApiStatus, + setFloorsConfig, + setPriceFloors, +} from '../../../modules/pubxaiRtdProvider'; +import { config } from '../../../src/config'; +import * as hook from '../../../src/hook.js'; +import { server } from '../../mocks/xhr.js'; + +const getConfig = () => ({ + params: { + useRtd: true, + endpoint: 'http://pubxai.com:3001/floors', + data: { + currency: 'EUR', + floorProvider: 'PubxFloorProvider', + modelVersion: 'gpt-mvm_AB_0.50_dt_0.75_dwt_0.95_dnt_0.25_fm_0.50', + schema: { fields: ['gptSlot', 'mediaType'] }, + values: { '*|banner': 0.02 }, + }, + }, +}); + +const getFloorsResponse = () => ({ + currency: 'USD', + floorProvider: 'PubxFloorProvider', + modelVersion: 'gpt-mvm_AB_0.50_dt_0.75_dwt_0.95_dnt_0.25_fm_0.50', + schema: { fields: ['gptSlot', 'mediaType'] }, + values: { '*|banner': 0.02 }, +}); + +const resetGlobals = () => { + window.__pubxLoaded__ = undefined; + window.__pubxPrevFloorsConfig__ = undefined; + window.__pubxFloorsConfig__ = undefined; + window.__pubxFloorsApiStatus__ = undefined; + window.__pubxFloorRulesPromise__ = null; +}; + +const fakeServer = ( + fakeResponse = '', + providerConfig = undefined, + statusCode = 200 +) => { + const fakeResponseHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }; + const request = server.requests[0]; + request.respond( + statusCode, + fakeResponseHeaders, + fakeResponse ? JSON.stringify(fakeResponse) : '' + ); + return request; +}; + +const stubConfig = () => { + const stub = sinon.stub(config, 'setConfig'); + return stub; +}; + +describe('pubxaiRtdProvider', () => { + describe('beforeInit', () => { + it('should register RTD submodule provider', function () { + let submoduleStub = sinon.stub(hook, 'submodule'); + beforeInit(); + assert(submoduleStub.calledOnceWith('realTimeData', pubxaiSubmodule)); + submoduleStub.restore(); + }); + }); + describe('submodule', () => { + describe('name', function () { + it('should be pubxai', function () { + expect(pubxaiSubmodule.name).to.equal('pubxai'); + }); + }); + }); + describe('init', () => { + let stub; + beforeEach(() => { + resetGlobals(); + stub = stubConfig(); + }); + afterEach(() => { + stub.restore(); + }); + it('standard case - returns true', () => { + const initResult = pubxaiSubmodule.init({ params: { useRtd: true } }); + expect(initResult).to.be.true; + }); + it('setPriceFloors called when `useRtd` is true in the provider config', () => { + pubxaiSubmodule.init(getConfig()); + expect(window.__pubxLoaded__).to.equal(true); + }); + }); + describe('getBidRequestData', () => { + const reqBidsConfigObj = { + adUnits: [{ code: 'ad-slot-code-0' }], + auctionId: 'auction-id-0', + }; + let stub; + beforeEach(() => { + window.__pubxFloorRulesPromise__ = Promise.resolve(); + stub = sinon.stub(priceFloors, 'createFloorsDataForAuction'); + }); + afterEach(() => { + resetGlobals(); + stub.restore(); + }); + it('createFloorsDataForAuction called once before and once after __pubxFloorRulesPromise__. Also getBidRequestData executed only once', async () => { + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + assert(priceFloors.createFloorsDataForAuction.calledOnce); + await window.__pubxFloorRulesPromise__; + assert(priceFloors.createFloorsDataForAuction.calledTwice); + assert( + priceFloors.createFloorsDataForAuction.alwaysCalledWith( + reqBidsConfigObj.adUnits, + reqBidsConfigObj.auctionId + ) + ); + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + await window.__pubxFloorRulesPromise__; + assert(priceFloors.createFloorsDataForAuction.calledTwice); + }); + }); + describe('fetchFloorRules', () => { + const providerConfig = getConfig(); + const floorsResponse = getFloorsResponse(); + it('success with floors response', (done) => { + const promise = fetchFloorRules(providerConfig); + fakeServer(floorsResponse); + promise.then((res) => { + expect(res).to.deep.equal(floorsResponse); + done(); + }); + }); + it('success with no floors response', (done) => { + const promise = fetchFloorRules(providerConfig); + fakeServer(undefined); + promise.then((res) => { + expect(res).to.deep.equal(null); + done(); + }); + }); + it('API call error', (done) => { + const promise = fetchFloorRules(providerConfig); + fakeServer(undefined, undefined, 404); + promise + .then((res) => { + expect(true).to.be.false; + }) + .catch((e) => { + expect(e).to.not.be.undefined; + }) + .finally(() => { + done(); + }); + }); + it('Wrong API response', (done) => { + const promise = fetchFloorRules(providerConfig); + fakeServer('floorsResponse'); + promise + .then((res) => { + expect(true).to.be.false; + }) + .catch((e) => { + expect(e).to.not.be.undefined; + }) + .finally(() => { + done(); + }); + }); + }); + describe('setPriceFloors', () => { + const providerConfig = getConfig(); + const floorsResponse = getFloorsResponse(); + let stub; + beforeEach(() => { + resetGlobals(); + stub = stubConfig(); + }); + afterEach(() => { + stub.restore(); + }); + it('with floors response', (done) => { + const floorsPromise = setPriceFloors(providerConfig); + fakeServer(floorsResponse); + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal( + getFloorsConfig(providerConfig, providerConfig.params.data) + ); + floorsPromise.then(() => { + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal( + getFloorsConfig(providerConfig, floorsResponse) + ); + done(); + }); + }); + it('without floors response', (done) => { + const floorsPromise = setPriceFloors(providerConfig); + fakeServer(undefined); + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal( + getFloorsConfig(providerConfig, providerConfig.params.data) + ); + floorsPromise.then(() => { + expect(window.__pubxLoaded__).to.be.false; + expect(window.__pubxFloorsConfig__).to.deep.equal(null); + done(); + }); + }); + it('default floors', (done) => { + const floorsPromise = setPriceFloors(providerConfig); + fakeServer(undefined, undefined, 404); + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal( + getFloorsConfig(providerConfig, providerConfig.params.data) + ); + floorsPromise + .then(() => { + expect(true).to.be.false; + }) + .catch((e) => { + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal( + getFloorsConfig(providerConfig, providerConfig.params.data) + ); + }) + .finally(() => { + done(); + }); + }); + }); + describe('setFloorsConfig', () => { + const providerConfig = getConfig(); + let stub; + beforeEach(() => { + resetGlobals(); + stub = stubConfig(); + }); + afterEach(function () { + stub.restore(); + }); + it('non-empty floorResponse', () => { + const floorsResponse = getFloorsResponse(); + setFloorsConfig(providerConfig, floorsResponse); + const floorsConfig = getFloorsConfig(providerConfig, floorsResponse); + assert(config.setConfig.calledOnceWith(floorsConfig)); + expect(window.__pubxLoaded__).to.be.true; + expect(window.__pubxFloorsConfig__).to.deep.equal(floorsConfig); + }); + it('empty floorResponse', () => { + const floorsResponse = null; + setFloorsConfig(providerConfig, floorsResponse); + assert(config.setConfig.calledOnceWith({ floors: undefined })); + expect(window.__pubxLoaded__).to.be.false; + expect(window.__pubxFloorsConfig__).to.be.null; + }); + }); + describe('getFloorsConfig', () => { + let providerConfig; + const floorsResponse = getFloorsResponse(); + beforeEach(() => { + providerConfig = getConfig(); + }); + it('no customizations in the provider config', () => { + const result = getFloorsConfig(providerConfig, floorsResponse); + expect(result).to.deep.equal({ + floors: { + enforcement: { floorDeals: true }, + data: floorsResponse, + }, + }); + }); + it('only floormin in the provider config', () => { + providerConfig.params.floorMin = 2; + expect(getFloorsConfig(providerConfig, floorsResponse)).to.deep.equal({ + floors: { + enforcement: { floorDeals: true }, + floorMin: 2, + data: floorsResponse, + }, + }); + }); + it('only enforcement in the provider config', () => { + providerConfig.params.enforcement = { + bidAdjustment: true, + enforceJS: false, + }; + expect(getFloorsConfig(providerConfig, floorsResponse)).to.deep.equal({ + floors: { + enforcement: { + bidAdjustment: true, + enforceJS: false, + }, + data: floorsResponse, + }, + }); + }); + it('both floorMin and enforcement in the provider config', () => { + providerConfig.params.floorMin = 2; + providerConfig.params.enforcement = { + bidAdjustment: true, + enforceJS: false, + }; + expect(getFloorsConfig(providerConfig, floorsResponse)).to.deep.equal({ + floors: { + enforcement: { + bidAdjustment: true, + enforceJS: false, + }, + floorMin: 2, + data: floorsResponse, + }, + }); + }); + }); + describe('setDefaultPriceFloors', () => { + let stub; + beforeEach(() => { + resetGlobals(); + stub = stubConfig(); + }); + afterEach(function () { + stub.restore(); + }); + it('should set default floors config', () => { + const providerConfig = getConfig(); + setDefaultPriceFloors(providerConfig); + assert( + config.setConfig.calledOnceWith( + getFloorsConfig(providerConfig, providerConfig.params.data) + ) + ); + expect(window.__pubxLoaded__).to.be.true; + }); + }); + describe('setFloorsApiStatus', () => { + let stub; + beforeEach(() => { + resetGlobals(); + stub = sinon.stub(window, 'dispatchEvent'); + }); + afterEach(function () { + stub.restore(); + }); + it('set status', () => { + setFloorsApiStatus(FloorsApiStatus.SUCCESS); + expect(window.__pubxFloorsApiStatus__).to.equal(FloorsApiStatus.SUCCESS); + }); + it('dispatch event', () => { + setFloorsApiStatus(FloorsApiStatus.SUCCESS); + assert( + window.dispatchEvent.calledOnceWith( + new CustomEvent(FLOORS_EVENT_HANDLE, { + detail: { status: FloorsApiStatus.SUCCESS }, + }) + ) + ); + }); + }); + describe('getUrl', () => { + const provider = { + name: 'pubxai', + waitForIt: true, + params: { + pubxId: '12345', + }, + }; + it('floors end point', () => { + expect(FLOORS_END_POINT).to.equal('https://floor.pbxai.com/'); + }); + it('standard case', () => { + expect(getUrl(provider)).to.equal( + `https://floor.pbxai.com/?pubxId=12345&page=${window.location.href}` + ); + }); + it('custom url provided', () => { + provider.params.endpoint = 'https://custom.floor.com/'; + expect(getUrl(provider)).to.equal( + `https://custom.floor.com/?pubxId=12345&page=${window.location.href}` + ); + }); + }); +}); From 0e58601ab5e4e4c60120c2a780bc7b0156ffefa9 Mon Sep 17 00:00:00 2001 From: Keith Petri <74626343+KEPlockr@users.noreply.github.com> Date: Wed, 1 May 2024 04:24:47 -0400 Subject: [PATCH 687/760] lockr AIM user module: initial release (#11159) * Adding Lockr AIM module and tests * formatting * Fix formatting * format * fix formatting * add paapi back * added markdown file * 20240324 Fixed Storage Manager | lockrAIMIdSystem.js * Updated to use Prebid Storage Manager | lockrAIMIdSystem_shared.js * added the fix for the comments * removed the unnecessary console --------- Co-authored-by: chris-lockr Co-authored-by: avin-lockr --- modules/.submodules.json | 13 +- modules/lockrAIMIdSystem.js | 165 +++++++++++++++++++++ modules/lockrAIMIdSystem.md | 165 +++++++++++++++++++++ test/spec/modules/lockrAIMIdSystem_spec.js | 72 +++++++++ 4 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 modules/lockrAIMIdSystem.js create mode 100644 modules/lockrAIMIdSystem.md create mode 100644 test/spec/modules/lockrAIMIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 3b7f84af245..8409ea9918d 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -4,6 +4,7 @@ "33acrossIdSystem", "admixerIdSystem", "adtelligentIdSystem", + "adqueryIdSystem", "amxIdSystem", "britepoolIdSystem", "connectIdSystem", @@ -13,9 +14,11 @@ "deepintentDpesIdSystem", "dmdIdSystem", "fabrickIdSystem", + "freepassIdSystem", "hadronIdSystem", "id5IdSystem", "ftrackIdSystem", + "gravitoIdSystem", "identityLinkIdSystem", "idxIdSystem", "imuIdSystem", @@ -23,13 +26,16 @@ "justIdSystem", "kinessoIdSystem", "liveIntentIdSystem", + "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", "mwOpenLinkIdSystem", + "mygaruIdSystem", "naveggIdSystem", "netIdSystem", "novatiqIdSystem", "oneKeyIdSystem", + "operaadsIdSystem", "parrableIdSystem", "pubProvidedIdSystem", "publinkIdSystem", @@ -44,12 +50,7 @@ "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem", - "adqueryIdSystem", - "gravitoIdSystem", - "freepassIdSystem", - "operaadsIdSystem", - "mygaruIdSystem" + "zeotapIdPlusIdSystem" ], "adpod": [ "freeWheelAdserverVideo", diff --git a/modules/lockrAIMIdSystem.js b/modules/lockrAIMIdSystem.js new file mode 100644 index 00000000000..de0435a2255 --- /dev/null +++ b/modules/lockrAIMIdSystem.js @@ -0,0 +1,165 @@ +/** + * This module adds lockr AIM ID support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/lockrAIMIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { logInfo, logWarn } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { gppDataHandler } from '../src/adapterManager.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').lockrAIMId} lockrAIMId + */ + +const MODULE_NAME = 'lockrAIMId' +const LOG_PRE_FIX = 'lockr-AIM: '; + +const AIM_PROD_URL = 'https://identity.loc.kr'; + +export const lockrAIMCodeVersion = '1.0'; + +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }) + +function createLogger(logger, prefix) { + return function (...strings) { + logger(prefix + ' ', ...strings); + } +} + +const _logInfo = createLogger(logInfo, LOG_PRE_FIX); +const _logWarn = createLogger(logWarn, LOG_PRE_FIX); + +/** @type {Submodule} */ +export const lockrAIMSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + init() { + _logInfo('lockrAIM Initialization complete'); + }, + + /** + * performs action to obtain id and return a value. + * @function + * @param {SubmoduleConfig} [config] + * @param {ConsentData|undefined} consentData + * @returns {lockrAIMId} + */ + getId(config, consentData) { + if (consentData?.gdprApplies === true) { + _logWarn('lockrAIM is not intended for use where GDPR applies. The lockrAIM module will not run'); + return undefined; + } + + const gppConsent = gppDataHandler.getConsentData(); + let gppString = ''; + if (gppConsent) { + gppString = gppConsent.gppString; + } + const mappedConfig = { + appID: config?.params?.appID, + email: config?.params?.email, + baseUrl: AIM_PROD_URL, + }; + + _logInfo('lockr AIM configurations loaded and mapped.', mappedConfig); + if (!mappedConfig.appID || !mappedConfig.email) { + return undefined; + } + const tokenGenerator = new LockrAIMApiClient(mappedConfig, _logInfo, _logWarn, storage, gppString); + const result = tokenGenerator.generateToken(); + _logInfo('lockr AIM results generated'); + return result; + } +} + +class LockrAIMApiClient { + static expiryDateKeys = []; + static canRefreshToken = false; + + constructor(opts, logInfo, logWarn, prebidStorageManager, gppString) { + this._baseUrl = opts.baseUrl; + this._appID = opts.appID; + this._email = opts.email; + this._logInfo = logInfo; + this._logWarn = logWarn; + this._gppString = gppString; + this.prebidStorageManager = prebidStorageManager; + LockrAIMApiClient.expiryDateKeys = this.prebidStorageManager.getDataFromLocalStorage('lockr_expiry_keys') ? JSON.parse(this.prebidStorageManager.getDataFromLocalStorage('lockr_expiry_keys')) : [] + this.initializeRefresher(); + } + + async generateToken(type = 'email', value) { + const url = this._baseUrl + '/publisher/app/v1/identityLockr/generate-tokens'; + let rejectPromise; + const promise = new Promise((resolve, reject) => { + rejectPromise = reject; + }); + const requestBody = { + appID: this._appID, + data: { + type: type, + value: value ?? this._email, + gppString: this._gppString, + } + } + this._logInfo('Sending the token generation request') + ajax(url, { + success: (responseText) => { + try { + const response = JSON.parse(responseText); + LockrAIMApiClient.canRefreshToken = false; + const token = response.lockrMappingToken; + this.prebidStorageManager.setDataInLocalStorage('ilui', token); + response.data.forEach(cookieitem => { + const settings = cookieitem?.settings; + this.prebidStorageManager.setDataInLocalStorage(`${cookieitem.key_name}_expiry`, cookieitem.identity_expires); + if (!LockrAIMApiClient.expiryDateKeys.includes(`${cookieitem.key_name}_expiry`)) { + LockrAIMApiClient.expiryDateKeys.push(`${cookieitem.key_name}_expiry`); + } + this.prebidStorageManager.setDataInLocalStorage('lockr_expiry_keys', JSON.stringify(LockrAIMApiClient.expiryDateKeys)); + if (!settings?.dropLocalStorage) { + this.prebidStorageManager.setDataInLocalStorage(cookieitem.key_name, cookieitem.advertising_token); + } + if (!settings?.dropCookie) { + this.prebidStorageManager.setCookie(cookieitem.key_name, cookieitem.advertising_token); + } + }); + LockrAIMApiClient.canRefreshToken = true; + return; + } catch (_err) { + this._logWarn(_err); + rejectPromise(responseText); + LockrAIMApiClient.canRefreshToken = true; + } + } + }, JSON.stringify(requestBody), { method: 'POST', contentType: 'application/json;charset=UTF-8' }); + return promise; + } + + async initializeRefresher() { + setInterval(() => { + LockrAIMApiClient.expiryDateKeys.forEach(expiryItem => { + const currentMillis = new Date().getTime(); + const dateMillis = this.prebidStorageManager.getDataFromLocalStorage(expiryItem); + if (currentMillis > dateMillis && dateMillis !== null && this.prebidStorageManager.getDataFromLocalStorage('ilui') && LockrAIMApiClient.canRefreshToken) { + this.generateToken('refresh', this.prebidStorageManager.getDataFromLocalStorage('ilui')); + } + }) + }, 1000); + } +} + +// Register submodule for userId +submodule('userId', lockrAIMSubmodule); diff --git a/modules/lockrAIMIdSystem.md b/modules/lockrAIMIdSystem.md new file mode 100644 index 00000000000..95eee5bc5e3 --- /dev/null +++ b/modules/lockrAIMIdSystem.md @@ -0,0 +1,165 @@ +## **lockr AIM** + +Alternative Identity Manager (AIM) is a unified container for identity and data management. +With AIM’s self-service platform, publishers seamlessly integrate and activate alternative IDs like LiveRamp’s Authenticated Traffic Solution (ATS), Unified ID 2.0 (UID2), ID5 and more. The burden of due diligence and maintenance, coupled with the benefits of server-side calls result in the adoption of multiple alternative IDs, clean rooms like InfoSum and CDPs like Blueconic based on their specific needs. + +### **Account Creation | AIM** + +Sign up for an [Identity lockr account.](https://sso.loc.kr/console/signup) +Setup your app and activate the AIM library. +Compile Prebid with the appropriate configurations, and deploy. + +### **Configuration | AIM** + +First, make sure to add the lockr’s AIM submodule to your Prebid.js package with: +The following configuration parameters are available: +AIM supports all Single Sign On functions, newsletter registrations, UTM parameters, etc. For the sake of clarity, a few examples are shared below. +**Google oAuth: ** +If you are using Google oAuth (_as an example_), the onSignIn function will subsequently call window.lockr.setAdditionalData function and include a raw email. + +``` +function onSignIn(googleUser) { + pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'lockrAIMId', + params: { + email: 'john@example.com', + appID: 'e84afc5f-4adf-4144-949f-1de5bd151fcc' + } + }] + } + }); +} +``` + +**Facebook oAuth:** +If you are using Facebook Login (_as an example_), the statusChangeCallback function will subsequently call window.lockr.setAdditionalData function and include a raw email. + +``` +function statusChangeCallback(response) { + console.log('statusChangeCallback'); + console.log(response); + if(response.status === 'connected'){ + pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'lockrAIMId', + params: { + email: 'john@example.com', + appID: 'e84afc5f-4adf-4144-949f-1de5bd151fcc' + } + }] + } + }); + }else{ + document.getElementById('status').innerHTML = 'Please login'; + } +} +``` + +**Note:** The above code can be triggered from anywhere on the domain (i.e. a subscription form). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Param + Scope + Type + Description + Example +
name + Required + String + The name of this module: "lockrAIMId" + "lockrAIMId" +
params + Required + Object + Details for the configuration. + +
params.email + Required + String + Email address for identity tokens. + test@example.com +
params.appID + Required + String + Identity lockr appID + test@example.com +
+ +**lockr AIM Example** + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'lockrAIMId', + params: { + email: 'test@example.com', + appID: 'e84afc5f-4adf-4144-949f-1de5bd151fcc' + } + }] + } +}); +``` + +_Note_: lockr’s AIM self-service interface empowers publishers with the ability to pass the alternative IDs activated back to the client as local storage or as a first party cookie. Each Identity Provider can be individually set to restrict from client-side delivery and instead be retained as an authentication event within Identity lockr. In this case no data is lost, but instead maintained for automated or manual sharing to any Data Endpoint. + +**Troubleshooting and Error handling:** + +1. Navigate to the domain where Prebid.js Library is integrated. +2. Go to the 'Network' tab of your Developer Tools. Search for “prebid.js” +3. In the application tab, you can confirm any activated Identity Provider (if client-side storage is turned on in AIM’s Identity Provider settings). +4. Debugging: + Enable the debug flag to true in the setConfig call: + +``` +pbjs.setConfig({ + debug: true, + userSync: { + userIds: [{ + name: 'lockrAIMId', + params: { + email: 'test@example.com', + appID: 'e84afc5f-4adf-4144-949f-1de5bd151fcc' + } + }] + } +}); +``` diff --git a/test/spec/modules/lockrAIMIdSystem_spec.js b/test/spec/modules/lockrAIMIdSystem_spec.js new file mode 100644 index 00000000000..c886fb305c2 --- /dev/null +++ b/test/spec/modules/lockrAIMIdSystem_spec.js @@ -0,0 +1,72 @@ +/* eslint-disable no-console */ +/* eslint-disable quotes */ +import * as lockrAIMSystem from "../../../modules/lockrAIMIdSystem.js"; +import { hook } from "../../../src/hook.js"; +import { expect } from "chai"; +import { coreStorage } from "../../../modules/userId/index.js"; + +const defaultConfig = { + appID: "3b5a0f6c-7e91-11ec-b9c7-e330d98440a7", + email: "example@test.com", +}; + +const LIVE_RAMP_COOKIE = "_lr_env"; +const UID2_COOKIE = "_uid2_advertising_token"; +const ID5_COOKIE = "id5id"; +const dummyTokenValue = 'Success OK'; + +const getDataFromStorage = (dataKey) => { + return coreStorage.getDataFromLocalStorage(dataKey); +}; + +const mockHTTPRequestSuccess = (key, value) => { + coreStorage.setDataInLocalStorage(key, value); +} + +describe("lockr AIM ID System", function () { + before(() => { + hook.ready(); + }); + + describe("Check for invalid publisher config and GDPR", function () { + it("Should fail for invalid config", async function () { + // no Config + const idResult = await lockrAIMSystem.lockrAIMSubmodule.getId(); + expect(idResult).is.eq(undefined); + const idResultNoConfig = await lockrAIMSystem.lockrAIMSubmodule.getId({}); + expect(idResultNoConfig).is.eq(undefined); + }); + + it("Does not generate the token, when GDPR is enabled", async function () { + // Mocking the GDPR + const idResult = await lockrAIMSystem.lockrAIMSubmodule.getId( + defaultConfig, + { gdprApplies: true } + ); + expect(idResult).is.eq(undefined); + }); + }); + + describe("Generates the token successfully", function () { + it("Generates the UID2 token successfully", async function () { + mockHTTPRequestSuccess(UID2_COOKIE, dummyTokenValue); + await lockrAIMSystem.lockrAIMSubmodule.getId(defaultConfig); + const uid2Cookie = getDataFromStorage(UID2_COOKIE); + expect(uid2Cookie).is.eq(dummyTokenValue); + }); + + it("Generates the ID5 token successfully", async function () { + mockHTTPRequestSuccess(ID5_COOKIE, dummyTokenValue); + await lockrAIMSystem.lockrAIMSubmodule.getId(defaultConfig); + const id5Cookie = getDataFromStorage(ID5_COOKIE); + expect(id5Cookie).is.eq(dummyTokenValue); + }); + + it("Generates the liveramp token successfully", async function () { + mockHTTPRequestSuccess(LIVE_RAMP_COOKIE, dummyTokenValue); + await lockrAIMSystem.lockrAIMSubmodule.getId(defaultConfig); + const liveRampCookie = getDataFromStorage(LIVE_RAMP_COOKIE); + expect(liveRampCookie).is.eq(dummyTokenValue); + }); + }); +}); From 670a8594d13b998a68de4064668125b76cfd5917 Mon Sep 17 00:00:00 2001 From: IgorKulemzin <65122039+IgorKulemzin@users.noreply.github.com> Date: Wed, 1 May 2024 16:17:43 +0300 Subject: [PATCH 688/760] OpenWeb Bid Adapter : remove multi currency support (#11430) * removed currency from getFloor * removed currency from generateBidParameters * removed currency from optional params in md * removed currency from test --------- Co-authored-by: Dedi Sidi --- modules/openwebBidAdapter.js | 10 ++++------ modules/openwebBidAdapter.md | 4 +--- test/spec/modules/openwebBidAdapter_spec.js | 10 ---------- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index 39bd50f61a9..cf0334b7f29 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -141,16 +141,16 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid, mediaType, currency) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: currency, + currency: DEFAULT_CURRENCY, mediaType: mediaType, size: '*' }); - return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** @@ -286,7 +286,6 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); - const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { @@ -297,8 +296,7 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - currency: currency, - floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), diff --git a/modules/openwebBidAdapter.md b/modules/openwebBidAdapter.md index 36c1f0ca6c5..5450182265c 100644 --- a/modules/openwebBidAdapter.md +++ b/modules/openwebBidAdapter.md @@ -24,8 +24,6 @@ The adapter supports Video and Display demand. | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false -| `currency` | optional | String | 3 letters currency | "EUR" - # Test Parameters ```javascript @@ -50,4 +48,4 @@ var adUnits = [ }] } ]; -``` \ No newline at end of file +``` diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index 5a0264f00b8..4586ce78135 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -178,16 +178,6 @@ describe('openwebAdapter', function () { 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: { From 16616c5f08b10ffacb8ff8747440bbc667441617 Mon Sep 17 00:00:00 2001 From: Dedi <32620962+Dedis23@users.noreply.github.com> Date: Wed, 1 May 2024 16:39:05 +0300 Subject: [PATCH 689/760] STN Bid Adapter: remove multi currency support (#11429) * removed currency from getFloor * removed currency from generateBidParameters * removed currency from optional params in md * removed currency from test --- modules/stnBidAdapter.js | 11 ++++------- modules/stnBidAdapter.md | 1 - test/spec/modules/stnBidAdapter_spec.js | 10 ---------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index 633e941b3b7..42b69ee7c2b 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -139,19 +139,18 @@ registerBidder(spec); * Get floor price * @param bid {bid} * @param mediaType {String} - * @param currency {String} * @returns {Number} */ -function getFloor(bid, mediaType, currency) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: currency, + currency: DEFAULT_CURRENCY, mediaType: mediaType, size: '*' }); - return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** @@ -289,7 +288,6 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); - const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { @@ -300,8 +298,7 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - currency: currency, - floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), diff --git a/modules/stnBidAdapter.md b/modules/stnBidAdapter.md index 46374c5a53d..90b0b58e34b 100644 --- a/modules/stnBidAdapter.md +++ b/modules/stnBidAdapter.md @@ -24,7 +24,6 @@ The adapter supports Video(instream) & Banner. | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | true -| `currency` | optional | String | 3 letters currency | "EUR" # Test Parameters ```javascript diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index deba87baac2..95cab32e41d 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -178,16 +178,6 @@ describe('stnAdapter', function () { 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: { From c56184373df725c035751d0727025f83562ac10f Mon Sep 17 00:00:00 2001 From: Dedi <32620962+Dedis23@users.noreply.github.com> Date: Wed, 1 May 2024 16:47:24 +0300 Subject: [PATCH 690/760] MinuteMedia Bid Adapter: remove multi currency support (#11428) * removed currency from getFloor * removed currency from generateBidParameters * removed currency from test * removed currency from optional params in md * removed currency --- modules/minutemediaBidAdapter.js | 10 ++++------ modules/minutemediaBidAdapter.md | 1 - test/spec/modules/minutemediaBidAdapter_spec.js | 10 ---------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index 81200f28a6f..d14af07210e 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -141,16 +141,16 @@ registerBidder(spec); * @param bid {bid} * @returns {Number} */ -function getFloor(bid, mediaType, currency) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: currency, + currency: DEFAULT_CURRENCY, mediaType: mediaType, size: '*' }); - return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** @@ -286,7 +286,6 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); - const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { @@ -297,8 +296,7 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - currency: currency, - floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), diff --git a/modules/minutemediaBidAdapter.md b/modules/minutemediaBidAdapter.md index fdfdf1b32bf..66b54adaf0e 100644 --- a/modules/minutemediaBidAdapter.md +++ b/modules/minutemediaBidAdapter.md @@ -24,7 +24,6 @@ The adapter supports Video(instream) & Banner. | `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00 | `placementId` | optional | String | A unique placement identifier | "12345678" | `testMode` | optional | Boolean | This activates the test mode | false -| `currency` | optional | String | 3 letters currency | "EUR" # Test Parameters ```javascript diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index d5d6cdc5449..cf50ad2cd0a 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -178,16 +178,6 @@ describe('minutemediaAdapter', function () { 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: { From 0bb0ad3e55e48217719d746ddccc284d9cce3beb Mon Sep 17 00:00:00 2001 From: Dedi <32620962+Dedis23@users.noreply.github.com> Date: Wed, 1 May 2024 16:52:01 +0300 Subject: [PATCH 691/760] Rise Bid Adapter : remove multi currency support (#11427) * removed currency test * removed currency from optional params in md * removed currency from generateBidParameters * removed currency from getFloor --- modules/riseBidAdapter.js | 11 ++++------- modules/riseBidAdapter.md | 2 -- test/spec/modules/riseBidAdapter_spec.js | 10 ---------- 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index 82790805303..b176ab08aaf 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -146,19 +146,18 @@ registerBidder(spec); * Get floor price * @param bid {bid} * @param mediaType {string} - * @param currency {string} * @returns {Number} */ -function getFloor(bid, mediaType, currency) { +function getFloor(bid, mediaType) { if (!isFn(bid.getFloor)) { return 0; } let floorResult = bid.getFloor({ - currency: currency, + currency: DEFAULT_CURRENCY, mediaType: mediaType, size: '*' }); - return floorResult.currency === currency && floorResult.floor ? floorResult.floor : 0; + return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; } /** @@ -296,7 +295,6 @@ function generateBidParameters(bid, bidderRequest) { const {params} = bid; const mediaType = isBanner(bid) ? BANNER : VIDEO; const sizesArray = getSizesArray(bid, mediaType); - const currency = params.currency || config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY; // fix floor price in case of NAN if (isNaN(params.floorPrice)) { params.floorPrice = 0; @@ -306,8 +304,7 @@ function generateBidParameters(bid, bidderRequest) { mediaType, adUnitCode: getBidIdParameter('adUnitCode', bid), sizes: sizesArray, - currency: currency, - floorPrice: Math.max(getFloor(bid, mediaType, currency), params.floorPrice), + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), bidId: getBidIdParameter('bidId', bid), bidderRequestId: getBidIdParameter('bidderRequestId', bid), loop: getBidIdParameter('bidderRequestsCount', bid), diff --git a/modules/riseBidAdapter.md b/modules/riseBidAdapter.md index ac0ea559c88..94d36a08510 100644 --- a/modules/riseBidAdapter.md +++ b/modules/riseBidAdapter.md @@ -26,8 +26,6 @@ The adapter supports Video(instream). | `testMode` | optional | Boolean | This activates the test mode | false | `rtbDomain` | optional | String | Sets the seller end point | "www.test.com" | `is_wrapper` | private | Boolean | Please don't use unless your account manager asked you to | false -| `currency` | optional | String | 3 letters currency | "EUR" - # Test Parameters ```javascript diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index ec9309fd4ae..3cb5cb5c154 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -201,16 +201,6 @@ describe('riseAdapter', function () { 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: { From 3cb4c1631abd0f31d359ad24fd6c5656819cf804 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 1 May 2024 14:51:08 +0000 Subject: [PATCH 692/760] Prebid 8.47.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c9bddfaf06..1dffbcec331 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0-pre", + "version": "8.47.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 7236e62b5d4..6e5022bcece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0-pre", + "version": "8.47.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 54c87ba4ac6c0d1de14e8855e10edf6197e9ce57 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Wed, 1 May 2024 14:51:09 +0000 Subject: [PATCH 693/760] Increment version to 8.48.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1dffbcec331..e7621c210f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0", + "version": "8.48.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6e5022bcece..acac0a99da7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.47.0", + "version": "8.48.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From f4d0ef011491811e5bb426ffc2e92038ea00b233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 09:44:08 -0600 Subject: [PATCH 694/760] Bump ejs from 3.1.9 to 3.1.10 (#11432) Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e7621c210f1..261cbdb1fb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "prebid.js", - "version": "8.43.0-pre", + "version": "8.48.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.16.7", @@ -10979,9 +10979,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { "jake": "^10.8.5" @@ -37620,9 +37620,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "requires": { "jake": "^10.8.5" From 3350dee8ee32e07941d7871caf66b75bd66dc93b Mon Sep 17 00:00:00 2001 From: ecdrsvc <82906140+ecdrsvc@users.noreply.github.com> Date: Wed, 1 May 2024 17:16:33 -0400 Subject: [PATCH 695/760] Add lmpIdSystem userId submodule (#11431) Co-authored-by: Antoine Niek --- modules/.submodules.json | 1 + modules/lmpIdSystem.js | 61 +++++++++++++ modules/lmpIdSystem.md | 27 ++++++ modules/userId/userId.md | 3 + test/spec/modules/lmpIdSystem_spec.js | 124 ++++++++++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 modules/lmpIdSystem.js create mode 100644 modules/lmpIdSystem.md create mode 100644 test/spec/modules/lmpIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index 8409ea9918d..9dfeaf910f8 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -26,6 +26,7 @@ "justIdSystem", "kinessoIdSystem", "liveIntentIdSystem", + "lmpIdSystem", "lockrAIMIdSystem", "lotamePanoramaIdSystem", "merkleIdSystem", diff --git a/modules/lmpIdSystem.js b/modules/lmpIdSystem.js new file mode 100644 index 00000000000..b6dcae3118b --- /dev/null +++ b/modules/lmpIdSystem.js @@ -0,0 +1,61 @@ +/** + * This module adds lmpId support to the User ID module + * The {@link module:modules/userId} module is required. + * @module modules/lmpIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const MODULE_NAME = 'lmpid'; +const STORAGE_KEY = '__lmpid'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + +function readFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(STORAGE_KEY) : null; +} + +function getLmpid() { + return window[STORAGE_KEY] || readFromLocalStorage(); +} + +/** @type {Submodule} */ +export const lmpIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: MODULE_NAME, + + /** + * decode the stored id value for passing to bid requests + * @function + * @param { string | undefined } value + * @return { {lmpid: string} | undefined } + */ + decode(value) { + return value ? { lmpid: value } : undefined; + }, + + /** + * Retrieve the LMPID + * @function + * @param {SubmoduleConfig} config + * @return {{id: string | undefined} | undefined} + */ + getId(config) { + const id = getLmpid(); + return id ? { id } : undefined; + }, + + eids: { + 'lmpid': { + source: 'loblawmedia.ca', + atype: 3 + }, + } +}; + +submodule('userId', lmpIdSubmodule); diff --git a/modules/lmpIdSystem.md b/modules/lmpIdSystem.md new file mode 100644 index 00000000000..a56c9dbb3d6 --- /dev/null +++ b/modules/lmpIdSystem.md @@ -0,0 +1,27 @@ +# LMPID + +The Loblaw Media Private ID (LMPID) is the Loblaw Advance identity solution deployed by its media partners. LMPID leverages encrypted user registration information to provide a privacy-conscious, secure, and reliable identifier to power Loblaw Advance's digital advertising ecosystem. + +## LMPID Registration + +If you're a media company looking to partner with Loblaw Advance, please reach out to us through our [Contact page](https://www.loblawadvance.ca/contact-us) + +## LMPID Configuration + +First, make sure to add the LMPID submodule to your Prebid.js package with: + +``` +gulp build --modules=lmpIdSystem,userId +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'lmpid' + }] + } +}); +``` diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 7a01e128814..1ec109ff309 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -358,6 +358,9 @@ pbjs.setConfig({ }, { name: 'naveggId', + }, + { + name: 'lmpid', }], syncDelay: 5000 } diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js new file mode 100644 index 00000000000..37c7351f143 --- /dev/null +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -0,0 +1,124 @@ +import { expect } from 'chai'; +import { find } from 'src/polyfill.js'; +import { config } from 'src/config.js'; +import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; +import { mockGdprConsent } from '../../helpers/consentData.js'; + +function getConfigMock() { + return { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'lmpid' + }] + } + } +} + +function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: { banner: {}, native: {} }, + sizes: [ + [300, 200], + [300, 600] + ], + bids: [{ + bidder: 'sampleBidder', + params: { placementId: 'banner-only-bidder' } + }] + }; +} + +describe('LMPID System', () => { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let windowLmpidStub; + + beforeEach(() => { + window.__lmpid = undefined; + windowLmpidStub = sinon.stub(window, '__lmpid'); + getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + }); + + afterEach(() => { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + windowLmpidStub.restore(); + }); + + describe('LMPID: test "getId" method', () => { + it('prefers the window cached LMPID', () => { + localStorageIsEnabledStub.returns(true); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + windowLmpidStub.value('lmpid'); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'lmpid' }); + }); + + it('fallbacks on localStorage when window cache is falsy', () => { + localStorageIsEnabledStub.returns(true); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + windowLmpidStub.value(''); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' }); + + windowLmpidStub.value(false); + expect(lmpIdSubmodule.getId()).to.deep.equal({ id: 'stored-lmpid' }); + }); + + it('fallbacks only if localStorageIsEnabled', () => { + localStorageIsEnabledStub.returns(false); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + + expect(lmpIdSubmodule.getId()).to.be.undefined; + }); + }); + + describe('LMPID: test "decode" method', () => { + it('provides the lmpid from a stored object', () => { + expect(lmpIdSubmodule.decode('lmpid')).to.deep.equal({ lmpid: 'lmpid' }); + }); + }); + + describe('LMPID: requestBids hook', () => { + let adUnits; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockGdprConsent(sandbox); + adUnits = [getAdUnitMock()]; + init(config); + setSubmoduleRegistry([lmpIdSubmodule]); + getDataFromLocalStorageStub.withArgs('__lmpid').returns('stored-lmpid'); + localStorageIsEnabledStub.returns(true); + config.setConfig(getConfigMock()); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('when a stored LMPID exists it is added to bids', (done) => { + requestBidsHook(() => { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.lmpid'); + expect(bid.userId.lmpid).to.equal('stored-lmpid'); + const lmpidAsEid = find(bid.userIdAsEids, e => e.source == 'loblawmedia.ca'); + expect(lmpidAsEid).to.deep.equal({ + source: 'loblawmedia.ca', + uids: [{ + id: 'stored-lmpid', + atype: 3, + }] + }); + }); + }); + done(); + }, { adUnits }); + }); + }); +}); From 8c72bc2239e3f9adbb14b40ec211b2be8eb8a0fb Mon Sep 17 00:00:00 2001 From: Giuseppe Cera <117671343+giuseppe-exads@users.noreply.github.com> Date: Thu, 2 May 2024 14:31:04 +0100 Subject: [PATCH 696/760] EXADS Bid Adapter: initial release (#11284) * First commit * fix: readme.md * fix: changed exads urls * fix: Tools and suggestions related to the doc * fix: from code review * fix: from code review * fix: from code review * fix: error from code review - native example * fox: from code review * fix: from code review * fix: from code review * fix: native img set as mandatory * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: bidfloor and bidfloorcur set as optional * fix: dsa * fix: mananing multiple responses * fix: unit test after code review * fix: fixing native snippet code * fix: from code review * fix: video events after code review * fix: video module into documentation * fix: impression tracker for native * fix: afeter code review * fix: unit tests * fix: added badv and bcat * fix: video -> mimes and protocols * fix * fix: removed image_output and video_output params, forcing always html for rtb banner * fix: gulp * fix: added site.name * fix: removed EXADS dir * fix: after linting * fix: unit tests * fix: final dsa solution * fix: dsa * fix: fix instream example * fix: doc media type context * fix: documented the endpoint param into native section * fix: related to markdown lint validation (#2) * fix: from CR (#3) --------- Co-authored-by: tfoliveira --- modules/exadsBidAdapter.js | 514 ++++++++++++++++++ modules/exadsBidAdapter.md | 484 +++++++++++++++++ test/spec/modules/exadsBidAdapter_spec.js | 632 ++++++++++++++++++++++ 3 files changed, 1630 insertions(+) create mode 100644 modules/exadsBidAdapter.js create mode 100644 modules/exadsBidAdapter.md create mode 100644 test/spec/modules/exadsBidAdapter_spec.js diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js new file mode 100644 index 00000000000..31d75db470d --- /dev/null +++ b/modules/exadsBidAdapter.js @@ -0,0 +1,514 @@ +import * as utils from '../src/utils.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +const BIDDER = 'exadsadserver'; + +const PARTNERS = { + ORTB_2_4: 'ortb_2_4' +}; + +const GVL_ID = 1084; + +const htmlImageOutput = 'html'; +const htmlVideoOutput = 'html'; + +const adPartnerHandlers = { + [PARTNERS.ORTB_2_4]: { + request: handleReqORTB2Dot4, + response: handleResORTB2Dot4, + validation: handleValidORTB2Dot4 + } +}; + +function handleReqORTB2Dot4(validBidRequest, endpointUrl, bidderRequest) { + utils.logInfo(`Calling endpoint for ortb_2_4:`, endpointUrl); + const gdprConsent = getGdprConsentChoice(bidderRequest); + const envParams = getEnvParams(); + + // Make a dynamic bid request to the ad partner's endpoint + let bidRequestData = { + 'id': validBidRequest.bidId, // NOT bid.bidderRequestId or bid.auctionId + 'at': 1, + 'imp': [], + 'bcat': validBidRequest.params.bcat, + 'badv': validBidRequest.params.badv, + 'site': { + 'id': validBidRequest.params.siteId, + 'name': validBidRequest.params.siteName, + 'domain': envParams.domain, + 'page': envParams.page, + 'keywords': validBidRequest.params.keywords + }, + 'device': { + 'ua': envParams.userAgent, + 'ip': validBidRequest.params.userIp, + 'geo': { + 'country': validBidRequest.params.country + }, + 'language': envParams.lang, + 'os': envParams.osName, + 'js': 0, + 'ext': { + 'accept_language': envParams.language + } + }, + 'user': { + 'id': validBidRequest.params.userId, + }, + 'ext': { + 'sub': 0, + 'prebid': { + 'channel': { + 'name': 'pbjs', + 'version': '$prebid.version$' + } + } + } + }; + + if (gdprConsent && gdprConsent.gdprApplies) { + bidRequestData.user['ext'] = { + consent: gdprConsent.consentString + } + } + + if (validBidRequest.params.dsa && ( + hasValue(validBidRequest.params.dsa.dsarequired) || + hasValue(validBidRequest.params.dsa.pubrender) || + hasValue(validBidRequest.params.dsa.datatopub))) { + bidRequestData.regs = { + 'ext': { + 'dsa': { + 'dsarequired': validBidRequest.params.dsa.dsarequired, + 'pubrender': validBidRequest.params.dsa.pubrender, + 'datatopub': validBidRequest.params.dsa.datatopub + } + } + } + } + + const impData = imps.get(validBidRequest.params.impressionId); + + // Banner setup + const bannerMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.banner'); + if (bannerMediaType != null) { + impData.mediaType = BANNER; + bidRequestData.imp = bannerMediaType.sizes.map(size => { + return ({ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'banner': { + 'w': size[0], + 'h': size[1], + 'mimes': validBidRequest.params.mimes ? validBidRequest.params.mimes : undefined, + 'ext': { + image_output: htmlImageOutput, + video_output: htmlVideoOutput, + } + }, + }); + }); + } + + const nativeMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.native'); + + if (nativeMediaType != null) { + impData.mediaType = NATIVE; + const nativeVersion = '1.2'; + + const native = { + 'native': { + 'ver': nativeVersion, + 'plcmttype': 4, + 'plcmtcnt': validBidRequest.params.native.plcmtcnt + } + }; + + native.native.assets = bidRequestData.imp = nativeMediaType.ortb.assets.map(asset => { + const newAsset = asset; + if (newAsset.img != null) { + newAsset.img.wmin = newAsset.img.h; + newAsset.img.hmin = newAsset.img.w; + } + return newAsset; + }); + + const imp = [{ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'native': { + 'request': JSON.stringify(native), + 'ver': nativeVersion + }, + }]; + + bidRequestData.imp = imp; + }; + + const videoMediaType = utils.deepAccess(validBidRequest, 'mediaTypes.video'); + + if (videoMediaType != null) { + impData.mediaType = VIDEO; + const imp = [{ + 'id': validBidRequest.params.impressionId, + 'bidfloor': validBidRequest.params.bidfloor, + 'bidfloorcur': validBidRequest.params.bidfloorcur, + 'video': { + 'mimes': videoMediaType.mimes, + 'protocols': videoMediaType.protocols, + }, + 'ext': validBidRequest.params.imp.ext + }]; + + bidRequestData.imp = imp; + } + + utils.logInfo('PAYLOAD', bidRequestData, JSON.stringify(bidRequestData)); + utils.logInfo('FINAL URL', endpointUrl); + + return makeBidRequest(endpointUrl, bidRequestData); +}; + +function handleResORTB2Dot4(serverResponse, request, adPartner) { + utils.logInfo('on handleResORTB2Dot4 -> request:', request); + utils.logInfo('on handleResORTB2Dot4 -> request json data:', JSON.parse(request.data)); + utils.logInfo('on handleResORTB2Dot4 -> serverResponse:', serverResponse); + + let bidResponses = []; + const bidRq = JSON.parse(request.data); + + if (serverResponse.hasOwnProperty('body') && serverResponse.body.hasOwnProperty('id')) { + utils.logInfo('Ad server response', serverResponse.body.id); + + const requestId = serverResponse.body.id; + const currency = serverResponse.body.cur; + + serverResponse.body.seatbid.forEach((seatbid, seatIndex) => { + seatbid.bid.forEach((bidData, bidIndex) => { + utils.logInfo('serverResponse.body.seatbid[' + seatIndex + '].bid[' + bidIndex + ']', bidData); + + const bidResponseAd = bidData.adm; + const bannerInfo = utils.deepAccess(bidRq.imp[0], 'banner'); + const nativeInfo = utils.deepAccess(bidRq.imp[0], 'native'); + const videoInfo = utils.deepAccess(bidRq.imp[0], 'video'); + + let w; let h = 0; + let mediaType = ''; + const native = {}; + + if (bannerInfo != null) { + w = bidRq.imp[0].banner.w; + h = bidRq.imp[0].banner.h; + mediaType = BANNER; + } else if (nativeInfo != null) { + const responseADM = JSON.parse(bidResponseAd); + responseADM.native.assets.forEach(asset => { + if (asset.img != null) { + const imgAsset = JSON.parse(bidRq.imp[0].native.request) + .native.assets.filter(asset => asset.img != null).map(asset => asset.img); + w = imgAsset[0].w; + h = imgAsset[0].h; + native.image = { + url: asset.img.url, + height: h, + width: w + } + } else if (asset.title != null) { + native.title = asset.title.text; + } else if (asset.data != null) { + native.body = asset.data.value; + } else { + utils.logWarn('bidResponse->', 'wrong asset type or null'); + } + }); + + if (responseADM.native) { + if (responseADM.native.link) { + native.clickUrl = responseADM.native.link.url; + } + if (responseADM.native.eventtrackers) { + native.impressionTrackers = []; + + responseADM.native.eventtrackers.forEach(tracker => { + if (tracker.method == 1) { + native.impressionTrackers.push(tracker.url); + } + }); + } + } + mediaType = NATIVE; + } else if (videoInfo != null) { + mediaType = VIDEO; + } + + const metaData = {}; + + if (hasValue(bidData.ext.dsa)) { + metaData.dsa = bidData.ext.dsa; + } + + const bidResponse = { + requestId: requestId, + currency: currency, + ad: bidData.adm, + cpm: bidData.price, + creativeId: bidData.crid, + cid: bidData.cid, + width: w, + ttl: 360, + height: h, + netRevenue: true, + mediaType: mediaType, + meta: metaData, + nurl: bidData.nurl.replace(/^http:\/\//i, 'https://') + }; + + if (mediaType == 'native') { + bidResponse.native = native; + } + + if (mediaType == 'video') { + bidResponse.vastXml = bidData.adm; + bidResponse.width = bidData.w; + bidResponse.height = bidData.h; + } + + utils.logInfo('bidResponse->', bidResponse); + + bidResponses.push(bidResponse); + }); + }); + } else { + imps.delete(bidRq.imp[0].id); + utils.logInfo('NO Ad server response ->', serverResponse.body.id); + } + + utils.logInfo('interpretResponse -> bidResponses:', bidResponses); + + return bidResponses; +} + +function makeBidRequest(url, data) { + const payloadString = JSON.stringify(data); + + return { + method: 'POST', + url: url, + data: payloadString + } +} + +function getUrl(adPartner, bid) { + let endpointUrlMapping = { + [PARTNERS.ORTB_2_4]: bid.params.endpoint + '?idzone=' + bid.params.zoneId + '&fid=' + bid.params.fid + }; + + return endpointUrlMapping[adPartner] ? endpointUrlMapping[adPartner] : 'defaultEndpoint'; +} + +function getEnvParams() { + const envParams = { + lang: '', + userAgent: '', + osName: '', + page: '', + domain: '', + language: '' + }; + + envParams.domain = window.location.hostname; + envParams.page = window.location.protocol + '//' + window.location.host + window.location.pathname; + envParams.lang = navigator.language.indexOf('-') > -1 + ? navigator.language.split('-')[0] + : navigator.language; + envParams.userAgent = navigator.userAgent; + + if (envParams.userAgent.match(/Windows/i)) { + envParams.osName = 'Windows'; + } else if (envParams.userAgent.match(/Mac OS|Macintosh/i)) { + envParams.osName = 'MacOS'; + } else if (envParams.userAgent.match(/Unix/i)) { + envParams.osName = 'Unix'; + } else if (envParams.userAgent.userAgent.match(/Android/i)) { + envParams.osName = 'Android'; + } else if (envParams.userAgent.userAgent.match(/iPhone|iPad|iPod/i)) { + envParams.osName = 'iOS'; + } else if (envParams.userAgent.userAgent.match(/Linux/i)) { + envParams.osName = 'Linux'; + } else { + envParams.osName = 'Unknown'; + } + + let browserLanguage = navigator.language || navigator.userLanguage; + let acceptLanguage = browserLanguage.replace('_', '-'); + + envParams.language = acceptLanguage; + + utils.logInfo('Domain -> ', envParams.domain); + utils.logInfo('Page -> ', envParams.page); + utils.logInfo('Lang -> ', envParams.lang); + utils.logInfo('OS -> ', envParams.osName); + utils.logInfo('User Agent -> ', envParams.userAgent); + utils.logInfo('Primary Language -> ', envParams.language); + + return envParams; +} + +export const imps = new Map(); + +/* + Common mandatory parameters: + - endpoint + - userIp + - userIp - the minimum constraint is having the propery, empty or not + - zoneId + - partner + - fid + - siteId + - impressionId + - country + - mediaTypes?.banner or mediaTypes?.native or mediaTypes?.video + + for native parameters + - assets - it should contain the img property + + for video parameters + - mimes - it has to contain one mime type at least + - procols - it should contain one protocol at least + +*/ +function handleValidORTB2Dot4(bid) { + const bannerInfo = bid.mediaTypes?.banner; + const nativeInfo = bid.mediaTypes?.native; + const videoInfo = bid.mediaTypes?.video; + const isValid = ( + hasValue(bid.params.endpoint) && + hasValue(bid.params.userIp) && + bid.params.hasOwnProperty('userIp') && + hasValue(bid.params.zoneId) && + hasValue(bid.params.partner) && + hasValue(bid.params.fid) && + hasValue(bid.params.siteId) && + hasValue(bid.params.impressionId) && + hasValue(bid.params.country) && + hasValue(bid.params.country.length > 0) && + ((!hasValue(bid.params.bcat) || + bid.params.bcat.length > 0)) && + ((!hasValue(bid.params.badv) || + bid.params.badv.length > 0)) && + (bannerInfo || nativeInfo || videoInfo) && + (nativeInfo ? bid.params.native && + nativeInfo.ortb.assets && + nativeInfo.ortb.assets.some(asset => !!asset.img) : true) && + (videoInfo ? (videoInfo.mimes && + videoInfo.mimes.length > 0 && + videoInfo.protocols && + videoInfo.protocols.length > 0) : true)); + if (!isValid) { + utils.logError('Validation Error'); + } + + return isValid; +} + +function hasValue(value) { + return ( + value !== undefined && + value !== null + ); +} + +function getGdprConsentChoice(bidderRequest) { + const hasGdprConsent = + hasValue(bidderRequest) && + hasValue(bidderRequest.gdprConsent); + + if (hasGdprConsent) { + return bidderRequest.gdprConsent; + } + + return null; +} + +export const spec = { + aliases: ['exads'], // short code + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + isBidRequestValid: function (bid) { + utils.logInfo('on isBidRequestValid -> bid:', bid); + + if (!bid.params.partner) { + utils.logError('Validation Error', 'bid.params.partner missed'); + return false; + } else if (!Object.values(PARTNERS).includes(bid.params.partner)) { + utils.logError('Validation Error', 'bid.params.partner is not valid'); + return false; + } + + let adPartner = bid.params.partner; + + if (adPartnerHandlers[adPartner] && adPartnerHandlers[adPartner]['validation']) { + return adPartnerHandlers[adPartner]['validation'](bid); + } else { + // Handle unknown or unsupported ad partners + return false; + } + }, + buildRequests: function (validBidRequests, bidderRequest) { + utils.logInfo('on buildRequests -> validBidRequests:', validBidRequests); + utils.logInfo('on buildRequests -> bidderRequest:', bidderRequest); + + return validBidRequests.map(bid => { + let adPartner = bid.params.partner; + + imps.set(bid.params.impressionId, { adPartner: adPartner, mediaType: null }); + + let endpointUrl = getUrl(adPartner, bid); + + // Call the handler for the ad partner, passing relevant parameters + if (adPartnerHandlers[adPartner]['request']) { + return adPartnerHandlers[adPartner]['request'](bid, endpointUrl, bidderRequest); + } else { + // Handle unknown or unsupported ad partners + return null; + } + }); + }, + interpretResponse: function (serverResponse, request) { + const bid = JSON.parse(request.data); + const impData = imps.get(bid.imp[0].id); + const adPartner = impData.adPartner; + + // Call the handler for the ad partner, passing relevant parameters + if (adPartnerHandlers[adPartner]['response']) { + return adPartnerHandlers[adPartner]['response'](serverResponse, request, adPartner); + } else { + // Handle unknown or unsupported ad partners + return null; + } + }, + onTimeout: function (timeoutData) { + utils.logWarn(`onTimeout -> timeoutData:`, timeoutData); + }, + onBidWon: function (bid) { + utils.logInfo(`onBidWon -> bid:`, bid); + if (bid.nurl) { + utils.triggerPixel(bid.nurl); + } + }, + onSetTargeting: function (bid) { + utils.logInfo(`onSetTargeting -> bid:`, bid); + }, + onBidderError: function (bid) { + imps.delete(bid.bidderRequest.bids[0].params.impressionId); + utils.logInfo('onBidderError -> bid:', bid); + }, +}; + +registerBidder({ + code: BIDDER, + gvlid: GVL_ID, + ...spec +}); diff --git a/modules/exadsBidAdapter.md b/modules/exadsBidAdapter.md new file mode 100644 index 00000000000..06b873d8da8 --- /dev/null +++ b/modules/exadsBidAdapter.md @@ -0,0 +1,484 @@ +# Overview + +**Module Name**: Exads Bidder Adapter + +**Module Type**: Bidder Adapter + +**Maintainer**: + +## Description + +Module that connects to EXADS' bidder for bids. + +## Build + +If you don't need to use the prebidJS video module, please remove the videojsVideoProvider module. + +```bash +gulp build --modules=consentManagement,exadsBidAdapter,videojsVideoProvider +``` + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the exadsBidAdapter, as specified below. + +* Set "debug" as true if you need to read logs; +* Set "gdprApplies" as true if you need to pass gdpr consent string; +* The tcString is the iabtcf consent string for gdpr; +* Uncomment the cache instruction if you need to configure a cache server (e.g. for instream video) + +```js +pbjs.setConfig({ + debug: false, + //cache: { url: "https://prebid.adnxs.com/pbc/v1/cache" }, + consentManagement: { + gdpr: { + cmpApi: 'static', + timeout: 1000000, + defaultGdprScope: true, + consentData: { + getTCData: { + tcString: consentString, + gdprApplies: false // set to true to pass the gdpr consent string + } + } + } + } +}); +``` + +Add the `video` config if you need to render videos using the video module. +For more info navigate to . + +```js +pbjs.setConfig({ + video: { + providers: [{ + divId: 'player', // the id related to the videojs tag in your body + vendorCode: 2, // videojs, + playerConfig: { + params: { + adPluginConfig: { + numRedirects: 10 + }, + vendorConfig: { + controls: true, + autoplay: true, + preload: "auto", + } + } + } + },] + }, +}); +``` + +### Test Parameters + +Now you will find the different parameters to set, based on publisher website. They are optional unless otherwise specified. + +#### RTB Banner 2.4 + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **banner.sizes** (required) - one integer array - [width, height] +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (*required) - unique user ID (string).*If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - country ISO3 +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **mimes** - list of supported mime types. We support: image/jpeg, image/jpg, image/png, image/png, image/gif, image/webp, video/mp4 (string array) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **endpoint** (required) - EXADS endpoint (URL) + +##### RTB Banner 2.4 (Image) + +```js + +adUnits = + [{ code: 'postbid_iframe', // the frame where to render the creative + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + country: 'IRL', + impressionId: impression_id.toString(), + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] + }]; +``` + +##### RTB Banner 2.4 (Video) + +```js +adUnits = + [{ code: 'postbid_iframe', // the frame where to render the creative + mediaTypes: { + banner: { + sizes: [900, 250] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + country: 'IRL', + impressionId: '1234', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] + }]; +``` + +#### RTB 2.4 Video (Instream/OutStream/Video Slider) - VAST XML or VAST TAG (url) + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (required) - unique user ID (string). *If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - Country ISO3 (string) +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **mediaTypes.video** (required) + * **mimes** (required) - list of supported mime types (string array) + * **protocols** (required) - list of supported video bid response protocols (integer array) + * **context** - (recommended) - the video context, either 'instream', 'outstream'. Defaults to ‘instream’ (string) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **endpoint** (required) - EXADS endpoint (URL) + +```js +adUnits = [{ + code: 'postbid_iframe', + mediaTypes: { + video: { + mimes: ['video/mp4'], + context: 'instream', + protocols: [3, 6] + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + impressionId: '1234', + imp: { + ext: { + video_cta: 0 + } + }, + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + country: 'IRL', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] +}]; +``` + +#### RTB 2.4 Native + +* **zoneId** (required) - you can get it from the endpoint created after configuring the zones (integer) +* **fid** (required) - you can get it from the endpoint created after configuring the zones (string) +* **partner** (required) - currently we support rtb 2.4 ("ortb_2_4") only (string) +* **siteId** (recommended) - unique Site ID (string) +* **siteName** site name (string) +* **userIp** (required) - IP address of the user, ipv4 or ipv6 (string) +* **userId** (*required) - unique user ID (string).*If you cannot generate a user ID, you can leave it empty (""). The request will get a response as long as "user" object is included in the request +* **country** - country ISO3 (string) +* **impressionId** (required) - unique impression ID within this bid request (string) +* **keywords** - keywords can be used to ensure ad zones get the right type of advertising. Keywords should be a string of comma-separated words +* **bidfloor** - minimum bid for this impression (CPM) / click (CPC) and account currency (float) +* **bidfloorcur** - currency for minimum bid value specified using ISO-4217 alpha codes (string) +* **bcat** - blocked advertiser categories using the IAB content categories (string array) +* **badv** - block list of advertisers by their domains (string array) +* **dsa** - DSA transparency information + * **dsarequired** - flag to indicate if DSA information should be made available (integer) + *0 - Not required + * 1 - Supported, bid responses with or without DSA object will be accepted + *2 - Required, bid responses without DSA object will not be accepted + * 3 - Required, bid responses without DSA object will not be accepted, Publisher is an Online Platform + * **pubrender** - flag to indicate if the publisher will render the DSA Transparency info (integer) + * 0 - Publisher can't render + * 1 - Publisher could render depending on adrender + * 2 - Publisher will render + * **datatopub** - independent of pubrender, the publisher may need the transparency data for audit purposes (integer) + * 0 - do not send transparency data + * 1 - optional to send transparency data + * 2 - send transparency data +* **native.plcmtcnt** - the number of identical placements in this Layout (integer) +* **assets (title)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **required** - set to 1 if asset is required or 0 if asset is optional (integer) + * **title** + * len (required) - maximum length of the text in the title element (integer) +* **assets (data)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **data** + * **type** - type ID of the element supported by the publisher (integer). We support: + *1 - sponsored - sponsored By message where response should contain the brand name of the sponsor + * 2 - desc - descriptive text associated with the product or service being advertised + * **len** - maximum length of the text in the element’s response (integer) +* **assets (img)** + * **id** - unique asset ID, assigned by exchange. Typically a counter for the array (integer): + *1 - image asset ID + * 2 - title asset ID + * 3 - description asset ID + * **required** - set to 1 if asset is required or 0 if asset is optional (integer) + * **img** + * **type** - type ID of the image element supported by the publisher. We support: + *1 - icon image (integer) + * 3 - large image preview for the ad (integer) + * **w** - width of the image in pixels, optional (integer) + * **h** - height of the image in pixels, optional (integer) +* **endpoint** (required) - EXADS endpoint (URL) + +```js +adUnits = [{ + code: 'postbid_iframe', + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 2, + required: 1, + title: { + len: 124 + } + }, + { + id: 3, + data: { + type: 1, + len: 50 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 300 + } + }] + } + } + }, + bids: [{ + bidder: 'exadsadserver', + params: { + zoneId: 12345, + fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', + partner: 'ortb_2_4', + siteId: '123', + siteName: 'test.com', + userIp: '0.0.0.0', + userId: '1234', + impressionId: '1234', + native: { + plcmtcnt: 4 + }, + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + country: 'IRL', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39','IAB8-18','IAB8-5','IAB9-9'], + badv: ['first.com', 'second.com'], + endpoint: 'https://your-ad-network.com/rtb.php' + } + }] +}]; +``` + +## DSA Transparency + +All DSA information, returned by the ad server, can be found into the **meta** tag of the response. As: + +```js +"meta": { + "dsa": { + "behalf": "...", + "paid": "...", + "transparency": [ + { + "params": [ + ... + ] + } + ], + "adrender": ... + } +} +``` + +For more information navigate to . + +## Tools and suggestions + +This section contains some suggestions that allow to set some parameters automatically. + +### User Ip / Country + +In order to detect the current user ip there are different approaches. An example is using public web services as ```https://api.ipify.org```. + +Example of usage (to add to the publisher websites): + +```html + +``` + +The same service gives the possibility to detect the country as well. Check the official web page about possible limitations of the free licence. + +### Impression Id + +Each advertising request has to be identified uniquely by an id. +One possible approach is using a classical hash function. + +```html + +``` + +### User Id + +The approach used for impression id could be used for generating a unique user id. +Also, it is recommended to store the id locally, e.g. by the browser localStorage. + +```html + +``` diff --git a/test/spec/modules/exadsBidAdapter_spec.js b/test/spec/modules/exadsBidAdapter_spec.js new file mode 100644 index 00000000000..9253f21ddf1 --- /dev/null +++ b/test/spec/modules/exadsBidAdapter_spec.js @@ -0,0 +1,632 @@ +import { expect } from 'chai'; +import { spec, imps } from 'modules/exadsBidAdapter.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; + +describe('exadsBidAdapterTest', function () { + const bidder = 'exadsadserver'; + + const partners = { + ORTB_2_4: 'ortb_2_4' + }; + + const imageBanner = { + mediaTypes: { + banner: { + sizes: [300, 250] + } + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + bcat: ['IAB25', 'IAB7-39', 'IAB8-18', 'IAB8-5', 'IAB9-9'], + badv: ['first.com', 'second.com'], + mimes: ['image/jpg'], + endpoint: 'test.com', + dsa: { + dsarequired: 3, + pubrender: 0, + datatopub: 2 + }, + } + }; + + const native = { + mediaTypes: { + native: { + ortb: { + assets: [{ + id: 3, + required: 1, + title: { + len: 124 + } + }, + { + id: 2, + data: { + type: 1, + len: 50 + } + }, + { + id: 1, + required: 1, + img: { + type: 3, + w: 300, + h: 300, + } + }] + } + }, + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + native: { + plcmtcnt: 4, + }, + dsa: { + pubrender: 0, + datatopub: 2 + }, + endpoint: 'test.com' + } + }; + + const instream = { + mediaTypes: { + video: { + mimes: ['video/mp4'], + protocols: [3, 6], + } + }, + bidder: bidder, + params: { + zoneId: 5147485, + fid: '829a896f011475d505a0d89cfdd1af8d9cdb07ff', + partner: partners.ORTB_2_4, + siteId: '12345', + siteName: 'your-site.com', + catIab: ['IAB25-3'], + userIp: '0.0.0.0', + userId: '', + country: 'IRL', + impressionId: '123456', + keywords: 'lifestyle, humour', + bidfloor: 0.00000011, + bidfloorcur: 'EUR', + imp: { + ext: { + video_cta: 0 + } + }, + dsa: { + datatopub: 2 + }, + endpoint: 'test.com', + } + }; + + describe('while validating bid request', function () { + it('should check the validity of bidRequest with all mandatory params for banner ad-format', function () { + expect(spec.isBidRequestValid(imageBanner)).to.equal(true); + }); + + it('should check the validity of a bidRequest with all mandatory params for native ad-format', function () { + expect(spec.isBidRequestValid(native)); + }); + + it('should check the validity of a bidRequest with all mandatory params for instream ad-format', function () { + expect(spec.isBidRequestValid(instream)).to.equal(true); + }); + + it('should check the validity of a bidRequest with wrong partner', function () { + expect(spec.isBidRequestValid({ + ...imageBanner, + params: { + ...imageBanner.params, + partner: 'not_ortb_2_4' + } + })).to.eql(false); + }); + + it('should check the validity of a bidRequest without params', function () { + expect(spec.isBidRequestValid({ + bidder: bidder, + params: { } + })).to.equal(false); + }); + }); + + describe('while building bid request for banner ad-format', function () { + const bidRequests = [imageBanner]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while building bid request for native ad-format', function () { + const bidRequests = [native]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while building bid request for instream ad-format', function () { + const bidRequests = [instream]; + + it('should make a bidRequest by HTTP method', function () { + const requests = spec.buildRequests(bidRequests, {}); + requests.forEach(function(requestItem) { + expect(requestItem.method).to.equal('POST'); + }); + }); + }); + + describe('while interpreting bid response', function () { + beforeEach(() => { + imps.set('270544423272657', { adPartner: 'ortb_2_4', mediaType: null }); + }); + + it('should test the banner interpretResponse', function () { + const serverResponse = { + body: { + 'id': '2d2a496527398e', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8f7fa506af97bc193e7bf099d8ed6930bd50aaf1', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '\n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ] + }, + 'nurl': 'http://your-ad-network.com/', + 'cid': '6260389', + 'crid': '89453173', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 250, + 'attr': [ + 12 + ] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2d2a496527398e', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'banner': { + 'w': 300, + 'h': 250 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-banner.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + + it('should test the native interpretResponse', function () { + const serverResponse = { + body: { + 'id': '21dea1fc6c3e1b', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'cedc93987cd4a1e08fdfe97de97482d1ecc503ee', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '{"native":{"link":{"url":"https:\\/\\/your-ad-network.com"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/your-ad-network.com"}],"assets":[{"id":1,"title":{"text":"Title"}},{"id":2,"data":{"value":"Description"}},{"id":3,"img":{"url":"https:\\/\\/your-ad-network.com\\/32167\\/f85ee87ea23.jpg"}}]}}', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ] + }, + 'nurl': 'http://your-ad-network.com', + 'cid': '6260393', + 'crid': '89453189', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 300, + 'attr': [] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '21dea1fc6c3e1b', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'native': { + 'request': '{"native":{"ver":"1.2","context":1,"contextsubtype":10,"plcmttype":4,"plcmtcnt":4,"assets":[{"id":1,"required":1,"title":{"len":124}},{"id":2,"data":{"type":1,"len":50}},{"id":3,"required":1,"img":{"type":3,"w":300,"h":300,"wmin":300,"hmin":300}}]}}', + 'ver': '1.2' + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-native.html' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(NATIVE); + }); + + it('should test the InStream Video interpretResponse', function () { + const serverResponse = { + body: { + 'id': '2218abc7ebca97', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'd2d2063517b126252f56e22767c53f936ff40411', + 'impid': '270544423272657', + 'price': 0.12474000000000002, + 'adm': '\n\n \n \n your-ad-network.com\n \n \n \n \n \n \n 00:00:20.32\n \n \n \n \n \n \n \n \n \n \n \n \n test.com\n \n \n \n \n \n \n \n \n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'video/mp4' + ] + }, + 'nurl': 'http://your-ad-network.com', + 'cid': '6260395', + 'crid': '89453191', + 'adomain': [ + 'test.com' + ], + 'w': 0, + 'h': 0, + 'attr': [] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2218abc7ebca97', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'video': { + 'mimes': [ + 'video/mp4' + ] + }, + 'protocols': [ + 3, + 6 + ], + 'ext': { + 'video_cta': 0 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-InStreamVideo.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(VIDEO); + }); + }); + + describe('checking dsa information', function() { + it('should add dsa information to the request via bidderRequest.params.dsa', function () { + const bidRequests = [imageBanner]; + + const requests = spec.buildRequests(bidRequests, {}); + + requests.forEach(function(requestItem) { + const payload = JSON.parse(requestItem.data); + + expect(payload.regs.ext.dsa).to.exist; + expect(payload.regs.ext.dsa.dsarequired).to.equal(3); + expect(payload.regs.ext.dsa.pubrender).to.equal(0); + expect(payload.regs.ext.dsa.datatopub).to.equal(2); + }); + }); + + it('should test the dsa interpretResponse', function () { + const dsaResponse = { + 'behalf': '...', + 'paid': '...', + 'transparency': [ + { + 'params': [ + 2 + ] + } + ], + 'adrender': 0 + }; + + const serverResponse = { + body: { + 'id': '2d2a496527398e', + 'seatbid': [ + { + 'bid': [ + { + 'id': '8f7fa506af97bc193e7bf099d8ed6930bd50aaf1', + 'impid': '270544423272657', + 'price': 0.0045000000000000005, + 'adm': '\n\n', + 'ext': { + 'btype': 1, + 'asset_mime_type': [ + 'image/jpeg', + 'image/jpg' + ], + 'dsa': dsaResponse + }, + 'nurl': 'http://your-ad-network.com/', + 'cid': '6260389', + 'crid': '89453173', + 'adomain': [ + 'test.com' + ], + 'w': 300, + 'h': 250, + 'attr': [ + 12 + ] + } + ] + } + ], + 'cur': 'USD' + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + data: JSON.stringify({ + 'id': '2d2a496527398e', + 'at': 1, + 'imp': [ + { + 'id': '270544423272657', + 'bidfloor': 1.1e-7, + 'bidfloorcur': 'EUR', + 'banner': { + 'w': 300, + 'h': 250 + } + } + ], + 'site': { + 'id': '12345', + 'domain': 'your-ad-network.com', + 'cat': [ + 'IAB25-3' + ], + 'page': 'https://your-ad-network.com/prebidJS-client-RTB-banner.html', + 'keywords': 'lifestyle, humour' + }, + 'device': { + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', + 'ip': '95.233.216.174', + 'geo': { + 'country': 'ITA' + }, + 'language': 'en', + 'os': 'MacOS', + 'js': 0, + 'ext': { + 'remote_addr': '', + 'x_forwarded_for': '', + 'accept_language': 'en-GB' + } + }, + 'user': { + 'id': '' + }, + 'ext': { + 'sub': 0 + }, + 'regs': { + 'ext': { + 'dsa': { + 'dsarequired': 3, + 'pubrender': 0, + 'datatopub': 2 + } + } + } + }) + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + const bidResponse = bidResponses[0]; + expect(bidResponse.meta).to.exist; + expect(bidResponse.meta.dsa).to.exist; + expect(bidResponse.meta.dsa).equal(dsaResponse); + }); + }); + + describe('on getting the win event', function() { + it('should not create nurl request if bid is undefined', function() { + const result = spec.onBidWon({}); + expect(result).to.be.undefined; + }); + }); + + describe('checking timeut', function () { + it('should exists and be a function', () => { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + }); +}); From 5cfae1b34bd28d41e89c29f005b7abebe26a8c48 Mon Sep 17 00:00:00 2001 From: Hrechko Dmytro Date: Thu, 2 May 2024 17:38:22 +0300 Subject: [PATCH 697/760] Eight Pod Bid / Analytics Adapter : initial release (#11260) * eight pod init * fix mocks * specify eightPodAnalyticAdapter tests * update Event import --- modules/eightPodAnalyticsAdapter.js | 186 ++++++++++++++++ modules/eightPodAnalyticsAdapter.md | 19 ++ modules/eightPodBidAdapter.js | 193 +++++++++++++++++ modules/eightPodBidAdapter.md | 36 ++++ .../modules/eightPodAnalyticsAdapter_spec.js | 178 +++++++++++++++ test/spec/modules/eightPodBidAdapter_spec.js | 203 ++++++++++++++++++ 6 files changed, 815 insertions(+) create mode 100644 modules/eightPodAnalyticsAdapter.js create mode 100644 modules/eightPodAnalyticsAdapter.md create mode 100644 modules/eightPodBidAdapter.js create mode 100644 modules/eightPodBidAdapter.md create mode 100644 test/spec/modules/eightPodAnalyticsAdapter_spec.js create mode 100644 test/spec/modules/eightPodBidAdapter_spec.js diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js new file mode 100644 index 00000000000..64ff845505d --- /dev/null +++ b/modules/eightPodAnalyticsAdapter.js @@ -0,0 +1,186 @@ +import {logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import { EVENTS } from '../src/constants.js'; +import adapterManager from '../src/adapterManager.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js' +import {getStorageManager} from '../src/storageManager.js'; + +const analyticsType = 'endpoint'; +const MODULE_NAME = `eightPod`; +const MODULE = `${MODULE_NAME}AnalyticProvider`; + +/** + * Custom tracking server that gets internal events from EightPod's ad unit + */ +const trackerUrl = 'https://demo.8pod.com/tracker/track'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) + +const { + BID_WON +} = EVENTS; + +export let queue = []; +export let context; + +/** + * Create eightPod Analytic adapter + */ +let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { + /** + * Execute on bid won - setup basic settings, save context about EightPod's bid. We will send it with our events later + */ + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + if (args.bidder === 'eightPod') { + eightPodAnalytics.setupPage(args); + context = makeContext(args); + break; + } + } + }, + + /** + * Execute on bid won - subscribe on events from adUnit + */ + setupPage() { + eightPodAnalytics.eventSubscribe(); + queue = getEventFromLocalStorage(); + }, + /** + * Subscribe on internal ad unit tracking events + */ + eventSubscribe() { + window.addEventListener('message', async (event) => { + const data = event?.data; + + if (!data?.detail?.name) { + return; + } + + trackEvent(data); + }); + + // Send queue of event every 10 seconds + setInterval(sendEvents, 10_000); + }, + resetQueue() { + queue = []; + }, + getContext() { + return context; + } +}); + +/** + * Create context of event, who emits it + */ +function makeContext(args) { + const params = args?.params?.[0]; + return { + bidId: args.seatBidId, + variantId: args.creativeId || 'variantId', + campaignId: 'campaignId', + publisherId: params.publisherId, + placementId: params.placementId, + }; +} + +/** + * Create event, add context and push it to queue + */ +export function trackEvent(event) { + if (!event.detail) { + return; + } + const fullEvent = { + context: eightPodAnalytics.getContext(), + eventType: event.detail.type, + eventClass: 'adunit', + timestamp: new Date().getTime(), + eventName: event.detail.name, + payload: event.detail.payload + }; + + addEvent(fullEvent); +} + +/** + * Push event to queue, save event list in local storage + */ +function addEvent(eventPayload) { + queue.push(eventPayload); + storage.setDataInLocalStorage(`EIGHT_POD_EVENTS`, JSON.stringify(queue), null); +} + +/** + * Gets previously saved event that has not been sent + */ +function getEventFromLocalStorage() { + const storedEvents = storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage('EIGHT_POD_EVENTS') : null; + + if (storedEvents) { + return JSON.parse(storedEvents); + } else { + return []; + } +} + +/** + * Send event to our custom tracking server and reset queue + */ +function sendEvents() { + eightPodAnalytics.eventsStorage = queue; + + if (queue.length) { + try { + sendEventsApi(queue, { + success: () => { + resetLocalStorage(); + eightPodAnalytics.resetQueue(); + }, + error: (e) => { + logError(MODULE, 'Cant send events', e); + } + }) + } catch (e) { + logError(MODULE, 'Cant send events', e); + } + } +} + +/** + * Send event to our custom tracking server + */ +function sendEventsApi(eventList, callbacks) { + ajax(trackerUrl, callbacks, JSON.stringify(eventList)); +} + +/** + * Remove saved events in success scenario + */ +const resetLocalStorage = () => { + storage.setDataInLocalStorage(`EIGHT_POD_EVENTS`, JSON.stringify([]), null); +} + +// save the base class function +eightPodAnalytics.originEnableAnalytics = eightPodAnalytics.enableAnalytics; +eightPodAnalytics.eventsStorage = []; + +// override enableAnalytics so we can get access to the config passed in from the page +eightPodAnalytics.enableAnalytics = function (config) { + eightPodAnalytics.originEnableAnalytics(config); + logInfo(MODULE, 'init', config); +}; + +/** + * Register Analytics Adapter + */ +adapterManager.registerAnalyticsAdapter({ + adapter: eightPodAnalytics, + code: MODULE_NAME +}); + +export default eightPodAnalytics; diff --git a/modules/eightPodAnalyticsAdapter.md b/modules/eightPodAnalyticsAdapter.md new file mode 100644 index 00000000000..fe37bf34459 --- /dev/null +++ b/modules/eightPodAnalyticsAdapter.md @@ -0,0 +1,19 @@ +# Overview +Module Name: 8pod Analytics by 8Pod + +Module Type: Analytics Adapter + +Maintainer: bianca@8pod.com + +# Description + +Analytics adapter for prebid provided by 8pod. It gets events from eightPod's ad unit and send it to our tracking server to improve user experience. +Please, use it ONLY with eightPodBidAdapter. + +# Analytics Adapter configuration example + +``` +{ + provider: 'eightPod' +} +``` diff --git a/modules/eightPodBidAdapter.js b/modules/eightPodBidAdapter.js new file mode 100644 index 00000000000..02f0954af07 --- /dev/null +++ b/modules/eightPodBidAdapter.js @@ -0,0 +1,193 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { registerBidder } from '../src/adapters/bidderFactory.js' +import { BANNER } from '../src/mediaTypes.js' +import * as utils from '../src/utils.js' + +export const BIDDER_CODE = 'eightPod' +const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid?trace=true'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + isBidRequestValid, + buildRequests, + interpretResponse, + isBannerBid, + isVideoBid, + onBidWon +} + +registerBidder(spec) + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context) + return req + }, + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context) + return response.bids + }, + imp(buildImp, bidRequest, context) { + return buildImp(bidRequest, context) + }, + bidResponse +}) + +function hasRequiredParams(bidRequest) { + return !!bidRequest?.params?.placementId +} + +function isBidRequestValid(bidRequest) { + return hasRequiredParams(bidRequest) +} + +function buildRequests(bids, bidderRequest) { + let bannerBids = bids.filter((bid) => isBannerBid(bid)) + let requests = bannerBids.length + ? [createRequest(bannerBids, bidderRequest, BANNER)] + : [] + return requests +} + +function bidResponse(buildBidResponse, bid, context) { + bid.nurl = replacePriceInUrl(bid.nurl, bid.price); + + const bidResponse = buildBidResponse(bid, context); + + bidResponse.height = context?.imp?.banner?.format?.[0].h; + bidResponse.width = context?.imp?.banner?.format?.[0].w; + + bidResponse.burl = replacePriceInUrl(bid.burl, bidResponse.originalCpm || bidResponse.cpm); + return bidResponse; +} + +function onBidWon(bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl) + } +} +function replacePriceInUrl(url, price) { + return url.replace(/\${AUCTION_PRICE}/, price) +} + +export function parseUserAgent() { + const ua = navigator.userAgent.toLowerCase(); + + // Check if it's iOS + if (/iphone|ipad|ipod/.test(ua)) { + // Extract iOS version and device type + const iosInfo = /(iphone|ipad|ipod) os (\d+[._]\d+)|((iphone|ipad|ipod)(\D+cpu) os (\d+(?:[._\s]\d+)?))/.exec(ua); + return { + platform: 'ios', + version: iosInfo ? iosInfo[1] : '', + device: iosInfo ? iosInfo[2].replace('_', '.') : '' + }; + } else if (/android/.test(ua)) { + // Check if it's Android + // Extract Android version + const androidVersion = /android (\d+([._]\d+)?)/.exec(ua); + return { + platform: 'android', + version: androidVersion ? androidVersion[1].replace('_', '.') : '', + device: '' + }; + } else { + // If neither iOS nor Android, return unknown + return { + platform: 'Unknown', + version: '', + device: '' + }; + } +} + +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return ((element && element.content) || '').replaceAll(' ', ''); +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + const data = converter.toORTB({ + bidRequests, + bidderRequest, + context: { mediaType }, + }); + + data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; + data.at = 1; + + const params = getBidderParams(bidRequests); + + data.device = { + ...data.device, + model: parseUserAgent().device, + os: parseUserAgent().platform, + osv: parseUserAgent().version, + geo: { + country: params.country || 'GRB' + }, + language: params.language || data.device.language, + } + data.site = { + ...data.site, + keywords: getPageKeywords(window), + } + data.imp = [ + { + ...data.imp?.[0], + pmp: params.dealId + ? { + ...data.pmp, + deals: [ + { + bidfloor: 0.5, + at: 2, + id: params.dealId, + }, + ], + private_auction: 1, + } + : data.pmp, + } + ] + data.adSlotPlacementId = params.placementId; + + const req = { + method: 'POST', + url, + data + } + return req +} + +function getBidderParams(bidRequests) { + const bid = bidRequests.find(bid => { + return bid.bidder === BIDDER_CODE + }); + + return bid?.params ? bid.params : undefined; +} + +function isVideoBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.video') +} + +function isBannerBid(bid) { + return utils.deepAccess(bid, 'mediaTypes.banner') +} + +function interpretResponse(resp, req) { + return converter.fromORTB({ request: req.data, response: resp.body }) +} diff --git a/modules/eightPodBidAdapter.md b/modules/eightPodBidAdapter.md new file mode 100644 index 00000000000..afefd5717de --- /dev/null +++ b/modules/eightPodBidAdapter.md @@ -0,0 +1,36 @@ +# Overview +Module Name: 8pod Bidder Adapter + +Module Type: Bidder Adapter + +Maintainer: bianca@8pod.com + +# Description + +Connect to 8pod for bids. + +This adapter requires setup and approval from the 8pod team. + +Please add eightPodAnalytics to collect user behavior and improve user experience as well. + +# Bidder Adapter configuration example + +``` +var adUnits = [{ + code: 'something', + mediaTypes: { + banner: { + sizes: [[350, 550]], + }, + }, + bids: [ + { + bidder: 'eightPod', + params: { + placementId: 13144370, + publisherId: 'publisherID-488864646', + }, + }, + ], + }]; +``` diff --git a/test/spec/modules/eightPodAnalyticsAdapter_spec.js b/test/spec/modules/eightPodAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..930b15bda31 --- /dev/null +++ b/test/spec/modules/eightPodAnalyticsAdapter_spec.js @@ -0,0 +1,178 @@ +import analyticsAdapter, { storage, queue, context, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import eightPodAnalytics from 'modules/eightPodAnalyticsAdapter.js'; +import { EVENTS } from '../../../src/constants.js'; + +const { + BID_WON +} = EVENTS; + +describe('eightPodAnalyticAdapter', function() { + let sandbox; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + adapterManager.enableAnalytics({ + provider: 'eightPod' + }); + }); + + afterEach(function() { + sandbox.restore(); + analyticsAdapter.disableAnalytics(); + }); + + describe('setup page', function() { + let getDataFromLocalStorageStub, localStorageIsEnabledStub; + let addEventListenerSpy; + + beforeEach(function() { + localStorageIsEnabledStub = sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + getDataFromLocalStorageStub = sandbox.stub( + storage, + 'getDataFromLocalStorage' + ); + addEventListenerSpy = sandbox.spy(window, 'addEventListener'); + }); + + afterEach(function() { + getDataFromLocalStorageStub.restore(); + localStorageIsEnabledStub.restore(); + addEventListenerSpy.restore(); + }); + + it('should subscribe on messageEvents', function() { + getDataFromLocalStorageStub.returns(JSON.stringify([])); + sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + + analyticsAdapter.setupPage(); + + sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 1); + sandbox.assert.callCount(addEventListenerSpy, 1); + }); + + it('should receive saved events list', function() { + const eventList = [1, 2, 3]; + getDataFromLocalStorageStub.returns(JSON.stringify(eventList)); + sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + + analyticsAdapter.setupPage(); + expect(queue).to.deep.equal(eventList) + }); + }); + + describe('track event', function() { + let setupPageStub; + + beforeEach(function() { + setupPageStub = sandbox.stub(eightPodAnalytics, 'setupPage'); + }); + + afterEach(function() { + setupPageStub.restore(); + }); + + it('should NOT call setup page and get context', function() { + eightPodAnalytics.track({ + eventType: 'wrong_event_type', + }) + + sandbox.assert.callCount(setupPageStub, 0); + expect(context).to.deep.equal(undefined) + }); + + it('should call setup page and get context', function() { + eightPodAnalytics.track({ + eventType: BID_WON, + args: { + bidder: 'eightPod', + creativeId: 'creativeId', + seatBidId: 'seatBidId', + params: [ + { + publisherId: 'publisherId', + placementId: 'placementId', + } + ] + } + }) + + sandbox.assert.callCount(setupPageStub, 1); + expect(context).to.deep.equal({ + bidId: 'seatBidId', + campaignId: 'campaignId', + placementId: 'placementId', + publisherId: 'publisherId', + variantId: 'creativeId' + }) + }); + }); + + describe('trackEvent', function() { + let getContextStub, getTimeStub; + + beforeEach(function() { + getContextStub = sandbox.stub(eightPodAnalytics, 'getContext'); + getTimeStub = sandbox.stub(Date.prototype, 'getTime').returns(1234); + eightPodAnalytics.resetQueue(); + }); + + afterEach(function() { + getContextStub.restore(); + getTimeStub.restore(); + }); + + it('should add event to the queue', function() { + getContextStub.returns({}); + + const event1 = { + detail: { + type: 'Counter', + name: 'next_slide', + payload: { + from: '1.1', + to: '2.2', + value: 3 + } + } + } + const result1 = { + context: {}, + eventType: 'Counter', + eventClass: 'adunit', + timestamp: 1234, + eventName: 'next_slide', + payload: { + from: '1.1', + to: '2.2', + value: 3 + } + } + + const event2 = { + detail: { + type: 'Counter', + name: 'pod_impression', + payload: { + value: 2 + } + } + } + const result2 = { + context: {}, + eventType: 'Counter', + eventClass: 'adunit', + timestamp: 1234, + eventName: 'pod_impression', + payload: { + value: 2 + } + } + trackEvent(event1) + expect(queue).to.deep.equal([result1]); + trackEvent(event2); + expect(queue).to.deep.equal([result1, result2]); + }); + }); +}); diff --git a/test/spec/modules/eightPodBidAdapter_spec.js b/test/spec/modules/eightPodBidAdapter_spec.js new file mode 100644 index 00000000000..7d55997d8cd --- /dev/null +++ b/test/spec/modules/eightPodBidAdapter_spec.js @@ -0,0 +1,203 @@ +import { expect } from 'chai' +import { spec, getPageKeywords, parseUserAgent } from 'modules/eightPodBidAdapter' +import 'modules/priceFloors.js' +import { config } from 'src/config.js' +import { newBidder } from 'src/adapters/bidderFactory' +import * as utils from '../../../src/utils'; +import sinon from 'sinon'; + +describe('eightPodBidAdapter', 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 validBid = { + bidder: 'eightPod', + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + params: { + placementId: 'placementId1', + }, + } + const invalidBid = { + bidder: 'eightPod', + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + } + + beforeEach(() => { + config.resetConfig() + }) + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(validBid)).to.equal(true) + }) + + it('should return false when required params found and invalid bid', function () { + expect(spec.isBidRequestValid(invalidBid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests, bidderRequest + beforeEach(function () { + bidRequests = [ + { + bidder: 'eightPod', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + adUnitCode: '/adunit-code/test-path', + bidId: 'test-bid-id-1', + bidderRequestId: 'test-bid-request-1', + auctionId: 'test-auction-1', + transactionId: 'test-transactionId-1', + params: { + placementId: 'placementId1', + } + } + ] + bidderRequest = { + refererInfo: {}, + ortb2: { + device: { + ua: 'ua', + language: 'en', + dnt: 1, + js: 1, + } + } } + }) + + it('should return an empty array when no bid requests', function () { + const bidRequest = spec.buildRequests([], bidderRequest) + expect(bidRequest).to.be.an('array') + expect(bidRequest.length).to.equal(0) + }) + + it('should return a valid bid request object', function () { + const request = spec.buildRequests(bidRequests, bidderRequest) + + expect(request).to.be.an('array') + expect(request[0].data).to.be.an('object') + expect(request[0].method).to.equal('POST') + expect(request[0].url).to.not.equal('') + expect(request[0].url).to.not.equal(undefined) + expect(request[0].url).to.not.equal(null) + }) + }) + + describe('onBidWon', function() { + beforeEach(function() { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function() { + utils.triggerPixel.restore(); + }); + + it('Should not trigger pixel if bid does not contain nurl', function() { + spec.onBidWon({}); + expect(utils.triggerPixel.callCount).to.equal(0) + }) + + it('Should trigger pixel if bid nurl', function() { + spec.onBidWon({ + burl: 'https://example.com/some-tracker' + }); + expect(utils.triggerPixel.callCount).to.equal(1) + }) + }) + + describe('getPageKeywords function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document keywords if available', function() { + const keywordsContent = 'keyword1,keyword2,keyword3'; + const fakeTopDocument = { + querySelector: sandbox.stub() + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + + const result = getPageKeywords({ top: fakeTopWindow }); + expect(result).to.equal(keywordsContent); + }); + + it('should return the current document keywords if top document is not accessible', function() { + const keywordsContent = 'keyword1,keyword2,keyword3'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }); + + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + + const result = getPageKeywords(fakeWindow); + expect(result).to.equal(keywordsContent); + }); + + it('should return an empty string if no keywords meta tag is found', function() { + sandbox.stub(document, 'querySelector').withArgs('meta[name="keywords"]').returns(null); + + const result = getPageKeywords(); + expect(result).to.equal(''); + }); + }); + + describe('parseUserAgent function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the platform and version IOS', function() { + const uaStub = sandbox.stub(window.navigator, 'userAgent'); + uaStub.value('Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1'); + + const result = parseUserAgent(); + expect(result.platform).to.equal('ios'); + expect(result.version).to.equal('iphone'); + expect(result.device).to.equal('16.6'); + }); + + it('should return the platform and version android', function() { + const uaStub = sandbox.stub(window.navigator, 'userAgent'); + uaStub.value('Mozilla/5.0 (Linux; Android 5.0.1; SM-G920V Build/LRX22C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36'); + + const result = parseUserAgent(); + expect(result.platform).to.equal('android'); + expect(result.version).to.equal('5.0'); + expect(result.device).to.equal(''); + }) + }) +}) From 7ab6165e342294fafa087135af219f3f88f0e220 Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Thu, 2 May 2024 21:59:31 +0400 Subject: [PATCH 698/760] Limelight Digital Bid Adapter: fix page field filling (#11436) --- modules/limelightDigitalBidAdapter.js | 6 ++-- .../limelightDigitalBidAdapter_spec.js | 36 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index acc6876b822..c816f800fda 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -128,7 +128,8 @@ function buildRequest(winTop, host, adUnits, bidderRequest) { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, adUnits: adUnits, - sua: bidderRequest?.ortb2?.device?.sua + sua: bidderRequest?.ortb2?.device?.sua, + page: bidderRequest?.ortb2?.site?.page || bidderRequest?.refererInfo?.page } } } @@ -170,8 +171,7 @@ function buildPlacement(bidRequest) { custom2: bidRequest.params.custom2, custom3: bidRequest.params.custom3, custom4: bidRequest.params.custom4, - custom5: bidRequest.params.custom5, - page: bidRequest.refererInfo.page + custom5: bidRequest.params.custom5 } } } diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 9e8a00959d4..51cf8cd14ee 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -17,9 +17,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page1' - }, placementCode: 'placement_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', mediaTypes: { @@ -68,9 +65,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page2' - }, placementCode: 'placement_1', auctionId: '482f88de-29ab-45c8-981a-d25e39454a34', sizes: [[350, 200]], @@ -121,9 +115,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page3' - }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', sizes: [[800, 600]], @@ -171,9 +162,6 @@ describe('limelightDigitalAdapter', function () { custom4: 'custom4', custom5: 'custom5' }, - refererInfo: { - page: 'https://publisher.com/page4' - }, placementCode: 'placement_2', auctionId: 'e4771143-6aa7-41ec-8824-ced4342c96c8', video: { @@ -218,6 +206,9 @@ describe('limelightDigitalAdapter', function () { architecture: 'arm' } } + }, + refererInfo: { + page: 'testPage' } } const serverRequests = spec.buildRequests([bid1, bid2, bid3, bid4], bidderRequest) @@ -243,7 +234,8 @@ describe('limelightDigitalAdapter', function () { 'deviceHeight', 'secure', 'adUnits', - 'sua' + 'sua', + 'page' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -262,8 +254,7 @@ describe('limelightDigitalAdapter', function () { 'custom2', 'custom3', 'custom4', - 'custom5', - 'page' + 'custom5' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -277,12 +268,13 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom3).to.be.a('string'); expect(adUnit.custom4).to.be.a('string'); expect(adUnit.custom5).to.be.a('string'); - expect(adUnit.page).to.be.a('string'); }) expect(data.sua.browsers).to.be.a('array'); expect(data.sua.platform).to.be.a('array'); expect(data.sua.mobile).to.be.a('number'); expect(data.sua.architecture).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.page).to.be.equal('testPage'); }) }) it('Returns valid URL', function () { @@ -298,6 +290,17 @@ describe('limelightDigitalAdapter', function () { const serverRequests = spec.buildRequests([]) expect(serverRequests).to.be.an('array').that.is.empty }) + it('Returns request with page field value from ortb2 object if ortb2 has page field', function () { + bidderRequest.ortb2.site = { + page: 'testSitePage' + } + const serverRequests = spec.buildRequests([bid1], bidderRequest) + expect(serverRequests).to.have.lengthOf(1) + serverRequests.forEach(serverRequest => { + expect(serverRequest.data.page).to.be.a('string'); + expect(serverRequest.data.page).to.be.equal('testSitePage'); + }) + }) }) describe('interpretBannerResponse', function () { let resObject = { @@ -716,5 +719,4 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.publisherId).to.equal(bid.params.publisherId); expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); expect(adUnit.supplyChain).to.deep.equal(bid.schain); - expect(adUnit.page).to.equal(bid.refererInfo.page); } From 7d52b11169ab715284fa72162ec6425c61726d9c Mon Sep 17 00:00:00 2001 From: johnwier <49074029+johnwier@users.noreply.github.com> Date: Mon, 6 May 2024 10:32:16 -0700 Subject: [PATCH 699/760] Conversant Adapter - remove transformBidParams (#11441) Co-authored-by: johwier --- modules/conversantBidAdapter.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index ebcad38d866..76ff2a9e2ad 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -15,7 +15,6 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getStorageManager} from '../src/storageManager.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; @@ -180,20 +179,6 @@ export const spec = { return converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); }, - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - transformBidParams: function(params, isOpenRtb) { - return convertTypes({ - 'site_id': 'string', - 'secure': 'number', - 'mobile': 'number' - }, params); - }, - /** * Register User Sync. */ From 8a57af7acfd32e60c0cfa5d18117de3bde6a8746 Mon Sep 17 00:00:00 2001 From: Ruslan S Date: Tue, 7 May 2024 01:53:05 +0800 Subject: [PATCH 700/760] [Smaato] Migrating to ortbConverter requests build process (#11433) - Response handling will be done separately --- modules/smaatoBidAdapter.js | 421 ++++---- test/spec/modules/smaatoBidAdapter_spec.js | 1025 +++++++++++--------- 2 files changed, 755 insertions(+), 691 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 64d8765dc04..9a0a0f75623 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -3,10 +3,11 @@ import {find} from '../src/polyfill.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import { NATIVE_IMAGE_TYPES } from '../src/constants.js'; +import {NATIVE_IMAGE_TYPES} from '../src/constants.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; import {fill} from '../libraries/appnexusUtils/anUtils.js'; import {chunk} from '../libraries/chunk/chunk.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -18,134 +19,14 @@ import {chunk} from '../libraries/chunk/chunk.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_1.8' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_2.0' +const TTL = 300; const CURRENCY = 'USD'; - -const buildOpenRtbBidRequest = (bidRequest, bidderRequest) => { - const requestTemplate = { - id: bidderRequest.bidderRequestId, - at: 1, - cur: [CURRENCY], - tmax: bidderRequest.timeout, - site: { - id: window.location.hostname, - // TODO: do the fallbacks make sense here? - domain: bidderRequest.refererInfo.domain || window.location.hostname, - page: bidderRequest.refererInfo.page || window.location.href, - ref: bidderRequest.refererInfo.ref - }, - device: { - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - ua: navigator.userAgent, - dnt: getDNT() ? 1 : 0, - h: screen.height, - w: screen.width - }, - regs: { - coppa: config.getConfig('coppa') === true ? 1 : 0, - ext: {} - }, - user: { - ext: {} - }, - source: { - ext: { - schain: bidRequest.schain - } - }, - ext: { - client: SMAATO_CLIENT - } - }; - - let ortb2 = bidderRequest.ortb2 || {}; - Object.assign(requestTemplate.user, ortb2.user); - Object.assign(requestTemplate.site, ortb2.site); - - deepSetValue(requestTemplate, 'site.publisher.id', deepAccess(bidRequest, 'params.publisherId')); - - if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) { - deepSetValue(requestTemplate, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - deepSetValue(requestTemplate, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent !== undefined) { - deepSetValue(requestTemplate, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (ortb2.regs?.gpp !== undefined) { - deepSetValue(requestTemplate, 'regs.ext.gpp', ortb2.regs.gpp); - deepSetValue(requestTemplate, 'regs.ext.gpp_sid', ortb2.regs.gpp_sid); - } - - if (ortb2.device?.ifa !== undefined) { - deepSetValue(requestTemplate, 'device.ifa', ortb2.device.ifa); - } - - if (ortb2.device?.geo !== undefined) { - deepSetValue(requestTemplate, 'device.geo', ortb2.device.geo); - } - - if (deepAccess(bidRequest, 'params.app')) { - if (!deepAccess(requestTemplate, 'device.geo')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(requestTemplate, 'device.geo', geo); - } - if (!deepAccess(requestTemplate, 'device.ifa')) { - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(requestTemplate, 'device.ifa', ifa); - } - } - - const eids = deepAccess(bidRequest, 'userIdAsEids'); - if (eids && eids.length) { - deepSetValue(requestTemplate, 'user.ext.eids', eids); - } - - let requests = []; - - if (deepAccess(bidRequest, 'mediaTypes.banner')) { - const bannerRequest = Object.assign({}, requestTemplate, createBannerImp(bidRequest)); - requests.push(bannerRequest); - } - - const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); - if (videoMediaType) { - if (videoMediaType.context === ADPOD) { - const adPodRequest = Object.assign({}, requestTemplate, createAdPodImp(bidRequest, videoMediaType)); - addOptionalAdpodParameters(adPodRequest, videoMediaType); - requests.push(adPodRequest); - } else { - const videoRequest = Object.assign({}, requestTemplate, createVideoImp(bidRequest, videoMediaType)); - requests.push(videoRequest); - } - } - - const nativeOrtbRequest = bidRequest.nativeOrtbRequest; - if (nativeOrtbRequest) { - const nativeRequest = Object.assign({}, requestTemplate, createNativeImp(bidRequest, nativeOrtbRequest)); - requests.push(nativeRequest); - } - - return requests; -} - -const buildServerRequest = (validBidRequest, data) => { - logInfo('[SMAATO] OpenRTB Request:', data); - return { - method: 'POST', - url: validBidRequest.params.endpoint || SMAATO_ENDPOINT, - data: JSON.stringify(data), - options: { - withCredentials: true, - crossOrigin: true, - } - }; -} +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, gvlid: 82, /** @@ -195,13 +76,30 @@ export const spec = { return true; }, - buildRequests: (validBidRequests, bidderRequest) => { + buildRequests: (bidRequests, bidderRequest) => { logInfo('[SMAATO] Client version:', SMAATO_CLIENT); - return validBidRequests.map((validBidRequest) => { - const openRtbBidRequests = buildOpenRtbBidRequest(validBidRequest, bidderRequest); - return openRtbBidRequests.map((openRtbBidRequest) => buildServerRequest(validBidRequest, openRtbBidRequest)); - }).reduce((acc, item) => item != null && acc.concat(item), []); + let requests = []; + bidRequests.forEach(bid => { + // separate requests per mediaType + SUPPORTED_MEDIA_TYPES.forEach(mediaType => { + if ((bid.mediaTypes && bid.mediaTypes[mediaType]) || (mediaType === NATIVE && bid.nativeOrtbRequest)) { + const data = converter.toORTB({bidderRequest, bidRequests: [bid], context: {mediaType}}); + requests.push({ + method: 'POST', + url: bid.params.endpoint || SMAATO_ENDPOINT, + data: JSON.stringify(data), + options: { + withCredentials: true, + crossOrigin: true, + }, + bidderRequest + }) + } + }); + }); + + return requests; }, /** * Unpack the response from the server into a list of bids. @@ -239,7 +137,7 @@ export const spec = { creativeId: bid.crid, dealId: bid.dealid || null, netRevenue: deepAccess(bid, 'ext.net', true), - currency: response.cur, + currency: CURRENCY, meta: { advertiserDomains: bid.adomain, networkName: bid.bidderName, @@ -306,6 +204,172 @@ export const spec = { } registerBidder(spec); +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL, + currency: CURRENCY + }, + request(buildRequest, imps, bidderRequest, context) { + function isGdprApplicable() { + return bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies; + } + + const request = buildRequest(imps, bidderRequest, context); + const bidRequest = context.bidRequests[0]; + let siteContent; + const mediaType = context.mediaType; + if (mediaType === VIDEO) { + const videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams.context === ADPOD) { + request.imp = createAdPodImp(request.imp[0], videoParams); + siteContent = addOptionalAdpodParameters(videoParams); + } + } + + request.at = 1; + + if (request.user) { + if (isGdprApplicable()) { + deepSetValue(request.user, 'ext.consent', bidderRequest.gdprConsent.consentString); + } + } else { + const eids = deepAccess(bidRequest, 'userIdAsEids'); + request.user = { + ext: { + consent: isGdprApplicable() ? bidderRequest.gdprConsent.consentString : null, + eids: (eids && eids.length) ? eids : null + } + } + } + + if (request.site) { + request.site.id = window.location.hostname + if (siteContent) { + request.site.content = siteContent; + } + } else { + request.site = { + id: window.location.hostname, + domain: bidderRequest.refererInfo.domain || window.location.hostname, + page: bidderRequest.refererInfo.page || window.location.href, + ref: bidderRequest.refererInfo.ref, + content: siteContent || null + } + } + deepSetValue(request.site, 'publisher.id', bidRequest.params.publisherId); + + if (request.regs) { + if (isGdprApplicable()) { + deepSetValue(request.regs, 'ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + if (bidderRequest.uspConsent !== undefined) { + deepSetValue(request.regs, 'ext.us_privacy', bidderRequest.uspConsent); + } + if (request.regs?.gpp) { + deepSetValue(request.regs, 'ext.gpp', request.regs.gpp); + deepSetValue(request.regs, 'ext.gpp_sid', request.regs.gpp_sid); + } + } else { + request.regs = { + coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: { + gdpr: isGdprApplicable() ? bidderRequest.gdprConsent.gdprApplies ? 1 : 0 : null, + us_privacy: bidderRequest.uspConsent + } + } + } + + if (request.device) { + if (bidRequest.params.app) { + if (!deepAccess(request.device, 'geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(request.device, 'geo', geo); + } + if (!deepAccess(request.device, 'ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(request.device, 'ifa', ifa); + } + } + } else { + request.device = { + language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', + ua: navigator.userAgent, + dnt: getDNT() ? 1 : 0, + h: screen.height, + w: screen.width + } + if (!deepAccess(request.device, 'geo')) { + const geo = deepAccess(bidRequest, 'params.app.geo'); + deepSetValue(request.device, 'geo', geo); + } + if (!deepAccess(request.device, 'ifa')) { + const ifa = deepAccess(bidRequest, 'params.app.ifa'); + deepSetValue(request.device, 'ifa', ifa); + } + } + + request.source = { + ext: { + schain: bidRequest.schain + } + }; + request.ext = { + client: SMAATO_CLIENT + } + return request; + }, + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'tagid', bidRequest.params.adbreakId || bidRequest.params.adspaceId); + if (imp.bidfloorcur && imp.bidfloorcur !== CURRENCY) { + delete imp.bidfloor; + delete imp.bidfloorcur; + } + return imp; + }, + + overrides: { + imp: { + banner(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + + if (mediaType === BANNER) { + imp.bidfloor = getBidFloor(bidRequest, BANNER, getAdUnitSizes(bidRequest)); + } + + orig(imp, bidRequest, context); + }, + + video(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + if (mediaType === VIDEO) { + const videoParams = bidRequest.mediaTypes[VIDEO]; + imp.bidfloor = getBidFloor(bidRequest, VIDEO, videoParams.playerSize); + if (videoParams.context !== ADPOD) { + deepSetValue(imp, 'video.ext', { + rewarded: videoParams.ext && videoParams.ext.rewarded ? videoParams.ext.rewarded : 0 + }) + } + } + + orig(imp, bidRequest, context); + }, + + native(orig, imp, bidRequest, context) { + const mediaType = context.mediaType; + + if (mediaType === NATIVE) { + imp.bidfloor = getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(bidRequest.nativeOrtbRequest)); + } + + orig(imp, bidRequest, context); + } + }, + } +}); + const createImgAd = (adm) => { const image = JSON.parse(adm).image; @@ -346,65 +410,6 @@ const createNativeAd = (adm) => { } }; -function createBannerImp(bidRequest) { - const adUnitSizes = getAdUnitSizes(bidRequest); - const sizes = adUnitSizes.map((size) => ({w: size[0], h: size[1]})); - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, BANNER, adUnitSizes), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - banner: { - w: sizes[0].w, - h: sizes[0].h, - format: sizes - } - }] - }; -} - -function createVideoImp(bidRequest, videoMediaType) { - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - video: { - mimes: videoMediaType.mimes, - minduration: videoMediaType.minduration, - startdelay: videoMediaType.startdelay, - linearity: videoMediaType.linearity, - w: videoMediaType.playerSize[0][0], - h: videoMediaType.playerSize[0][1], - maxduration: videoMediaType.maxduration, - skip: videoMediaType.skip, - protocols: videoMediaType.protocols, - ext: { - rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0 - }, - skipmin: videoMediaType.skipmin, - api: videoMediaType.api - } - }] - }; -} - -function createNativeImp(bidRequest, nativeRequest) { - return { - imp: [{ - id: bidRequest.bidId, - tagid: deepAccess(bidRequest, 'params.adspaceId'), - bidfloor: getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(nativeRequest)), - native: { - request: JSON.stringify(nativeRequest), - ver: '1.2' - } - }] - }; -} - function getNativeMainImageSize(nativeRequest) { const mainImage = find(nativeRequest.assets, asset => asset.hasOwnProperty('img') && asset.img.type === NATIVE_IMAGE_TYPES.MAIN) if (mainImage) { @@ -418,30 +423,12 @@ function getNativeMainImageSize(nativeRequest) { return [] } -function createAdPodImp(bidRequest, videoMediaType) { - const tagid = deepAccess(bidRequest, 'params.adbreakId') +function createAdPodImp(imp, videoMediaType) { const bce = config.getConfig('adpod.brandCategoryExclusion') - let imp = { - id: bidRequest.bidId, - tagid: tagid, - bidfloor: getBidFloor(bidRequest, VIDEO, videoMediaType.playerSize), - instl: deepAccess(bidRequest.ortb2Imp, 'instl'), - video: { - w: videoMediaType.playerSize[0][0], - h: videoMediaType.playerSize[0][1], - mimes: videoMediaType.mimes, - startdelay: videoMediaType.startdelay, - linearity: videoMediaType.linearity, - skip: videoMediaType.skip, - protocols: videoMediaType.protocols, - skipmin: videoMediaType.skipmin, - api: videoMediaType.api, - ext: { - context: ADPOD, - brandcategoryexclusion: bce !== undefined && bce - } - } - } + imp.video.ext = { + context: ADPOD, + brandcategoryexclusion: bce !== undefined && bce + }; const numberOfPlacements = getAdPodNumberOfPlacements(videoMediaType) let imps = fill(imp, numberOfPlacements) @@ -471,9 +458,7 @@ function createAdPodImp(bidRequest, videoMediaType) { }); } - return { - imp: imps - } + return imps } function getAdPodNumberOfPlacements(videoMediaType) { @@ -486,7 +471,7 @@ function getAdPodNumberOfPlacements(videoMediaType) { : numberOfPlacements } -const addOptionalAdpodParameters = (request, videoMediaType) => { +const addOptionalAdpodParameters = (videoMediaType) => { const content = {} if (videoMediaType.tvSeriesName) { @@ -509,7 +494,7 @@ const addOptionalAdpodParameters = (request, videoMediaType) => { } if (!isEmpty(content)) { - request.site.content = content + return content } } diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 185dee2430f..28fd06c4b8d 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -1,7 +1,16 @@ import {spec} from 'modules/smaatoBidAdapter.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; -import {createEidsArray} from 'modules/userId/eids.js'; + +// load modules that register ORTB processors +import 'src/prebid.js' +import 'modules/currency.js'; +import 'modules/userId/index.js'; +import 'modules/multibid/index.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagement.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; const ADTYPE_IMG = 'Img'; const ADTYPE_RICHMEDIA = 'Richmedia'; @@ -112,14 +121,13 @@ describe('smaatoBidAdapterTest', () => { describe('buildRequests', () => { const BANNER_OPENRTB_IMP = { - w: 300, - h: 50, format: [ { h: 50, w: 300 } - ] + ], + topframe: 0, } describe('common', () => { @@ -145,13 +153,6 @@ describe('smaatoBidAdapterTest', () => { expect(req.at).to.be.equal(1); }) - it('currency is US dollar', () => { - const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.cur).to.be.deep.equal(['USD']); - }) - it('can override endpoint', () => { const overridenEndpoint = 'https://prebid/bidder'; const updatedBidRequest = utils.deepClone(singleBannerBidRequest); @@ -178,7 +179,7 @@ describe('smaatoBidAdapterTest', () => { it('sends bidfloor when configured', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function(arg) { + singleBannerBidRequestWithFloor.getFloor = function (arg) { if (arg.currency === 'USD' && arg.mediaType === 'banner' && JSON.stringify(arg.size) === JSON.stringify([300, 50])) { @@ -202,7 +203,7 @@ describe('smaatoBidAdapterTest', () => { } } }); - singleBannerMultipleSizesBidRequestWithFloor.getFloor = function(arg) { + singleBannerMultipleSizesBidRequestWithFloor.getFloor = function (arg) { if (arg.size === '*') { return { currency: 'USD', @@ -228,7 +229,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when invalid', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return undefined; } const reqs = spec.buildRequests([singleBannerBidRequestWithFloor], defaultBidderRequest); @@ -239,7 +240,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when not a number', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return { currency: 'USD', } @@ -252,7 +253,7 @@ describe('smaatoBidAdapterTest', () => { it('sends undefined bidfloor when wrong currency', () => { const singleBannerBidRequestWithFloor = Object.assign({}, singleBannerBidRequest); - singleBannerBidRequestWithFloor.getFloor = function() { + singleBannerBidRequestWithFloor.getFloor = function () { return { currency: 'EUR', floor: 0.123 @@ -275,6 +276,58 @@ describe('smaatoBidAdapterTest', () => { expect(req.site.publisher.id).to.equal('publisherId'); }) + it('sends correct site from ortb2', () => { + const domain = 'domain'; + const page = 'page'; + const ref = 'ref'; + const ortb2 = { + site: { + name: 'example', + domain: domain, + page: page, + ref: ref + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.id).to.exist.and.to.be.a('string'); + expect(req.site.domain).to.equal(domain); + expect(req.site.page).to.equal(page); + expect(req.site.ref).to.equal(ref); + expect(req.site.publisher.id).to.equal('publisherId'); + }) + + it('sends correct device from ortb2', () => { + const language = 'language' + const ua = 'ua' + const sua = 'sua' + const dnt = 1 + const w = 2 + const h = 3 + const ortb2 = { + device: { + language: language, + ua: ua, + sua: sua, + dnt: dnt, + w: w, + h: h + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.device.language).to.equal(language); + expect(req.device.ua).to.equal(ua); + expect(req.device.sua).to.equal(sua); + expect(req.device.dnt).to.equal(dnt); + expect(req.device.w).to.equal(w); + expect(req.device.h).to.equal(h); + }) + it('sends gdpr applies if exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -283,6 +336,19 @@ describe('smaatoBidAdapterTest', () => { expect(req.user.ext.consent).to.equal(CONSENT_STRING); }); + it('sends correct coppa from ortb2', () => { + const ortb2 = { + regs: { + coppa: 1 + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.coppa).to.equal(1); + }) + it('sends no gdpr applies if no gdpr exists', () => { const reqs = spec.buildRequests([singleBannerBidRequest], MINIMAL_BIDDER_REQUEST); @@ -321,7 +387,7 @@ describe('smaatoBidAdapterTest', () => { }); it('sends instl if instl exists', () => { - const instl = { instl: 1 }; + const instl = {instl: 1}; const bidRequestWithInstl = Object.assign({}, singleBannerBidRequest, {ortb2Imp: instl}); const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); @@ -381,6 +447,16 @@ describe('smaatoBidAdapterTest', () => { expect(req.device.geo.lon).to.equal(9.9872); }); + it('sends user first party data when user is not defined', () => { + const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.user.gender).be.undefined; + expect(req.user.yob).to.be.undefined; + expect(req.user.keywords).to.be.undefined; + expect(req.user.ext.consent).to.equal(CONSENT_STRING); + }); + it('has no user ids', () => { const reqs = spec.buildRequests([singleBannerBidRequest], defaultBidderRequest); @@ -456,531 +532,534 @@ describe('smaatoBidAdapterTest', () => { bidderWinsCount: 0 }; - it('sends correct video imps', () => { - const reqs = spec.buildRequests([singleVideoBidRequest], defaultBidderRequest); + if (FEATURES.VIDEO) { + it('sends correct video imps', () => { + const reqs = spec.buildRequests([singleVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].id).to.be.equal('bidId'); - expect(req.imp[0].tagid).to.be.equal('adspaceId'); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); - it('sends bidfloor when configured', () => { - const singleVideoBidRequestWithFloor = Object.assign({}, singleVideoBidRequest); - singleVideoBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'video' && - JSON.stringify(arg.size) === JSON.stringify([768, 1024])) { - return { - currency: 'USD', - floor: 0.456 + it('sends bidfloor when configured', () => { + const singleVideoBidRequestWithFloor = Object.assign({}, singleVideoBidRequest); + singleVideoBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([768, 1024])) { + return { + currency: 'USD', + floor: 0.456 + } } } - } - const reqs = spec.buildRequests([singleVideoBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([singleVideoBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.456); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.456); + }); - it('sends instl if instl exists', () => { - const instl = { instl: 1 }; - const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); + it('sends instl if instl exists', () => { + const instl = {instl: 1}; + const bidRequestWithInstl = Object.assign({}, singleVideoBidRequest, {ortb2Imp: instl}); - const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].instl).to.equal(1); - }); - - it('splits multi format bid requests', () => { - const combinedBannerAndVideoBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - mediaTypes: { - banner: BANNER_PREBID_MEDIATYPE, - video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE - }, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - sizes: [[300, 50]], - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }; - - const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); - - expect(reqs).to.have.length(2); - expect(JSON.parse(reqs[0].data).imp[0].banner).to.deep.equal(BANNER_OPENRTB_IMP); - expect(JSON.parse(reqs[0].data).imp[0].video).to.not.exist; - expect(JSON.parse(reqs[1].data).imp[0].banner).to.not.exist; - expect(JSON.parse(reqs[1].data).imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + }); - describe('ad pod / long form video', () => { - describe('required parameters with requireExactDuration false', () => { - const ADBREAK_ID = 'adbreakId'; - const ADPOD = 'adpod'; - const BID_ID = '4331'; - const W = 640; - const H = 480; - const ADPOD_DURATION = 300; - const DURATION_RANGE = [15, 30]; - const longFormVideoBidRequest = { + it('splits multi format bid requests', () => { + const combinedBannerAndVideoBidRequest = { + bidder: 'smaato', params: { publisherId: 'publisherId', - adbreakId: ADBREAK_ID, + adspaceId: 'adspaceId' }, mediaTypes: { - video: { - context: ADPOD, - playerSize: [[W, H]], - adPodDurationSec: ADPOD_DURATION, - durationRangeSec: DURATION_RANGE, - requireExactDuration: false - } + banner: BANNER_PREBID_MEDIATYPE, + video: VIDEO_OUTSTREAM_PREBID_MEDIATYPE }, - bidId: BID_ID + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + sizes: [[300, 50]], + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 }; - it('sends required fields', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.exist; - expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); - expect(req.imp[0].id).to.be.equal(BID_ID); - expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[0].video.w).to.be.equal(W); - expect(req.imp[0].video.h).to.be.equal(H); - expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[0].video.sequence).to.be.equal(1); - expect(req.imp[1].id).to.be.equal(BID_ID); - expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[1].bidfloor).to.be.undefined; - expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[1].video.w).to.be.equal(W); - expect(req.imp[1].video.h).to.be.equal(H); - expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.sequence).to.be.equal(2); - }); + const reqs = spec.buildRequests([combinedBannerAndVideoBidRequest], defaultBidderRequest); - it('sends instl if instl exists', () => { - const instl = { instl: 1 }; - const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); + expect(reqs).to.have.length(2); + expect(JSON.parse(reqs[0].data).imp[0].banner).to.deep.equal(BANNER_OPENRTB_IMP); + expect(JSON.parse(reqs[0].data).imp[0].video).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].banner).to.not.exist; + expect(JSON.parse(reqs[1].data).imp[0].video).to.deep.equal(VIDEO_OUTSTREAM_OPENRTB_IMP); + }); - const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + describe('ad pod / long form video', () => { + describe('required parameters with requireExactDuration false', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 300; + const DURATION_RANGE = [15, 30]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: false + } + }, + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.exist; + expect(req.imp.length).to.be.equal(ADPOD_DURATION / DURATION_RANGE[0]); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].bidfloor).to.be.undefined; + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + }); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].instl).to.equal(1); - expect(req.imp[1].instl).to.equal(1); - }); + it('sends instl if instl exists', () => { + const instl = {instl: 1}; + const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, {ortb2Imp: instl}); - it('sends bidfloor when configured', () => { - const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); - longFormVideoBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'video' && - JSON.stringify(arg.size) === JSON.stringify([640, 480])) { - return { - currency: 'USD', - floor: 0.789 + const reqs = spec.buildRequests([bidRequestWithInstl], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].instl).to.equal(1); + expect(req.imp[1].instl).to.equal(1); + }); + + it('sends bidfloor when configured', () => { + const longFormVideoBidRequestWithFloor = Object.assign({}, longFormVideoBidRequest); + longFormVideoBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'video' && + JSON.stringify(arg.size) === JSON.stringify([640, 480])) { + return { + currency: 'USD', + floor: 0.789 + } } } - } - const reqs = spec.buildRequests([longFormVideoBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.789); - expect(req.imp[1].bidfloor).to.be.equal(0.789); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.789); + expect(req.imp[1].bidfloor).to.be.equal(0.789); + }); - it('sends brand category exclusion as true when config is set to true', () => { - config.setConfig({adpod: {brandCategoryExclusion: true}}); + it('sends brand category exclusion as true when config is set to true', () => { + config.setConfig({adpod: {brandCategoryExclusion: true}}); - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(true); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(true); + }); - it('sends brand category exclusion as false when config is set to false', () => { - config.setConfig({adpod: {brandCategoryExclusion: false}}); + it('sends brand category exclusion as false when config is set to false', () => { + config.setConfig({adpod: {brandCategoryExclusion: false}}); - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); - it('sends brand category exclusion as false when config is not set', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + it('sends brand category exclusion as false when config is not set', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.brandcategoryexclusion).to.be.equal(false); + }); }); - }); - describe('required parameters with requireExactDuration true', () => { - const ADBREAK_ID = 'adbreakId'; - const ADPOD = 'adpod'; - const BID_ID = '4331'; - const W = 640; - const H = 480; - const ADPOD_DURATION = 5; - const DURATION_RANGE = [5, 15, 25]; - const longFormVideoBidRequest = { - params: { - publisherId: 'publisherId', - adbreakId: ADBREAK_ID, - }, - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[W, H]], - adPodDurationSec: ADPOD_DURATION, - durationRangeSec: DURATION_RANGE, - requireExactDuration: true - } - }, - bidId: BID_ID - }; - - it('sends required fields', () => { - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.id).to.exist; - expect(req.imp.length).to.be.equal(DURATION_RANGE.length); - expect(req.imp[0].id).to.be.equal(BID_ID); - expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[0].video.w).to.be.equal(W); - expect(req.imp[0].video.h).to.be.equal(H); - expect(req.imp[0].video.minduration).to.be.equal(DURATION_RANGE[0]); - expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[0]); - expect(req.imp[0].video.sequence).to.be.equal(1); - expect(req.imp[1].id).to.be.equal(BID_ID); - expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[1].video.w).to.be.equal(W); - expect(req.imp[1].video.h).to.be.equal(H); - expect(req.imp[1].video.minduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); - expect(req.imp[1].video.sequence).to.be.equal(2); - expect(req.imp[2].id).to.be.equal(BID_ID); - expect(req.imp[2].tagid).to.be.equal(ADBREAK_ID); - expect(req.imp[2].video.ext.context).to.be.equal(ADPOD); - expect(req.imp[2].video.w).to.be.equal(W); - expect(req.imp[2].video.h).to.be.equal(H); - expect(req.imp[2].video.minduration).to.be.equal(DURATION_RANGE[2]); - expect(req.imp[2].video.maxduration).to.be.equal(DURATION_RANGE[2]); - expect(req.imp[2].video.sequence).to.be.equal(3); + describe('required parameters with requireExactDuration true', () => { + const ADBREAK_ID = 'adbreakId'; + const ADPOD = 'adpod'; + const BID_ID = '4331'; + const W = 640; + const H = 480; + const ADPOD_DURATION = 5; + const DURATION_RANGE = [5, 15, 25]; + const longFormVideoBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: ADBREAK_ID, + }, + mediaTypes: { + video: { + context: ADPOD, + playerSize: [[W, H]], + adPodDurationSec: ADPOD_DURATION, + durationRangeSec: DURATION_RANGE, + requireExactDuration: true + } + }, + bidId: BID_ID + }; + + it('sends required fields', () => { + const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.id).to.exist; + expect(req.imp.length).to.be.equal(DURATION_RANGE.length); + expect(req.imp[0].id).to.be.equal(BID_ID); + expect(req.imp[0].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[0].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[0].video.w).to.be.equal(W); + expect(req.imp[0].video.h).to.be.equal(H); + expect(req.imp[0].video.minduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.maxduration).to.be.equal(DURATION_RANGE[0]); + expect(req.imp[0].video.sequence).to.be.equal(1); + expect(req.imp[1].id).to.be.equal(BID_ID); + expect(req.imp[1].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[1].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[1].video.w).to.be.equal(W); + expect(req.imp[1].video.h).to.be.equal(H); + expect(req.imp[1].video.minduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.maxduration).to.be.equal(DURATION_RANGE[1]); + expect(req.imp[1].video.sequence).to.be.equal(2); + expect(req.imp[2].id).to.be.equal(BID_ID); + expect(req.imp[2].tagid).to.be.equal(ADBREAK_ID); + expect(req.imp[2].video.ext.context).to.be.equal(ADPOD); + expect(req.imp[2].video.w).to.be.equal(W); + expect(req.imp[2].video.h).to.be.equal(H); + expect(req.imp[2].video.minduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.maxduration).to.be.equal(DURATION_RANGE[2]); + expect(req.imp[2].video.sequence).to.be.equal(3); + }); }); - }); - - describe('forwarding of optional parameters', () => { - const MIMES = ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v']; - const STARTDELAY = 0; - const LINEARITY = 1; - const SKIP = 1; - const PROTOCOLS = [7]; - const SKIPMIN = 5; - const API = [7]; - const validBasicAdpodBidRequest = { - params: { - publisherId: 'publisherId', - adbreakId: 'adbreakId', - }, - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - mimes: MIMES, - startdelay: STARTDELAY, - linearity: LINEARITY, - skip: SKIP, - protocols: PROTOCOLS, - skipmin: SKIPMIN, - api: API - } - }, - bidId: 'bidId' - }; - it('sends general video fields when they are present', () => { - const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.mimes).to.eql(MIMES); - expect(req.imp[0].video.startdelay).to.be.equal(STARTDELAY); - expect(req.imp[0].video.linearity).to.be.equal(LINEARITY); - expect(req.imp[0].video.skip).to.be.equal(SKIP); - expect(req.imp[0].video.protocols).to.eql(PROTOCOLS); - expect(req.imp[0].video.skipmin).to.be.equal(SKIPMIN); - expect(req.imp[0].video.api).to.eql(API); - }); + describe('forwarding of optional parameters', () => { + const MIMES = ['video/mp4', 'video/quicktime', 'video/3gpp', 'video/x-m4v']; + const STARTDELAY = 0; + const LINEARITY = 1; + const SKIP = 1; + const PROTOCOLS = [7]; + const SKIPMIN = 5; + const API = [7]; + const validBasicAdpodBidRequest = { + params: { + publisherId: 'publisherId', + adbreakId: 'adbreakId', + }, + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 300, + durationRangeSec: [15, 30], + mimes: MIMES, + startdelay: STARTDELAY, + linearity: LINEARITY, + skip: SKIP, + protocols: PROTOCOLS, + skipmin: SKIPMIN, + api: API + } + }, + bidId: 'bidId' + }; + + it('sends general video fields when they are present', () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.mimes).to.eql(MIMES); + expect(req.imp[0].video.startdelay).to.be.equal(STARTDELAY); + expect(req.imp[0].video.linearity).to.be.equal(LINEARITY); + expect(req.imp[0].video.skip).to.be.equal(SKIP); + expect(req.imp[0].video.protocols).to.eql(PROTOCOLS); + expect(req.imp[0].video.skipmin).to.be.equal(SKIPMIN); + expect(req.imp[0].video.api).to.eql(API); + }); - it('sends series name when parameter is present', () => { - const SERIES_NAME = 'foo' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvSeriesName = SERIES_NAME; + it('sends series name when parameter is present', () => { + const SERIES_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeriesName = SERIES_NAME; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.series).to.be.equal(SERIES_NAME); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.series).to.be.equal(SERIES_NAME); + }); - it('sends episode name when parameter is present', () => { - const EPISODE_NAME = 'foo' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvEpisodeName = EPISODE_NAME; + it('sends episode name when parameter is present', () => { + const EPISODE_NAME = 'foo' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeName = EPISODE_NAME; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.title).to.be.equal(EPISODE_NAME); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.title).to.be.equal(EPISODE_NAME); + }); - it('sends season number as string when parameter is present', () => { - const SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST = 42 - const SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST = '42' - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvSeasonNumber = SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST; + it('sends season number as string when parameter is present', () => { + const SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST = 42 + const SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST = '42' + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvSeasonNumber = SEASON_NUMBER_AS_NUMBER_IN_PREBID_REQUEST; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.season).to.be.equal(SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.season).to.be.equal(SEASON_NUMBER_AS_STRING_IN_OUTGOING_REQUEST); + }); - it('sends episode number when parameter is present', () => { - const EPISODE_NUMBER = 42 - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.tvEpisodeNumber = EPISODE_NUMBER; + it('sends episode number when parameter is present', () => { + const EPISODE_NUMBER = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.tvEpisodeNumber = EPISODE_NUMBER; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.episode).to.be.equal(EPISODE_NUMBER); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.episode).to.be.equal(EPISODE_NUMBER); + }); - it('sends content length when parameter is present', () => { - const LENGTH = 42 - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentLengthSec = LENGTH; + it('sends content length when parameter is present', () => { + const LENGTH = 42 + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentLengthSec = LENGTH; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.len).to.be.equal(LENGTH); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.len).to.be.equal(LENGTH); + }); - it('sends livestream as 1 when content mode parameter is live', () => { - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentMode = 'live'; + it('sends livestream as 1 when content mode parameter is live', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'live'; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.livestream).to.be.equal(1); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(1); + }); - it('sends livestream as 0 when content mode parameter is on-demand', () => { - const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); - adpodRequestWithParameter.mediaTypes.video.contentMode = 'on-demand'; + it('sends livestream as 0 when content mode parameter is on-demand', () => { + const adpodRequestWithParameter = utils.deepClone(validBasicAdpodBidRequest); + adpodRequestWithParameter.mediaTypes.video.contentMode = 'on-demand'; - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); + const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.site.content.livestream).to.be.equal(0); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.site.content.livestream).to.be.equal(0); + }); - it("doesn't send any optional parameters when none are present", () => { - const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); + it('doesn\'t send any optional parameters when none are present', () => { + const reqs = spec.buildRequests([validBasicAdpodBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; - expect(req.site.content).to.not.exist; + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; + expect(req.site.content).to.not.exist; + }); }); }); - }); + } }); - describe('buildRequests for native imps', () => { - const NATIVE_OPENRTB_REQUEST = { - ver: '1.2', - assets: [ - { - id: 4, - required: 1, - img: { - type: 3, - w: 150, - h: 50, - } - }, - { - id: 2, - required: 1, - img: { - type: 2, - w: 50, - h: 50 - } - }, - { - id: 0, - required: 1, - title: { - len: 80 - } - }, - { - id: 1, - required: 1, - data: { - type: 1 - } - }, - { - id: 3, - required: 1, - data: { - type: 2 - } - }, - { - id: 5, - required: 1, - data: { - type: 3 - } - }, - { - id: 6, - required: 1, - data: { - type: 4 - } - }, - { - id: 7, - required: 1, - data: { - type: 5 - } - }, - { - id: 8, - required: 1, - data: { - type: 6 - } - }, - { - id: 9, - required: 1, - data: { - type: 7 - } - }, - { - id: 10, - required: 0, - data: { - type: 8 - } - }, - { - id: 11, - required: 1, - data: { - type: 9 - } - }, - { - id: 12, - require: 0, - data: { - type: 10 - } - }, - { - id: 13, - required: 0, - data: { - type: 11 - } - }, - { - id: 14, - required: 1, - data: { - type: 12 + if (FEATURES.NATIVE) { + describe('buildRequests for native imps', () => { + const NATIVE_OPENRTB_REQUEST = { + ver: '1.2', + assets: [ + { + id: 4, + required: 1, + img: { + type: 3, + w: 150, + h: 50, + } + }, + { + id: 2, + required: 1, + img: { + type: 2, + w: 50, + h: 50 + } + }, + { + id: 0, + required: 1, + title: { + len: 80 + } + }, + { + id: 1, + required: 1, + data: { + type: 1 + } + }, + { + id: 3, + required: 1, + data: { + type: 2 + } + }, + { + id: 5, + required: 1, + data: { + type: 3 + } + }, + { + id: 6, + required: 1, + data: { + type: 4 + } + }, + { + id: 7, + required: 1, + data: { + type: 5 + } + }, + { + id: 8, + required: 1, + data: { + type: 6 + } + }, + { + id: 9, + required: 1, + data: { + type: 7 + } + }, + { + id: 10, + required: 0, + data: { + type: 8 + } + }, + { + id: 11, + required: 1, + data: { + type: 9 + } + }, + { + id: 12, + require: 0, + data: { + type: 10 + } + }, + { + id: 13, + required: 0, + data: { + type: 11 + } + }, + { + id: 14, + required: 1, + data: { + type: 12 + } } - } - ] - }; + ] + }; - const singleNativeBidRequest = { - bidder: 'smaato', - params: { - publisherId: 'publisherId', - adspaceId: 'adspaceId' - }, - nativeOrtbRequest: NATIVE_OPENRTB_REQUEST, - adUnitCode: '/19968336/header-bid-tag-0', - transactionId: 'transactionId', - bidId: 'bidId', - bidderRequestId: 'bidderRequestId', - src: 'client', - bidRequestsCount: 1, - bidderRequestsCount: 1, - bidderWinsCount: 0 - }; + const singleNativeBidRequest = { + bidder: 'smaato', + params: { + publisherId: 'publisherId', + adspaceId: 'adspaceId' + }, + nativeOrtbRequest: NATIVE_OPENRTB_REQUEST, + adUnitCode: '/19968336/header-bid-tag-0', + transactionId: 'transactionId', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0 + }; - it('sends correct native imps', () => { - const reqs = spec.buildRequests([singleNativeBidRequest], defaultBidderRequest); + it('sends correct native imps', () => { + const reqs = spec.buildRequests([singleNativeBidRequest], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].id).to.be.equal('bidId'); - expect(req.imp[0].tagid).to.be.equal('adspaceId'); - expect(req.imp[0].bidfloor).to.be.undefined; - expect(req.imp[0].native.request).to.deep.equal(JSON.stringify(NATIVE_OPENRTB_REQUEST)); - }); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].id).to.be.equal('bidId'); + expect(req.imp[0].tagid).to.be.equal('adspaceId'); + expect(req.imp[0].bidfloor).to.be.undefined; + expect(req.imp[0].native.request).to.deep.equal(JSON.stringify(NATIVE_OPENRTB_REQUEST)); + }); - it('sends bidfloor when configured', () => { - const singleNativeBidRequestWithFloor = Object.assign({}, singleNativeBidRequest); - singleNativeBidRequestWithFloor.getFloor = function(arg) { - if (arg.currency === 'USD' && - arg.mediaType === 'native' && - JSON.stringify(arg.size) === JSON.stringify([150, 50])) { - return { - currency: 'USD', - floor: 0.123 + it('sends bidfloor when configured', () => { + const singleNativeBidRequestWithFloor = Object.assign({}, singleNativeBidRequest); + singleNativeBidRequestWithFloor.getFloor = function (arg) { + if (arg.currency === 'USD' && + arg.mediaType === 'native' && + JSON.stringify(arg.size) === JSON.stringify([150, 50])) { + return { + currency: 'USD', + floor: 0.123 + } } } - } - const reqs = spec.buildRequests([singleNativeBidRequestWithFloor], defaultBidderRequest); + const reqs = spec.buildRequests([singleNativeBidRequestWithFloor], defaultBidderRequest); - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].bidfloor).to.be.equal(0.123); + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.imp[0].bidfloor).to.be.equal(0.123); + }); }); - }); - + } describe('in-app requests', () => { const LOCATION = { lat: 33.3, @@ -1263,7 +1342,7 @@ describe('smaatoBidAdapterTest', () => { adm = ''; break; case ADTYPE_NATIVE: - adm = JSON.stringify({ native: NATIVE_RESPONSE }) + adm = JSON.stringify({native: NATIVE_RESPONSE}) break; default: throw Error('Invalid AdType'); From 3a1dff0f635139eca591585ebcde178271ae7265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kowalski?= <112544015+pkowalski-id5@users.noreply.github.com> Date: Mon, 6 May 2024 20:16:11 +0200 Subject: [PATCH 701/760] ID5 ID module: config call as bounce (#11424) --- modules/id5IdSystem.js | 44 +++--- test/spec/modules/id5IdSystem_spec.js | 203 ++++++++++++++------------ 2 files changed, 131 insertions(+), 116 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index 42f0044edc3..e7a7ec7f037 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -148,7 +148,7 @@ export const id5IdSubmodule = { id5id: { uid: universalUid, ext: ext - }, + } }; if (isPlainObject(ext.euid)) { @@ -156,7 +156,7 @@ export const id5IdSubmodule = { uid: ext.euid.uids[0].id, source: ext.euid.source, ext: {provider: ID5_DOMAIN} - } + }; } const abTestingResult = deepAccess(value, 'ab_testing.result'); @@ -196,7 +196,7 @@ export const id5IdSubmodule = { } if (!hasWriteConsentToLocalStorage(consentData)) { - logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.') + logInfo(LOG_PREFIX + 'Skipping ID5 local storage write because no consent given.'); return undefined; } @@ -204,7 +204,7 @@ export const id5IdSubmodule = { const fetchFlow = new IdFetchFlow(submoduleConfig, consentData, cacheIdObj, uspDataHandler.getConsentData(), gppDataHandler.getConsentData()); fetchFlow.execute() .then(response => { - cbFunction(response) + cbFunction(response); }) .catch(error => { logError(LOG_PREFIX + 'getId fetch encountered an error', error); @@ -227,7 +227,7 @@ export const id5IdSubmodule = { */ extendId(config, consentData, cacheIdObj) { if (!hasWriteConsentToLocalStorage(consentData)) { - logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.') + logInfo(LOG_PREFIX + 'No consent given for ID5 local storage writing, skipping nb increment.'); return cacheIdObj; } @@ -239,12 +239,12 @@ export const id5IdSubmodule = { }, eids: { 'id5id': { - getValue: function(data) { - return data.uid + getValue: function (data) { + return data.uid; }, source: ID5_DOMAIN, atype: 1, - getUidExt: function(data) { + getUidExt: function (data) { if (data.ext) { return data.ext; } @@ -264,16 +264,16 @@ export const id5IdSubmodule = { } } } - }, + } }; export class IdFetchFlow { constructor(submoduleConfig, gdprConsentData, cacheIdObj, usPrivacyData, gppData) { - this.submoduleConfig = submoduleConfig - this.gdprConsentData = gdprConsentData - this.cacheIdObj = cacheIdObj - this.usPrivacyData = usPrivacyData - this.gppData = gppData + this.submoduleConfig = submoduleConfig; + this.gdprConsentData = gdprConsentData; + this.cacheIdObj = cacheIdObj; + this.usPrivacyData = usPrivacyData; + this.gppData = gppData; } /** @@ -324,7 +324,11 @@ export class IdFetchFlow { let url = this.submoduleConfig.params.configUrl || ID5_API_CONFIG_URL; // override for debug/test purposes only const response = await fetch(url, { method: 'POST', - body: JSON.stringify(this.submoduleConfig) + body: JSON.stringify({ + ...this.submoduleConfig, + bounce: true + }), + credentials: 'include' }); if (!response.ok) { throw new Error('Error while calling config endpoint: ', response); @@ -342,7 +346,7 @@ export class IdFetchFlow { const extensionsUrl = extensionsCallConfig.url; const method = extensionsCallConfig.method || 'GET'; const body = method === 'GET' ? undefined : JSON.stringify(extensionsCallConfig.body || {}); - const response = await fetch(extensionsUrl, { method, body }); + const response = await fetch(extensionsUrl, {method, body}); if (!response.ok) { throw new Error('Error while calling extensions endpoint: ', response); } @@ -360,7 +364,7 @@ export class IdFetchFlow { ...additionalData, extensions: extensionsData }); - const response = await fetch(fetchUrl, { method: 'POST', body, credentials: 'include' }); + const response = await fetch(fetchUrl, {method: 'POST', body, credentials: 'include'}); if (!response.ok) { throw new Error('Error while calling fetch endpoint: ', response); } @@ -456,7 +460,7 @@ function validateConfig(config) { return false; } - const partner = config.params.partner + const partner = config.params.partner; if (typeof partner === 'string' || partner instanceof String) { let parsedPartnerId = parseInt(partner); if (isNaN(parsedPartnerId) || parsedPartnerId < 0) { @@ -566,8 +570,8 @@ export function storeInLocalStorage(key, value, expDays) { */ function hasWriteConsentToLocalStorage(consentData) { const hasGdpr = consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies; - const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`) - const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`) + const localstorageConsent = deepAccess(consentData, `vendorData.purpose.consents.1`); + const id5VendorConsent = deepAccess(consentData, `vendorData.vendor.consents.${GVLID.toString()}`); if (hasGdpr && (!localstorageConsent || !id5VendorConsent)) { return false; } diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index fd5d24295f5..1da862cc007 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -8,7 +8,7 @@ import { } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; import * as events from '../../../src/events.js'; -import { EVENTS } from '../../../src/constants.js'; +import {EVENTS} from '../../../src/constants.js'; import * as utils from '../../../src/utils.js'; import {uspDataHandler, gppDataHandler} from '../../../src/adapterManager.js'; import '../../../src/prebid.js'; @@ -81,11 +81,11 @@ describe('ID5 ID System', function () { 131: true } } - } + }; const HEADERS_CONTENT_TYPE_JSON = { 'Content-Type': 'application/json' - } + }; function getId5FetchConfig(partner = ID5_TEST_PARTNER_ID, storageName = id5System.ID5_STORAGE_NAME, storageType = 'html5') { return { @@ -98,7 +98,7 @@ describe('ID5 ID System', function () { type: storageType, expires: 90 } - } + }; } function getId5ValueConfig(value) { @@ -109,7 +109,7 @@ describe('ID5 ID System', function () { uid: value } } - } + }; } function getUserSyncConfig(userIds) { @@ -118,7 +118,7 @@ describe('ID5 ID System', function () { userIds: userIds, syncDelay: 0 } - } + }; } function getFetchLocalStorageConfig() { @@ -167,6 +167,7 @@ describe('ID5 ID System', function () { const configRequest = await this.expectFirstRequest(); expect(configRequest.url).is.eq(ID5_API_CONFIG_URL); expect(configRequest.method).is.eq('POST'); + expect(configRequest.withCredentials).is.eq(true); return configRequest; } @@ -184,7 +185,7 @@ describe('ID5 ID System', function () { } async #waitOnRequest(index) { - const server = this.server + const server = this.server; return new GreedyPromise((resolve) => { const waitForCondition = () => { if (server.requests && server.requests.length > index) { @@ -214,31 +215,37 @@ describe('ID5 ID System', function () { expect(id5System.id5IdSubmodule.getId({})).is.eq(undefined); // valid params, invalid id5System.storage - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: {} })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { name: '' } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ params: { partner: 123 }, storage: { type: '' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {name: ''}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({params: {partner: 123}, storage: {type: ''}})).to.be.eq(undefined); // valid id5System.storage, invalid params - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { } })).to.be.eq(undefined); - expect(id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 'abc' } })).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}, params: {}})).to.be.eq(undefined); + expect(id5System.id5IdSubmodule.getId({ + storage: {name: 'name', type: 'html5'}, + params: {partner: 'abc'} + })).to.be.eq(undefined); }); it('should warn with non-recommended id5System.storage params', function () { const logWarnStub = sinon.stub(utils, 'logWarn'); - id5System.id5IdSubmodule.getId({ storage: { name: 'name', type: 'html5', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({storage: {name: 'name', type: 'html5'}, params: {partner: 123}}); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); - id5System.id5IdSubmodule.getId({ storage: { name: id5System.ID5_STORAGE_NAME, type: 'cookie', }, params: { partner: 123 } }); + id5System.id5IdSubmodule.getId({ + storage: {name: id5System.ID5_STORAGE_NAME, type: 'cookie'}, + params: {partner: 123} + }); expect(logWarnStub.calledOnce).to.be.true; logWarnStub.restore(); }); }); - describe('Check for valid consent', function() { + describe('Check for valid consent', function () { const dataConsentVals = [ [{purpose: {consents: {1: false}}}, {vendor: {consents: {131: true}}}, ' no purpose consent'], [{purpose: {consents: {1: true}}}, {vendor: {consents: {131: false}}}, ' no vendor consent'], @@ -250,15 +257,15 @@ describe('ID5 ID System', function () { [{purpose: {consents: {1: true}}}, {vendor: {consents: {31: true}}}, ' incorrect vendor consent'] ]; - dataConsentVals.forEach(function([purposeConsent, vendorConsent, caseName]) { - it('should fail with invalid consent because of ' + caseName, function() { + dataConsentVals.forEach(function ([purposeConsent, vendorConsent, caseName]) { + it('should fail with invalid consent because of ' + caseName, function () { const dataConsent = { gdprApplies: true, consentString: 'consentString', vendorData: { purposeConsent, vendorConsent } - } + }; expect(id5System.id5IdSubmodule.getId(config)).is.eq(undefined); expect(id5System.id5IdSubmodule.getId(config, dataConsent)).is.eq(undefined); @@ -270,25 +277,25 @@ describe('ID5 ID System', function () { }); describe('Xhr Requests from getId()', function () { - const responseHeader = HEADERS_CONTENT_TYPE_JSON - let gppStub + const responseHeader = HEADERS_CONTENT_TYPE_JSON; + let gppStub; beforeEach(function () { }); afterEach(function () { - uspDataHandler.reset() - gppStub?.restore() + uspDataHandler.reset(); + gppStub?.restore(); }); it('should call the ID5 server and handle a valid response', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(config, undefined, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); expect(fetchRequest.url).to.contain(ID5_ENDPOINT); expect(fetchRequest.withCredentials).is.true; @@ -298,12 +305,12 @@ describe('ID5 ID System', function () { expect(requestBody.o).is.eq('pbjs'); expect(requestBody.pd).is.undefined; expect(requestBody.s).is.undefined; - expect(requestBody.provider).is.undefined + expect(requestBody.provider).is.undefined; expect(requestBody.v).is.eq('$prebid.version$'); expect(requestBody.gdpr).is.eq(0); expect(requestBody.gdpr_consent).is.undefined; expect(requestBody.us_privacy).is.undefined; - expect(requestBody.storage).is.deep.eq(config.storage) + expect(requestBody.storage).is.deep.eq(config.storage); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); @@ -312,17 +319,17 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with gdpr data ', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.gdpr).to.eq(1); @@ -335,19 +342,19 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without gdpr data when gdpr not applies ', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: false, consentString: 'consentString' - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.gdpr).to.eq(0); - expect(requestBody.gdpr_consent).is.undefined + expect(requestBody.gdpr_consent).is.undefined; fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); @@ -357,18 +364,18 @@ describe('ID5 ID System', function () { it('should call the ID5 server with us privacy consent', async function () { const usPrivacyString = '1YN-'; - uspDataHandler.setConsentData(usPrivacyString) - const xhrServerMock = new XhrServerMock(server) + uspDataHandler.setConsentData(usPrivacyString); + const xhrServerMock = new XhrServerMock(server); const consentData = { gdprApplies: true, consentString: 'consentString', vendorData: ALLOWED_ID5_VENDOR_DATA - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), consentData, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.us_privacy).to.eq(usPrivacyString); @@ -380,12 +387,12 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with no signature field when no stored object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.s).is.undefined; @@ -394,7 +401,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server for config with submodule config object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.extraParam = { x: 'X', @@ -402,22 +409,25 @@ describe('ID5 ID System', function () { a: 1, b: '3' } - } + }; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); const configRequest = await xhrServerMock.expectConfigRequest(); const requestBody = JSON.parse(configRequest.requestBody); - expect(requestBody).is.deep.eq(id5FetchConfig) + expect(requestBody).is.deep.eq({ + ...id5FetchConfig, + bounce: true + }); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server for config with partner id being a string', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); id5FetchConfig.params.partner = '173'; @@ -425,18 +435,18 @@ describe('ID5 ID System', function () { const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); const configRequest = await xhrServerMock.expectConfigRequest(); - const requestBody = JSON.parse(configRequest.requestBody) - expect(requestBody.params.partner).is.eq(173) + const requestBody = JSON.parse(configRequest.requestBody); + expect(requestBody.params.partner).is.eq(173); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server for config with overridden url', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5FetchConfig = getId5FetchConfig(); - id5FetchConfig.params.configUrl = 'http://localhost/x/y/z' + id5FetchConfig.params.configUrl = 'http://localhost/x/y/z'; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5FetchConfig, undefined, undefined); @@ -444,13 +454,13 @@ describe('ID5 ID System', function () { const configRequest = await xhrServerMock.expectFirstRequest(); expect(configRequest.url).is.eq('http://localhost/x/y/z'); - const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest) + const fetchRequest = await xhrServerMock.respondWithConfigAndExpectNext(configRequest); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with additional data when provided', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -472,18 +482,18 @@ describe('ID5 ID System', function () { expect(requestBody.partner).is.eq(ID5_TEST_PARTNER_ID); expect(requestBody.o).is.eq('pbjs'); expect(requestBody.v).is.eq('$prebid.version$'); - expect(requestBody.arg1).is.eq('123') + expect(requestBody.arg1).is.eq('123'); expect(requestBody.arg2).is.deep.eq({ x: '1', y: 2 - }) + }); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with extensions', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -498,8 +508,8 @@ describe('ID5 ID System', function () { method: 'GET' } }); - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('GET') + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT); + expect(extensionsRequest.method).is.eq('GET'); extensionsRequest.respond(200, responseHeader, JSON.stringify({ lb: 'ex' @@ -511,14 +521,14 @@ describe('ID5 ID System', function () { expect(requestBody.v).is.eq('$prebid.version$'); expect(requestBody.extensions).is.deep.eq({ lb: 'ex' - }) + }); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); await submoduleResponsePromise; }); it('should call the ID5 server with extensions fetched using method POST', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, undefined); @@ -537,15 +547,15 @@ describe('ID5 ID System', function () { } } }); - expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT) - expect(extensionsRequest.method).is.eq('POST') - const extRequestBody = JSON.parse(extensionsRequest.requestBody) + expect(extensionsRequest.url).is.eq(ID5_EXTENSIONS_ENDPOINT); + expect(extensionsRequest.method).is.eq('POST'); + const extRequestBody = JSON.parse(extensionsRequest.requestBody); expect(extRequestBody).is.deep.eq({ x: '1', y: 2 - }) + }); extensionsRequest.respond(200, responseHeader, JSON.stringify({ - lb: 'post', + lb: 'post' })); const fetchRequest = await xhrServerMock.expectNextRequest(); @@ -562,12 +572,12 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with signature field from stored object', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - const fetchRequest = await xhrServerMock.expectFetchRequest() + const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); @@ -576,7 +586,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with pd field when pd config is set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const pubData = 'b50ca08271795a8e7e4012813f23d505193d75c0f2e2bb99baa63aa822f66ed3'; const id5Config = getId5FetchConfig(); @@ -594,7 +604,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with no pd field when pd config is not set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); id5Config.params.pd = undefined; @@ -610,7 +620,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with nb=1 when no stored value exists and reset after', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const TEST_PARTNER_ID = 189; coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(TEST_PARTNER_ID)); @@ -647,9 +657,9 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server with ab_testing object when abTesting is turned on', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); - id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234} + id5Config.params.abTesting = {enabled: true, controlGroupPct: 0.234}; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); @@ -664,9 +674,9 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without ab_testing object when abTesting is turned off', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); - id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55} + id5Config.params.abTesting = {enabled: false, controlGroupPct: 0.55}; // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(id5Config, undefined, ID5_STORED_OBJ); @@ -680,7 +690,7 @@ describe('ID5 ID System', function () { }); it('should call the ID5 server without ab_testing when when abTesting is not set', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const id5Config = getId5FetchConfig(); // Trigger the fetch but we await on it later @@ -695,7 +705,7 @@ describe('ID5 ID System', function () { }); it('should store the privacy object from the ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -733,7 +743,7 @@ describe('ID5 ID System', function () { expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.null; }); - describe('with successful external module call', function() { + describe('with successful external module call', function () { const MOCK_RESPONSE = { ...ID5_JSON_RESPONSE, universal_uid: 'my_mock_reponse' @@ -743,7 +753,8 @@ describe('ID5 ID System', function () { beforeEach(() => { window.id5Prebid = { integration: { - fetchId5Id: function() {} + fetchId5Id: function () { + } } }; mockId5ExternalModule = sinon.stub(window.id5Prebid.integration, 'fetchId5Id') @@ -755,7 +766,7 @@ describe('ID5 ID System', function () { delete window.id5Prebid; }); - it('should retrieve the response from the external module interface', async function() { + it('should retrieve the response from the external module interface', async function () { const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); config.params.externalModuleUrl = 'https://test-me.test'; @@ -772,8 +783,8 @@ describe('ID5 ID System', function () { }); }); - describe('with failing external module loading', function() { - it('should fallback to regular logic if external module fails to load', async function() { + describe('with failing external module loading', function () { + it('should fallback to regular logic if external module fails to load', async function () { const xhrServerMock = new XhrServerMock(server); const config = getId5FetchConfig(); config.params.externalModuleUrl = 'https://test-me.test'; // Fails by loading this fake URL @@ -791,7 +802,7 @@ describe('ID5 ID System', function () { }); it('should pass gpp_string and gpp_sid to ID5 server', function () { - let xhrServerMock = new XhrServerMock(server) + let xhrServerMock = new XhrServerMock(server); gppStub = sinon.stub(gppDataHandler, 'getConsentData'); gppStub.returns({ ready: true, @@ -806,7 +817,7 @@ describe('ID5 ID System', function () { expect(requestBody.gpp_string).is.equal('GPP_STRING'); expect(requestBody.gpp_sid).contains(2); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - return submoduleResponse + return submoduleResponse; }); }); @@ -823,7 +834,7 @@ describe('ID5 ID System', function () { id5System.storage.getCookie.callsFake(() => ' Not JSON '); id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); - }) + }); }); describe('Local storage', () => { @@ -839,9 +850,9 @@ describe('ID5 ID System', function () { [true, 1], [false, 0] ].forEach(([isEnabled, expectedValue]) => { - it(`should check localStorage availability and log in request. Available=${isEnabled}`, async function() { - const xhrServerMock = new XhrServerMock(server) - id5System.storage.localStorageIsEnabled.callsFake(() => isEnabled) + it(`should check localStorage availability and log in request. Available=${isEnabled}`, async function () { + const xhrServerMock = new XhrServerMock(server); + id5System.storage.localStorageIsEnabled.callsFake(() => isEnabled); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -868,7 +879,7 @@ describe('ID5 ID System', function () { coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()) + coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()); adUnits = [getAdUnitMock()]; }); afterEach(function () { @@ -876,7 +887,7 @@ describe('ID5 ID System', function () { coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst') + coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst'); sandbox.restore(); }); @@ -930,7 +941,7 @@ describe('ID5 ID System', function () { provider: ID5_SOURCE } }] - }) + }); }); }); done(); @@ -967,7 +978,7 @@ describe('ID5 ID System', function () { requestBidsHook((adUnitConfig) => { expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); - done() + done(); }, {adUnits}); }); @@ -981,12 +992,12 @@ describe('ID5 ID System', function () { requestBidsHook(() => { expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - done() + done(); }, {adUnits}); }); it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { - const xhrServerMock = new XhrServerMock(server) + const xhrServerMock = new XhrServerMock(server); const initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); id5System.storeInLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`, id5System.expDaysStr(-1), 1); @@ -1000,7 +1011,7 @@ describe('ID5 ID System', function () { return new Promise((resolve) => { requestBidsHook(() => { - resolve() + resolve(); }, {adUnits}); }).then(() => { expect(xhrServerMock.hasReceivedAnyRequest()).is.false; @@ -1018,11 +1029,11 @@ describe('ID5 ID System', function () { if (id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); setTimeout(waitForCondition, 30); })(); - }) + }); }).then(() => { expect(decodeURIComponent(id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); - }) + }); }); }); From 2bb9f9759da25d53203b716f225adebf063ef09a Mon Sep 17 00:00:00 2001 From: Parth Shah Date: Tue, 7 May 2024 02:05:31 +0530 Subject: [PATCH 702/760] DeepIntent Id Module : fix user ids not being passed on page reload due to getValue func miss (#11383) * fix user ids not being passed on page reload due to extendId func miss * remove extendId and add function to read value for eids * handle inconsistent localstorage values in deepintent id * remove unused imports * revert to getValue changes * revert version in package-lock * update test suite for deepintent id system --- modules/deepintentDpesIdSystem.js | 9 ++- .../modules/deepintentDpesIdsystem_spec.js | 60 +++++++------------ 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/modules/deepintentDpesIdSystem.js b/modules/deepintentDpesIdSystem.js index 2d3eae980cd..a1f1e29a4ce 100644 --- a/modules/deepintentDpesIdSystem.js +++ b/modules/deepintentDpesIdSystem.js @@ -8,6 +8,7 @@ import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {isPlainObject} from '../src/utils.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -49,7 +50,13 @@ export const deepintentDpesSubmodule = { eids: { 'deepintentId': { source: 'deepintent.com', - atype: 3 + atype: 3, + getValue: (userIdData) => { + if (isPlainObject(userIdData) && userIdData?.id) { + return userIdData.id; + } + return userIdData; + } }, }, }; diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js index 4c26b118a98..3bd1e71d8f6 100644 --- a/test/spec/modules/deepintentDpesIdsystem_spec.js +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -1,12 +1,8 @@ import { expect } from 'chai'; -import {find} from 'src/polyfill.js'; -import { storage, deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { config } from 'src/config.js'; +import { deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; -const DI_COOKIE_NAME = '_dpes_id'; -const DI_COOKIE_STORED = '{"id":"2cf40748c4f7f60d343336e08f80dc99"}'; const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; +const DI_UPDATED_STORAGE = '2cf40748c4f7f60d343336e08f80dc99'; const cookieConfig = { name: 'deepintentId', @@ -27,50 +23,40 @@ const html5Config = { } describe('Deepintent DPES System', () => { - let getDataFromLocalStorageStub, localStorageIsEnabledStub; - let getCookieStub, cookiesAreEnabledStub; - - beforeEach(() => { - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); - getCookieStub = sinon.stub(storage, 'getCookie'); - cookiesAreEnabledStub = sinon.stub(storage, 'cookiesAreEnabled'); - }); - - afterEach(() => { - getDataFromLocalStorageStub.restore(); - localStorageIsEnabledStub.restore(); - getCookieStub.restore(); - cookiesAreEnabledStub.restore(); - }); - describe('Deepintent Dpes Sytsem: test "getId" method', () => { - it('Wrong config should fail the tests', () => { - // no config - expect(deepintentDpesSubmodule.getId()).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({ })).to.be.eq(undefined); - - expect(deepintentDpesSubmodule.getId({params: {}, storage: {}})).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({params: {}, storage: {type: 'cookie'}})).to.be.eq(undefined); - expect(deepintentDpesSubmodule.getId({params: {}, storage: {name: '_dpes_id'}})).to.be.eq(undefined); + it('If nothing is found in cache, return undefined', () => { + let diId = deepintentDpesSubmodule.getId({}, undefined, undefined); + expect(diId).to.be.eq(undefined); }); it('Get value stored in cookie for getId', () => { - getCookieStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); let diId = deepintentDpesSubmodule.getId(cookieConfig, undefined, DI_COOKIE_OBJECT); expect(diId).to.deep.equal(DI_COOKIE_OBJECT); }); it('provides the stored deepintentId if cookie is absent but present in local storage', () => { - getDataFromLocalStorageStub.withArgs(DI_COOKIE_NAME).returns(DI_COOKIE_STORED); - let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_COOKIE_OBJECT); - expect(idx).to.deep.equal(DI_COOKIE_OBJECT); + let idx = deepintentDpesSubmodule.getId(html5Config, undefined, DI_UPDATED_STORAGE); + expect(idx).to.be.eq(DI_UPDATED_STORAGE); }); }); describe('Deepintent Dpes System : test "decode" method', () => { - it('Get the correct decoded value for dpes id', () => { - expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': {'id': '2cf40748c4f7f60d343336e08f80dc99'}}); + it('Get the correct decoded value for dpes id, if an object is set return object', () => { + expect(deepintentDpesSubmodule.decode(DI_COOKIE_OBJECT, cookieConfig)).to.deep.equal({'deepintentId': DI_COOKIE_OBJECT}); + }); + + it('Get the correct decoded value for dpes id, if a string is set return string', () => { + expect(deepintentDpesSubmodule.decode(DI_UPDATED_STORAGE, {})).to.deep.equal({'deepintentId': DI_UPDATED_STORAGE}); + }); + }); + + describe('Deepintent Dpes System : test "getValue" method in eids', () => { + it('Get the correct string value for dpes id, if an object is set in local storage', () => { + expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_COOKIE_OBJECT)).to.be.equal(DI_UPDATED_STORAGE); + }); + + it('Get the correct string value for dpes id, if a string is set in local storage', () => { + expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_UPDATED_STORAGE)).to.be.eq(DI_UPDATED_STORAGE); }); }); }); From 55d008a5934e035f3cb3adb36ef0376e4483264f Mon Sep 17 00:00:00 2001 From: ecoeco163 <147788250+ecoeco163@users.noreply.github.com> Date: Tue, 7 May 2024 04:58:58 +0800 Subject: [PATCH 703/760] Discovery Bid Adapter : get UTM tag data (#11380) * feat(isBidRequestValid): just filter token once. not filter publisher and tagid * feat(isBidRequestValid): add unit test * feat(spec): fix eslint * feat(spec): fix unit test * feat(spec): fix unit test * feat(ext): change default value * feat(utm): build UTMTag data * feat(spec): fix utm test * feat(utm): get UTMTag * feat(utm): add unit tests * feat(utm): fix unit tests * feat(utm): fix unit tests * feat(utm): fix unit tests --------- Co-authored-by: yubei01 --- modules/discoveryBidAdapter.js | 3 +- test/spec/modules/discoveryBidAdapter_spec.js | 53 +++++++------------ 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 7493dcb9af4..3ac905ef6b5 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -414,7 +414,7 @@ function getItems(validBidRequests, bidderRequest) { export const buildUTMTagData = (url) => { if (!storage.cookiesAreEnabled()) return; - const urlParams = utils.parseUrl(url).search; + const urlParams = utils.parseUrl(url).search || {}; const UTMParams = {}; Object.keys(urlParams).forEach(key => { if (/^utm_/.test(key)) { @@ -486,6 +486,7 @@ function getParam(validBidRequests, bidderRequest) { ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, pmguid: getPmgUID(), tpData, + utm: storage.getCookie(UTM_KEY), page: { title: title ? title.slice(0, 100) : undefined, desc: desc ? desc.slice(0, 300) : undefined, diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index d148d5062a4..42cc6ff68eb 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -15,6 +15,25 @@ import { import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(utils, 'generateUUID').returns('new-uuid'); + sandbox.stub(utils, 'parseUrl').returns({ + search: { + utm_source: 'example.com' + } + }); + sandbox.stub(storage, 'cookiesAreEnabled'); + }) + + afterEach(() => { + sandbox.restore(); + }); + let bidRequestData = { bidderCode: 'discovery', auctionId: 'ff66e39e-4075-4d18-9854-56fde9b879ac', @@ -200,8 +219,8 @@ describe('discovery:BidAdapterTests', function () { }) ).to.equal(true); }); - it('discovery:validate_generated_params', function () { + storage.getCookie.withArgs('_ss_pp_utm').callsFake(() => '{"utm_source":"example.com","utm_medium":"123","utm_campaign":"456"}'); request = spec.buildRequests(bidRequestData.bids, bidRequestData); let req_data = JSON.parse(request.data); expect(req_data.imp).to.have.lengthOf(1); @@ -216,20 +235,6 @@ describe('discovery:BidAdapterTests', function () { describe('discovery: buildRequests', function() { describe('getPmgUID function', function() { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getCookie'); - sandbox.stub(storage, 'setCookie'); - sandbox.stub(utils, 'generateUUID').returns('new-uuid'); - sandbox.stub(storage, 'cookiesAreEnabled'); - }) - - afterEach(() => { - sandbox.restore(); - }); - it('should generate new UUID and set cookie if not exists', () => { storage.cookiesAreEnabled.callsFake(() => true); storage.getCookie.callsFake(() => null); @@ -254,24 +259,6 @@ describe('discovery:BidAdapterTests', function () { }); }) describe('buildUTMTagData function', function() { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getCookie'); - sandbox.stub(storage, 'setCookie'); - sandbox.stub(utils, 'parseUrl').returns({ - search: { - utm_source: 'example.com' - } - }); - sandbox.stub(storage, 'cookiesAreEnabled'); - }) - - afterEach(() => { - sandbox.restore(); - }); - it('should set UTM cookie', () => { storage.cookiesAreEnabled.callsFake(() => true); storage.getCookie.callsFake(() => null); From 3ccef438e439c962fa68675c7cace9101c6cf350 Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 6 May 2024 17:08:21 -0400 Subject: [PATCH 704/760] README: note about build-bundle-dev (#11448) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 70e058d122b..f890f055104 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,16 @@ Or, if you are consuming Prebid through npm, with the `disableFeatures` option i **Note**: this is still a work in progress - at the moment, `NATIVE` is the only feature that can be disabled this way, resulting in a minimal decrease in size (but you can expect that to improve over time). +## Unminified code + +You can get a version of the code that's unminified for debugging with `build-bundle-dev`: + +```bash +gulp build-bundle-dev --modules=bidderA,module1,... +``` + +The results will be in build/dev/prebid.js. + ## Test locally To lint the code: From b6c25e18be3e70bc3e50eb47c3367483848b9f8a Mon Sep 17 00:00:00 2001 From: joseluis laso Date: Mon, 6 May 2024 14:41:16 -0700 Subject: [PATCH 705/760] adding the domain when calling home (#11440) --- modules/hadronIdSystem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index 66cb5624a38..bdb8e634de6 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -115,7 +115,7 @@ export const hadronIdSubmodule = { // config.params.url and config.params.urlArg are not documented // since their use is for debugging purposes only paramOrDefault(config.params.url, DEFAULT_HADRON_URL_ENDPOINT, config.params.urlArg), - `partner_id=${partnerId}&_it=prebid&t=1&src=id` // src=id => the backend was called from getId + `partner_id=${partnerId}&_it=prebid&t=1&src=id&domain=${document.location.hostname}` // src=id => the backend was called from getId ); if (isDebug) { url += '&debug=1' From 93533276030e4748503a20b2028845a8d5fbf6fb Mon Sep 17 00:00:00 2001 From: bretg Date: Mon, 6 May 2024 18:44:14 -0400 Subject: [PATCH 706/760] Update PULL_REQUEST_TEMPLATE.md (#11449) Updating the notes: - new adapters are pointed to the docs rather than the repo - updated client-side adapters are asked to consider whether PBS adapter update is needed --- .github/PULL_REQUEST_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 09ef5c445f2..b225be162a8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,8 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi - [ ] Bugfix - [ ] Feature -- [ ] New bidder adapter +- [ ] New bidder adapter +- [ ] Updated bidder adapter - [ ] Code style update (formatting, local variables) - [ ] Refactoring (no functional changes, no api changes) - [ ] Build related changes From 14d2a0e844ed9f75ea8478011c0e3e886ea5eb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Millet?= Date: Tue, 7 May 2024 16:39:23 +0200 Subject: [PATCH 707/760] Dailymotion Bid Adapter: accept ortb2 field (#11366) * Dailymotion Bid Adaptor: initial release * .md file lint issue resolved * Dailymotion Bid Adaptor: build bidder request based on param with fallbacks * Dailymotion Bid Adaptor: support video metadata * Dailymotion Bid Adaptor: add support for sending adUnitCode * Dailymotion Bid Adaptor: add support for sending startDelay * feat(LEO-528): Allow multiple IAB categories in video metadata The same way as we can have an array of IAB categories level 1 in the ORTB request, this PR introduces an array for the IAB categories level 2. To be forward compatible with level [2.2](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.2.tsv) and [3.0](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%203.0.tsv) specifications, the category IDs should be sent as strings. * Dailymotion bid adapter: Clarify the video metadata to provide in each player context * Dailymotion bid adapter: Move API key to bid params * Dailymotion bid adapter: Verify API key is string Co-authored-by: Rumesh * Dailymotion bid adapter: Move API key to bid params (fix tests) * Dailymotion Bid Adaptor: add gpp support and get coppa from request * Dailymotion Bid Adaptor: fix lint error * Dailymotion Bid Adaptor: add iabcat1 and fallback to ortb2 for iabcat2 * Dailymotion Bid Adaptor: get iabcats from ortb2.site.content.data * Dailymotion Bid Adaptor: get content data from ortb2.site.content * Dailymotion Bid Adaptor: add support for iabcat1 in videoParams and document mapping of ortb2 fpd to video metadata * Dailymotion Bid Adaptor: add support for Object: App * Dailymotion Bid Adaptor: only support video adunits in context instream * Dailymotion bid adapter: Add standard ORTB video parameters Note: I changed the case of `startdelay` to be consistent with the other parameters, but it won't have any impact on current deployments as this parameter is not used onsite. * Dailymotion Bid Adaptor: add support for livestream and app * Dailymotion Bid Adaptor: drop support for non standard fields in mediaTypes.video * Dailymotion Bid Adaptor: update docs examples --------- Co-authored-by: Kevin Siow Co-authored-by: Aditi Chaudhary Co-authored-by: Kevin Siow Co-authored-by: Rumesh --- modules/dailymotionBidAdapter.js | 105 +++++--- modules/dailymotionBidAdapter.md | 83 ++++-- .../modules/dailymotionBidAdapter_spec.js | 255 +++++++++++++++--- 3 files changed, 354 insertions(+), 89 deletions(-) diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index 2be5edad78e..afded538fd0 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -9,27 +9,33 @@ import { deepAccess } from '../src/utils.js'; * @return video metadata */ function getVideoMetadata(bidRequest, bidderRequest) { - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video', {}); - const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); + const videoParams = deepAccess(bidRequest, 'params.video', {}); - const videoParams = { - ...videoAdUnit, - ...videoBidderParams, // Bidder Specific overrides - }; + // As per oRTB 2.5 spec, "A bid request must not contain both an App and a Site object." + // See section 3.2.14 + // Content object is either from Object: Site or Object: App + const contentObj = deepAccess(bidderRequest, 'ortb2.site') + ? deepAccess(bidderRequest, 'ortb2.site.content') + : deepAccess(bidderRequest, 'ortb2.app.content'); - // Store as object keys to ensure uniqueness - const iabcat1 = {}; - const iabcat2 = {}; + const parsedContentData = { + // Store as object keys to ensure uniqueness + iabcat1: {}, + iabcat2: {}, + }; - deepAccess(bidderRequest, 'ortb2.site.content.data', []).forEach((data) => { + deepAccess(contentObj, 'data', []).forEach((data) => { if ([4, 5, 6, 7].includes(data?.ext?.segtax)) { (Array.isArray(data.segment) ? data.segment : []).forEach((segment) => { if (typeof segment.id === 'string') { // See https://docs.prebid.org/features/firstPartyData.html#segments-and-taxonomy // Only take IAB cats of taxonomy V1 - if (data.ext.segtax === 4) iabcat1[segment.id] = 1; - // Only take IAB cats of taxonomy V2 or higher - if ([5, 6, 7].includes(data.ext.segtax)) iabcat2[segment.id] = 1; + if (data.ext.segtax === 4) { + parsedContentData.iabcat1[segment.id] = 1; + } else { + // Only take IAB cats of taxonomy V2 or higher + parsedContentData.iabcat2[segment.id] = 1; + } } }); } @@ -37,18 +43,25 @@ function getVideoMetadata(bidRequest, bidderRequest) { const videoMetadata = { description: videoParams.description || '', - duration: videoParams.duration || 0, - iabcat1: Object.keys(iabcat1), + duration: videoParams.duration || deepAccess(contentObj, 'len', 0), + iabcat1: Array.isArray(videoParams.iabcat1) + ? videoParams.iabcat1 + : Array.isArray(deepAccess(contentObj, 'cat')) + ? contentObj.cat + : Object.keys(parsedContentData.iabcat1), iabcat2: Array.isArray(videoParams.iabcat2) ? videoParams.iabcat2 - : Object.keys(iabcat2), - id: videoParams.id || '', - lang: videoParams.lang || '', + : Object.keys(parsedContentData.iabcat2), + id: videoParams.id || deepAccess(contentObj, 'id', ''), + lang: videoParams.lang || deepAccess(contentObj, 'language', ''), private: videoParams.private || false, - tags: videoParams.tags || '', - title: videoParams.title || '', + tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''), + title: videoParams.title || deepAccess(contentObj, 'title', ''), topics: videoParams.topics || '', xid: videoParams.xid || '', + livestream: typeof videoParams.livestream === 'number' + ? !!videoParams.livestream + : !!deepAccess(contentObj, 'livestream', 0), }; return videoMetadata; @@ -67,7 +80,24 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return typeof bid?.params?.apiKey === 'string' && bid.params.apiKey.length > 10; + if (bid?.params) { + // We only accept video adUnits + if (!bid?.mediaTypes?.[VIDEO]) return false; + + // As `context`, `placement` & `plcmt` are optional (although recommended) + // values, we check the 3 of them to see if we are in an instream video context + const isInstream = bid.mediaTypes[VIDEO].context === 'instream' || + bid.mediaTypes[VIDEO].placement === 1 || + bid.mediaTypes[VIDEO].plcmt === 1; + + // We only accept instream video context + if (!isInstream) return false; + + // We need API key + return typeof bid.params.apiKey === 'string' && bid.params.apiKey.length > 10; + } + + return false; }, /** @@ -83,15 +113,15 @@ export const spec = { data: { bidder_request: { gdprConsent: { - apiVersion: bidderRequest?.gdprConsent?.apiVersion || 1, - consentString: bidderRequest?.gdprConsent?.consentString || '', + apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), + consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), // Cast boolean in any case (eg: if value is int) to ensure type - gdprApplies: !!bidderRequest?.gdprConsent?.gdprApplies, + gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), }, refererInfo: { - page: bidderRequest?.refererInfo?.page || '', + page: deepAccess(bidderRequest, 'refererInfo.page', ''), }, - uspConsent: bidderRequest?.uspConsent || '', + uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), gppConsent: { gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), @@ -104,15 +134,28 @@ export const spec = { }, // Cast boolean in any case (value should be 0 or 1) to ensure type coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), + // In app context, we need to retrieve additional informations + ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { + appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), + appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), + } : {}), request: { - adUnitCode: bid.adUnitCode || '', - auctionId: bid.auctionId || '', - bidId: bid.bidId || '', + adUnitCode: deepAccess(bid, 'adUnitCode', ''), + auctionId: deepAccess(bid, 'auctionId', ''), + bidId: deepAccess(bid, 'bidId', ''), mediaTypes: { video: { - playerSize: bid.mediaTypes?.[VIDEO]?.playerSize || [], api: bid.mediaTypes?.[VIDEO]?.api || [], - startDelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, + mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], + minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, + maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], + skip: bid.mediaTypes?.[VIDEO]?.skip || 0, + skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, + skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, + startdelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, + w: bid.mediaTypes?.[VIDEO]?.w || 0, + h: bid.mediaTypes?.[VIDEO]?.h || 0, }, }, sizes: bid.sizes || [], diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 795273c9229..7c871b0d536 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -9,10 +9,11 @@ Maintainer: ad-leo-engineering@dailymotion.com # Description Dailymotion prebid adapter. +Supports video ad units in instream context. # Configuration options -Before calling this adapter, you need to set at least the API key in the bid parameters: +Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: ```javascript const adUnits = [ @@ -21,8 +22,14 @@ const adUnits = [ bidder: 'dailymotion', params: { apiKey: 'fake_api_key' - } - }] + }, + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + context: 'instream', + }, + }, } ]; ``` @@ -39,9 +46,15 @@ const adUnits = [ bids: [{ bidder: 'dailymotion', params: { - apiKey: 'dailymotion-testing' - } - }] + apiKey: 'dailymotion-testing', + }, + }], + code: 'test-ad-unit', + mediaTypes: { + video: { + context: 'instream', + }, + }, } ]; ``` @@ -51,7 +64,7 @@ Please note that failing to set these will result in the adapter not bidding at # Sample video AdUnit To allow better targeting, you should provide as much context about the video as possible. -There are two ways of doing this depending on if you're using Dailymotion player or a third party one. +There are three ways of doing this depending on if you're using Dailymotion player or a third party one. If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example: @@ -61,7 +74,10 @@ const adUnits = [ bids: [{ bidder: 'dailymotion', params: { - apiKey: 'dailymotion-testing' + apiKey: 'dailymotion-testing', + video: { + xid: 'x123456' // Dailymotion infrastructure unique video ID + }, } }], code: 'test-ad-unit', @@ -69,9 +85,9 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', - playerSize: [ [1280, 720] ], - startDelay: 0, - xid: 'x123456' // Dailymotion infrastructure unique video ID + startdelay: 0, + w: 1280, + h: 720, }, } } @@ -91,7 +107,17 @@ const adUnits = [ params: { apiKey: 'dailymotion-testing', video: { - description: 'overriden video description' + description: 'this is a video description', + duration: 556, + iabcat1: ['IAB-2'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + livestream: 0, + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', } } }], @@ -100,37 +126,42 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', - description: 'this is a video description', - duration: 556, - iabcat2: ['6', '17'], - id: '54321', - lang: 'FR', - playerSize: [ [1280, 720] ], - private: false, - startDelay: 0, - tags: 'tag_1,tag_2,tag_3', - title: 'test video', - topics: 'topic_1, topic_2', + startdelay: 0, + w: 1280, + h: 720, }, } } ]; ``` -Each of the following video metadata fields can be added in mediaTypes.video or bids.params.video. -If a field exists in both places, it will be overridden by bids.params.video. +Each of the following video metadata fields can be added in bids.params.video. * `description` - Video description * `duration` - Video duration in seconds -* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) +* `iabcat1` - List of IAB category IDs from the [1.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%201.0.tsv) +* `iabcat2` - List of IAB category IDs from the [2.0 taxonomy](https://github.com/InteractiveAdvertisingBureau/Taxonomies/blob/main/Content%20Taxonomies/Content%20Taxonomy%202.0.tsv) and above * `id` - Video unique ID in host video infrastructure * `lang` - ISO 639-1 code for main language used in the video +* `livestream` - 0 = not live, 1 = content is live * `private` - True if video is not publicly available * `tags` - Tags for the video, comma separated * `title` - Video title * `topics` - Main topics for the video, comma separated * `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) +If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below. + +| From ortb2 | Metadata fields | +|---------------------------------------------------------------------------------|-----------------| +| `ortb2.site.content.cat` OR `ortb2.site.content.data` where `ext.segtax` is `4` | `iabcat1` | +| `ortb2.site.content.data` where `ext.segtax` is `5`, `6` or `7` | `iabcat2` | +| `ortb2.site.content.id` | `id` | +| `ortb2.site.content.language` | `lang` | +| `ortb2.site.content.livestream` | `livestream` | +| `ortb2.site.content.keywords` | `tags` | +| `ortb2.site.content.title` | `title` | + # Integrating the adapter To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index a102c26dca2..fe9484b2814 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -1,7 +1,7 @@ import { config } from 'src/config.js'; import { expect } from 'chai'; import { spec } from 'modules/dailymotionBidAdapter.js'; -import { VIDEO } from '../../../src/mediaTypes'; +import { BANNER, VIDEO } from '../../../src/mediaTypes'; describe('dailymotionBidAdapterTests', () => { // Validate that isBidRequestValid only validates requests with apiKey @@ -12,6 +12,11 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: '', }, + mediaTypes: { + [VIDEO]: { + context: 'instream', + }, + }, }; expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyApi))).to.be.false; @@ -20,9 +25,57 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: 'test_api_key', }, + mediaTypes: { + [VIDEO]: { + context: 'instream', + }, + }, }; expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithApi))).to.be.true; + + const bidWithEmptyMediaTypes = { + params: { + apiKey: '', + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyMediaTypes))).to.be.false; + + const bidWithEmptyVideoAdUnit = { + params: { + apiKey: '', + }, + mediaTypes: { + [VIDEO]: {}, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithEmptyVideoAdUnit))).to.be.false; + + const bidWithBannerMediaType = { + params: { + apiKey: 'test_api_key', + }, + mediaTypes: { + [BANNER]: {}, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithBannerMediaType))).to.be.false; + + const bidWithOutstreamContext = { + params: { + apiKey: 'test_api_key', + }, + mediaTypes: { + video: { + context: 'outstream', + }, + }, + }; + + expect(config.runWithBidder('dailymotion', () => spec.isBidRequestValid(bidWithOutstreamContext))).to.be.false; }); // Validate request generation @@ -33,20 +86,27 @@ describe('dailymotionBidAdapterTests', () => { adUnitCode: 'preroll', mediaTypes: { video: { - playerSize: [[1280, 720]], api: [2, 7], - description: 'this is a test video', - duration: 300, - iabcat2: ['6', '17'], - lang: 'ENG', + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, startdelay: 0, + w: 1280, + h: 720 }, }, sizes: [[1920, 1080]], params: { apiKey: 'test_api_key', video: { + description: 'this is a test video', duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], id: '54321', lang: 'FR', private: false, @@ -54,6 +114,7 @@ describe('dailymotionBidAdapterTests', () => { title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', + livestream: 1, }, }, }]; @@ -78,6 +139,117 @@ describe('dailymotionBidAdapterTests', () => { }, site: { content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + const { data: reqData } = request; + + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.bidder_request).to.eql({ + refererInfo: bidderRequestData.refererInfo, + uspConsent: bidderRequestData.uspConsent, + gdprConsent: bidderRequestData.gdprConsent, + gppConsent: bidderRequestData.gppConsent, + }); + expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); + expect(reqData.coppa).to.be.true; + expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); + expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); + expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); + expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.video_metadata).to.eql({ + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-1'], + iabcat2: bidRequestData[0].params.video.iabcat2, + id: bidRequestData[0].params.video.id, + lang: bidRequestData[0].params.video.lang, + private: bidRequestData[0].params.video.private, + tags: bidRequestData[0].params.video.tags, + title: bidRequestData[0].params.video.title, + topics: bidRequestData[0].params.video.topics, + xid: bidRequestData[0].params.video.xid, + duration: bidRequestData[0].params.video.duration, + livestream: !!bidRequestData[0].params.video.livestream, + }); + }); + + it('validates buildRequests with content values from App', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + app: { + bundle: 'app-bundle', + storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', + content: { + len: 556, data: [ { name: 'dataprovider.com', @@ -112,15 +284,25 @@ describe('dailymotionBidAdapterTests', () => { }); expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); expect(reqData.coppa).to.be.true; + expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); + expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.mimes).to.eql(bidRequestData[0].mediaTypes.video.mimes); + expect(reqData.request.mediaTypes.video.minduration).to.eql(bidRequestData[0].mediaTypes.video.minduration); + expect(reqData.request.mediaTypes.video.maxduration).to.eql(bidRequestData[0].mediaTypes.video.maxduration); + expect(reqData.request.mediaTypes.video.protocols).to.eql(bidRequestData[0].mediaTypes.video.protocols); + expect(reqData.request.mediaTypes.video.skip).to.eql(bidRequestData[0].mediaTypes.video.skip); + expect(reqData.request.mediaTypes.video.skipafter).to.eql(bidRequestData[0].mediaTypes.video.skipafter); + expect(reqData.request.mediaTypes.video.skipmin).to.eql(bidRequestData[0].mediaTypes.video.skipmin); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.w).to.eql(bidRequestData[0].mediaTypes.video.w); + expect(reqData.request.mediaTypes.video.h).to.eql(bidRequestData[0].mediaTypes.video.h); expect(reqData.video_metadata).to.eql({ - description: bidRequestData[0].mediaTypes.video.description, - iabcat1: ['IAB-1'], // Taxonomy v2 or higher is excluded - iabcat2: bidRequestData[0].mediaTypes.video.iabcat2, + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-1'], + iabcat2: bidRequestData[0].params.video.iabcat2, id: bidRequestData[0].params.video.id, lang: bidRequestData[0].params.video.lang, private: bidRequestData[0].params.video.private, @@ -129,22 +311,19 @@ describe('dailymotionBidAdapterTests', () => { topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, // Overriden through bidder params - duration: bidRequestData[0].params.video.duration, + duration: bidderRequestData.ortb2.app.content.len, + livestream: !!bidRequestData[0].params.video.livestream, }); }); - it('validates buildRequests with fallback values on ortb2 for gpp & iabcat', () => { + it('validates buildRequests with fallback values on ortb2 (gpp, iabcat2, id...)', () => { const bidRequestData = [{ auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', bidId: 123456, adUnitCode: 'preroll', mediaTypes: { video: { - playerSize: [[1280, 720]], api: [2, 7], - description: 'this is a test video', - duration: 300, - lang: 'ENG', startdelay: 0, }, }, @@ -152,11 +331,9 @@ describe('dailymotionBidAdapterTests', () => { params: { apiKey: 'test_api_key', video: { + description: 'this is a test video', duration: 556, - id: '54321', - lang: 'FR', private: false, - tags: 'tag_1,tag_2,tag_3', title: 'test video', topics: 'topic_1, topic_2', xid: 'x123456', @@ -182,6 +359,12 @@ describe('dailymotionBidAdapterTests', () => { }, site: { content: { + id: '54321', + language: 'FR', + keywords: 'tag_1,tag_2,tag_3', + title: 'test video', + livestream: 1, + cat: ['IAB-2'], data: [ undefined, // Undefined to check proper handling of edge cases {}, // Empty object to check proper handling of edge cases @@ -203,7 +386,7 @@ describe('dailymotionBidAdapterTests', () => { }, { name: 'dataprovider.com', - ext: { segtax: 4 }, + ext: { segtax: 5 }, segment: [{ id: 2222 }], // Invalid segment id to check proper handling of edge cases }, { @@ -251,21 +434,20 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startDelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); expect(reqData.video_metadata).to.eql({ - description: bidRequestData[0].mediaTypes.video.description, - // No iabcat1 here because nothing matches taxonomy - iabcat1: [], + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-2'], iabcat2: ['6', '17', '20'], - id: bidRequestData[0].params.video.id, - lang: bidRequestData[0].params.video.lang, + id: bidderRequestData.ortb2.site.content.id, + lang: bidderRequestData.ortb2.site.content.language, private: bidRequestData[0].params.video.private, - tags: bidRequestData[0].params.video.tags, - title: bidRequestData[0].params.video.title, + tags: bidderRequestData.ortb2.site.content.keywords, + title: bidderRequestData.ortb2.site.content.title, topics: bidRequestData[0].params.video.topics, xid: bidRequestData[0].params.video.xid, - // Overriden through bidder params duration: bidRequestData[0].params.video.duration, + livestream: !!bidderRequestData.ortb2.site.content.livestream, }); }); @@ -310,9 +492,17 @@ describe('dailymotionBidAdapterTests', () => { adUnitCode: '', mediaTypes: { video: { - playerSize: [], - startDelay: 0, api: [], + mimes: [], + minduration: 0, + maxduration: 0, + protocols: [], + skip: 0, + skipafter: 0, + skipmin: 0, + startdelay: 0, + w: 0, + h: 0, }, }, sizes: [], @@ -330,6 +520,7 @@ describe('dailymotionBidAdapterTests', () => { title: '', topics: '', xid: '', + livestream: false, }); }); From 685d72c50472b3e99dbe33c809b3d14fa8ec97ee Mon Sep 17 00:00:00 2001 From: Irakli Gotsiridze Date: Tue, 7 May 2024 18:41:12 +0400 Subject: [PATCH 708/760] enhance fledge (#11455) --- modules/sovrnBidAdapter.js | 38 ++++++- test/spec/modules/sovrnBidAdapter_spec.js | 132 +++++++++++++++++----- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 3df025b1619..b6563cac4c5 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -6,7 +6,7 @@ import { logError, deepAccess, isInteger, - logWarn, getBidIdParameter + logWarn, getBidIdParameter, isEmptyStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { @@ -254,16 +254,38 @@ export const spec = { })) .flat() - let fledgeAuctionConfigs = deepAccess(ext, 'fledge_auction_configs'); + let fledgeAuctionConfigs = null; + if (isArray(ext?.igbid)) { + const seller = ext.seller + const decisionLogicUrl = ext.decisionLogicUrl + const sellerTimeout = ext.sellerTimeout + ext.igbid.filter(item => isValidIgBid(item)).forEach((igbid) => { + const perBuyerSignals = {} + igbid.igbuyer.filter(item => isValidIgBuyer(item)).forEach(buyerItem => { + perBuyerSignals[buyerItem.igdomain] = buyerItem.buyerdata + }) + const interestGroupBuyers = [...Object.keys(perBuyerSignals)] + if (interestGroupBuyers.length) { + fledgeAuctionConfigs = fledgeAuctionConfigs || {} + fledgeAuctionConfigs[igbid.impid] = { + seller, + decisionLogicUrl, + sellerTimeout, + interestGroupBuyers: interestGroupBuyers, + perBuyerSignals, + } + } + }) + } if (fledgeAuctionConfigs) { fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { return { bidId, config: Object.assign({ - auctionSignals: {}, + auctionSignals: {} }, cfg) } - }); + }) return { bids, fledgeAuctionConfigs, @@ -367,4 +389,12 @@ function _getBidFloors(bid) { return !isNaN(paramValue) ? paramValue : undefined } +function isValidIgBid(igBid) { + return !isEmptyStr(igBid.impid) && isArray(igBid.igbuyer) && igBid.igbuyer.length +} + +function isValidIgBuyer(igBuyer) { + return !isEmptyStr(igBuyer.igdomain) +} + registerBidder(spec) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 9c7f433ef96..10f5ab8e89d 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -844,26 +844,56 @@ describe('sovrnBidAdapter', function() { }] }], ext: { - fledge_auction_configs: { - 'test_bid_id': { - seller: 'ap.lijit.com', - interestGroupBuyers: ['dsp1.com'], - sellerTimeout: 0, - perBuyerSignals: { - 'dsp1.com': { - bid_macros: 0.1, - disallowed_adv_ids: [ - '8765', - '4321' - ], - } + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + igbid: [{ + impid: 'test_imp_id', + igbuyer: [{ + igdomain: 'ap.lijit.com', + buyerdata: { + base_bid_micros: 0.1, + use_bid_multiplier: true, + multiplier: '1.3' } - } - } + }, { + igdomain: 'buyer2.com', + buyerdata: {} + }, { + igdomain: 'buyer3.com', + buyerdata: {} + }] + }, { + impid: 'test_imp_id_2', + igbuyer: [{ + igdomain: 'ap2.lijit.com', + buyerdata: { + base_bid_micros: '0.2', + } + }] + }, { + impid: '', + igbuyer: [{ + igdomain: 'ap3.lijit.com', + buyerdata: { + base_bid_micros: '0.3', + } + }] + }, { + impid: 'test_imp_id_3', + igbuyer: [{ + igdomain: '', + buyerdata: { + base_bid_micros: '0.3', + } + }] + }, { + impid: 'test_imp_id_4', + igbuyer: [] + }] } } } - let invalidFledgeResponse = { + let emptyFledgeResponse = { body: { id: '37386aade21a71', seatbid: [{ @@ -879,25 +909,73 @@ describe('sovrnBidAdapter', function() { }] }], ext: { - fledge_auction_configs: { + igbid: { } } } } - it('should return fledge auction configs alongside bids', function () { + let expectedResponse = { + requestId: '263c448586f5a1', + cpm: 0.45882675, + width: 728, + height: 90, + creativeId: 'creativelycreatedcreativecreative', + dealId: null, + currency: 'USD', + netRevenue: true, + mediaType: 'banner', + ttl: 60000, + meta: { advertiserDomains: [] }, + ad: decodeURIComponent(`>`) + } + let expectedFledgeResponse = [ + { + bidId: 'test_imp_id', + config: { + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + sellerTimeout: undefined, + auctionSignals: {}, + interestGroupBuyers: ['ap.lijit.com', 'buyer2.com', 'buyer3.com'], + perBuyerSignals: { + 'ap.lijit.com': { + base_bid_micros: 0.1, + use_bid_multiplier: true, + multiplier: '1.3' + }, + 'buyer2.com': {}, + 'buyer3.com': {} + } + } + }, + { + bidId: 'test_imp_id_2', + config: { + seller: 'seller.lijit.com', + decisionLogicUrl: 'https://decision.lijit.com', + sellerTimeout: undefined, + auctionSignals: {}, + interestGroupBuyers: ['ap2.lijit.com'], + perBuyerSignals: { + 'ap2.lijit.com': { + base_bid_micros: '0.2', + } + } + } + } + ] + + it('should return valid fledge auction configs alongside bids', function () { const result = spec.interpretResponse(fledgeResponse) expect(result).to.have.property('bids') expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(1) - expect(result.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id') - expect(result.fledgeAuctionConfigs[0].config).to.not.be.undefined - expect(result.fledgeAuctionConfigs[0].config).to.contain.keys('seller', 'interestGroupBuyers', 'sellerTimeout', 'perBuyerSignals') + expect(result.fledgeAuctionConfigs.length).to.equal(2) + expect(result.fledgeAuctionConfigs).to.deep.equal(expectedFledgeResponse) }) - it('should ignore invalid fledge auction configs', function () { - const result = spec.interpretResponse(invalidFledgeResponse) - expect(result).to.have.property('bids') - expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(0) + it('should ignore empty fledge auction configs array', function () { + const result = spec.interpretResponse(emptyFledgeResponse) + expect(result.length).to.equal(1) + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) }) }) From 80f627d33c164e680fa78b37dfe4395cacfea3b0 Mon Sep 17 00:00:00 2001 From: ecdrsvc <82906140+ecdrsvc@users.noreply.github.com> Date: Tue, 7 May 2024 10:46:29 -0400 Subject: [PATCH 709/760] Mabidder Bid Adapter : use ortbConverter facility to pass ortb2 (#11447) * Add lmpIdSystem userId submodule * Use ortbConverter facility in mabidder --------- Co-authored-by: Antoine Niek --- modules/mabidderBidAdapter.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 632403c6643..8d97f48f604 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,9 +1,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'mabidder'; export const baseUrl = 'https://prebid.ecdrsvc.com/bid'; +const converter = ortbConverter({}) + export const spec = { supportedMediaTypes: [BANNER], code: BIDDER_CODE, @@ -14,7 +17,8 @@ export const spec = { return !!(bid.params.ppid && bid.sizes && Array.isArray(bid.sizes) && Array.isArray(bid.sizes[0])) }, buildRequests: function(validBidRequests, bidderRequest) { - const fpd = bidderRequest.ortb2; + const fpd = converter.toORTB({ bidRequests: validBidRequests, bidderRequest: bidderRequest }); + const bids = []; validBidRequests.forEach(bidRequest => { const sizes = []; From 8ebc22d462fe40f51ab2c5e5cfb0231ed6c9807e Mon Sep 17 00:00:00 2001 From: Jeff Mahoney Date: Tue, 7 May 2024 10:47:22 -0400 Subject: [PATCH 710/760] Update sharethroughBidAdapter.js (#11451) * Update the logic to determine how `gpid` is applied to a bid request. --- modules/sharethroughBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 2264bc37ebb..590fddca079 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -103,7 +103,7 @@ export const sharethroughAdapterSpec = { // mergeDeep(impression, bidReq.ortb2Imp); // leaving this out for now as we may want to leave stuff out on purpose const tid = deepAccess(bidReq, 'ortb2Imp.ext.tid'); if (tid) impression.ext.tid = tid; - const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid', deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot')); + const gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid') || deepAccess(bidReq, 'ortb2Imp.ext.data.pbadslot'); if (gpid) impression.ext.gpid = gpid; const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); From ff458c5d1feac0df3a2f654406bb9ee1bb1fee80 Mon Sep 17 00:00:00 2001 From: Taro FURUKAWA <6879289+0tarof@users.noreply.github.com> Date: Tue, 7 May 2024 23:49:21 +0900 Subject: [PATCH 711/760] remove format guard (#11452) --- modules/ajaBidAdapter.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ajaBidAdapter.js b/modules/ajaBidAdapter.js index e02ab920707..699dfd6fa04 100644 --- a/modules/ajaBidAdapter.js +++ b/modules/ajaBidAdapter.js @@ -55,11 +55,6 @@ export const spec = { for (let i = 0, len = validBidRequests.length; i < len; i++) { const bidRequest = validBidRequests[i]; - if ( - (bidRequest.mediaTypes?.native || bidRequest.mediaTypes?.video) && - bidRequest.mediaTypes?.banner) { - continue - } let queryString = ''; From 10830eed32b5412c20839a9b8e4931b430ddd69d Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 7 May 2024 11:13:24 -0700 Subject: [PATCH 712/760] EXADS bid adapter: replace broken logic with merely bad logic (#11456) --- modules/exadsBidAdapter.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js index 31d75db470d..20ead7a7e6b 100644 --- a/modules/exadsBidAdapter.js +++ b/modules/exadsBidAdapter.js @@ -319,24 +319,25 @@ function getEnvParams() { language: '' }; + // TODO: all of this is already in first party data envParams.domain = window.location.hostname; envParams.page = window.location.protocol + '//' + window.location.host + window.location.pathname; envParams.lang = navigator.language.indexOf('-') > -1 ? navigator.language.split('-')[0] : navigator.language; envParams.userAgent = navigator.userAgent; - if (envParams.userAgent.match(/Windows/i)) { envParams.osName = 'Windows'; } else if (envParams.userAgent.match(/Mac OS|Macintosh/i)) { envParams.osName = 'MacOS'; } else if (envParams.userAgent.match(/Unix/i)) { envParams.osName = 'Unix'; - } else if (envParams.userAgent.userAgent.match(/Android/i)) { + // TODO: what is userAgent.userAgent supposed to be? + } else if (envParams.userAgent.userAgent?.match(/Android/i)) { envParams.osName = 'Android'; - } else if (envParams.userAgent.userAgent.match(/iPhone|iPad|iPod/i)) { + } else if (envParams.userAgent.userAgent?.match(/iPhone|iPad|iPod/i)) { envParams.osName = 'iOS'; - } else if (envParams.userAgent.userAgent.match(/Linux/i)) { + } else if (envParams.userAgent.userAgent?.match(/Linux/i)) { envParams.osName = 'Linux'; } else { envParams.osName = 'Unknown'; From 4a4ada816117cc8c085ca3966af12121283ac68e Mon Sep 17 00:00:00 2001 From: JonGoSonobi Date: Tue, 7 May 2024 14:59:29 -0400 Subject: [PATCH 713/760] look for gpid in the ortb2Imp.ext.gpid (#11460) --- modules/sonobiBidAdapter.js | 2 +- test/spec/modules/sonobiBidAdapter_spec.js | 24 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index e1b51affd09..edc4f255d18 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -329,7 +329,7 @@ function _validateFloor(bid) { } function _validateGPID(bid) { - const gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot') || deepAccess(getGptSlotInfoForAdUnitCode(bid.adUnitCode), 'gptSlot') || bid.params.ad_unit; if (gpid) { return `gpid=${gpid},` diff --git a/test/spec/modules/sonobiBidAdapter_spec.js b/test/spec/modules/sonobiBidAdapter_spec.js index c7f954cfdcf..75da1983f0c 100644 --- a/test/spec/modules/sonobiBidAdapter_spec.js +++ b/test/spec/modules/sonobiBidAdapter_spec.js @@ -302,6 +302,29 @@ describe('SonobiBidAdapter', function () { } } }, + { + + 'bidder': 'sonobi', + 'params': { + 'keywords': 'sports,news,some_other_keyword', + 'placement_id': '1a2b3c4d5e6f1a2b3c4d', + 'sizes': [[300, 250], [300, 600]], + 'floor': '1.25', + }, + 'adUnitCode': 'adunit-code-42', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1g', + ortb2Imp: { + ext: { + gpid: '/123123/gpt_publisher/adunit-code-42' + } + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } + }, { 'bidder': 'sonobi', 'params': { @@ -343,6 +366,7 @@ describe('SonobiBidAdapter', function () { let keyMakerData = { '30b31c1838de1f': '1a2b3c4d5e6f1a2b3c4d|640x480|f=1.25,gpid=/123123/gpt_publisher/adunit-code-1,c=v,pm=1:2:3,p=2,pl=3,', + '30b31c1838de1g': '1a2b3c4d5e6f1a2b3c4d|300x250,300x600|f=1.25,gpid=/123123/gpt_publisher/adunit-code-42,c=d,', '30b31c1838de1d': '1a2b3c4d5e6f1a2b3c4e|300x250,300x600|f=0.42,gpid=/123123/gpt_publisher/adunit-code-3,c=d,', '/7780971/sparks_prebid_LB|30b31c1838de1e': '300x250,300x600|gpid=/7780971/sparks_prebid_LB,c=d,', }; From eb5ae983c8bc958f27b6ca6d353a245aa6796cbb Mon Sep 17 00:00:00 2001 From: lukashakl <107847079+lukashakl@users.noreply.github.com> Date: Tue, 7 May 2024 21:01:03 +0200 Subject: [PATCH 714/760] Performax Bid Adapter: New bidder adapter (#11325) --- modules/performaxBidAdapter.js | 77 ++++++++ modules/performaxBidAdapter.md | 36 ++++ test/spec/modules/performaxBidAdapter_spec.js | 175 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 modules/performaxBidAdapter.js create mode 100644 modules/performaxBidAdapter.md create mode 100644 test/spec/modules/performaxBidAdapter_spec.js diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js new file mode 100644 index 00000000000..a765c4d9d78 --- /dev/null +++ b/modules/performaxBidAdapter.js @@ -0,0 +1,77 @@ +import { deepSetValue, deepAccess } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; + +const BIDDER_CODE = 'performax'; +const BIDDER_SHORT_CODE = 'px'; +const GVLID = 732 +const ENDPOINT = 'https://dale.performax.cz/ortb' +export const converter = ortbConverter({ + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue(imp, 'tagid', bidRequest.params.tagid); + return imp; + }, + + bidResponse(buildBidResponse, bid, context) { + context.netRevenue = deepAccess(bid, 'netRevenue'); + context.mediaType = deepAccess(bid, 'mediaType'); + context.currency = deepAccess(bid, 'currency'); + + return buildBidResponse(bid, context) + }, + + context: { + ttl: 360, + } +}) + +export const spec = { + code: BIDDER_CODE, + aliases: [BIDDER_SHORT_CODE], + gvlid: GVLID, + supportedMediaTypes: [BANNER], + + isBidRequestValid: function (bid) { + return !!bid.params.tagid; + }, + + buildRequests: function (bidRequests, bidderRequest) { + let data = converter.toORTB({bidderRequest, bidRequests}) + return [{ + method: 'POST', + url: ENDPOINT, + options: {'contentType': 'application/json'}, + data: data + }] + }, + + interpretResponse: function (bidderResponse, request) { + if (!bidderResponse.body) return []; + const response = bidderResponse.body + const data = { + + seatbid: response.seatbid.map(seatbid => ({ + seat: seatbid.seat, + bid: seatbid.bid.map(bid => ({ + impid: bid.imp_id, + w: bid.w, + h: bid.h, + requestId: request.data.id, + price: bid.price, + currency: response.cur, + adm: bid.adm, + crid: bid.id, + netRevenue: true, + mediaType: BANNER, + })) + })) + }; + return converter.fromORTB({ response: data, request: request.data }).bids + }, + +} + +registerBidder(spec); diff --git a/modules/performaxBidAdapter.md b/modules/performaxBidAdapter.md new file mode 100644 index 00000000000..8b5b702a8e6 --- /dev/null +++ b/modules/performaxBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Performax Bid Adapter +Module Type: Bidder Adapter +Maintainer: development@performax.cz +``` + +# Description + +Connects to Performax exchange for bids. + +Performax bid adapter supports Banner. + + +# Sample Banner Ad Unit: For Publishers + +```javascript + var adUnits = [ + { + code: 'performax-div', + mediaTypes: { + banner: {sizes: [[300, 300]]}, + }, + bids: [ + { + bidder: "performax", + params: { + tagid: "sample" // required + } + } + ] + }, + ]; +``` + diff --git a/test/spec/modules/performaxBidAdapter_spec.js b/test/spec/modules/performaxBidAdapter_spec.js new file mode 100644 index 00000000000..49a6a83e29d --- /dev/null +++ b/test/spec/modules/performaxBidAdapter_spec.js @@ -0,0 +1,175 @@ +import { expect } from 'chai'; +import { spec, converter } from 'modules/performaxBidAdapter.js'; + +describe('Performax adapter', function () { + let bids = [{ + bidder: 'performax', + params: { + tagid: 'sample' + }, + ortb2Imp: { + ext: {} + }, + mediaTypes: { + banner: { + sizes: [ + [300, 300], + ]}}, + adUnitCode: 'postbid_iframe', + transactionId: '84deda92-e9ba-4b0d-a797-43be5e522430', + adUnitId: '4ee4643b-931f-4a17-a571-ccba57886dc8', + sizes: [ + [300, 300], + ], + bidId: '2bc545c347dbbe', + bidderRequestId: '1534dec005b9a', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + source: {}, + site: {}, + device: {} + }, + }, + + { + bidder: 'performax', + params: { + tagid: '1545' + }, + ortb2Imp: { + ext: {} + }, + mediaTypes: { + banner: { + sizes: [ + [300, 600], + ]}}, + adUnitCode: 'postbid_halfpage_iframe', + transactionId: '84deda92-e9ba-4b0d-a797-43be5e522430', + adUnitId: '4ee4643b-931f-4a17-a571-ccba57886dc8', + sizes: [ + [300, 600], + ], + bidId: '3dd53d30c691fe', + bidderRequestId: '1534dec005b9a', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + src: 'client', + bidRequestsCount: 1, + bidderRequestsCount: 1, + bidderWinsCount: 0, + ortb2: { + source: {}, + site: {}, + device: {} + }}]; + + let bidderRequest = { + bidderCode: 'performax2', + auctionId: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + id: 'acd97e55-01e1-45ad-813c-67fa27fc5c1b', + bidderRequestId: '1534dec005b9a', + bids: bids, + ortb2: { + regs: { + ext: { + gdpr: 1 + }}, + user: { + ext: { + consent: 'consent-string' + } + }, + site: {}, + device: {} + }}; + + let serverResponse = { + body: { + cur: 'CZK', + seatbid: [ + { + seat: 'performax', + bid: [ + { + id: 'sample', + price: 20, + w: 300, + h: 300, + adm: 'My ad' + } + ]}]}, + } + + describe('isBidRequestValid', function () { + let bid = {}; + it('should return false when missing "tagid" param', function() { + bid.params = {slotId: 'param'}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when tagid is correct', function() { + bid.params = {tagid: 'sample'}; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }) + + describe('buildRequests', function () { + it('should set correct request method and url', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let request = requests[0]; + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://dale.performax.cz/ortb'); + expect(request.data).to.be.an('object'); + }); + + it('should pass correct imp', function () { + let requests = spec.buildRequests([bids[0]], bidderRequest); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(1); + expect(imp[0]).to.be.an('object'); + let bid = imp[0]; + expect(bid.id).to.equal('2bc545c347dbbe'); + expect(bid.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 300}]}); + }); + + it('should process multiple bids', function () { + let requests = spec.buildRequests(bids, bidderRequest); + expect(requests).to.be.an('array').that.has.lengthOf(1); + let {data} = requests[0]; + let {imp} = data; + expect(imp).to.be.an('array').that.has.lengthOf(bids.length); + let bid1 = imp[0]; + expect(bid1.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 300}]}); + let bid2 = imp[1]; + expect(bid2.banner).to.deep.equal({topframe: 0, format: [{w: 300, h: 600}]}); + }); + }); + + describe('interpretResponse', function () { + it('should map params correctly', function () { + let ortbRequest = {data: converter.toORTB({bidderRequest, bids})}; + serverResponse.body.id = ortbRequest.data.id; + serverResponse.body.seatbid[0].bid[0].imp_id = ortbRequest.data.imp[0].id; + + let result = spec.interpretResponse(serverResponse, ortbRequest); + expect(result).to.be.an('array').that.has.lengthOf(1); + let bid = result[0]; + + expect(bid.cpm).to.equal(20); + expect(bid.ad).to.equal('My ad'); + expect(bid.currency).to.equal('CZK'); + expect(bid.mediaType).to.equal('banner'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(360); + expect(bid.creativeId).to.equal('sample'); + }); + }); +}); From 0b574b3adfaecf756ccb7233229541e74241f3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=BEidar=20Bare=C5=A1i=C4=87?= <153560835+bbaresic@users.noreply.github.com> Date: Tue, 7 May 2024 15:20:27 -0500 Subject: [PATCH 715/760] faster-deep-clone (#11418) --- allowedModules.js | 2 +- modules/browsiRtdProvider.js | 2 +- package-lock.json | 25 ++++++++++++++----------- package.json | 2 +- src/utils.js | 4 ++-- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/allowedModules.js b/allowedModules.js index 75ad4141a6c..bc9ada39571 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -7,7 +7,7 @@ module.exports = { ], 'src': [ 'fun-hooks/no-eval', - 'just-clone', + 'klona', 'dlv', 'dset' ], diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index ad4019ed03c..01a38c63b69 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -57,7 +57,7 @@ export function addBrowsiTag(data) { script.setAttribute('prebidbpt', 'true'); script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); - script.prebidData = deepClone(data); + script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } diff --git a/package-lock.json b/package-lock.json index 261cbdb1fb7..9a373144ebd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", - "just-clone": "^1.0.2", + "klona": "^2.0.6", "live-connect-js": "^6.3.4" }, "devDependencies": { @@ -19198,11 +19198,6 @@ "node": ">=0.6.0" } }, - "node_modules/just-clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", - "integrity": "sha512-p93GINPwrve0w3HUzpXmpTl7MyzzWz1B5ag44KEtq/hP1mtK8lA2b9Q0VQaPlnY87352osJcE6uBmN0e8kuFMw==" - }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -19677,6 +19672,14 @@ "node": ">=6" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", @@ -43994,11 +43997,6 @@ "verror": "1.10.0" } }, - "just-clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-1.0.2.tgz", - "integrity": "sha512-p93GINPwrve0w3HUzpXmpTl7MyzzWz1B5ag44KEtq/hP1mtK8lA2b9Q0VQaPlnY87352osJcE6uBmN0e8kuFMw==" - }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -44382,6 +44380,11 @@ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==" + }, "konan": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", diff --git a/package.json b/package.json index acac0a99da7..28ce27dce05 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", - "just-clone": "^1.0.2", + "klona": "^2.0.6", "live-connect-js": "^6.3.4" }, "optionalDependencies": { diff --git a/src/utils.js b/src/utils.js index abb69209020..3225eac162e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,5 @@ import {config} from './config.js'; -import clone from 'just-clone'; +import {klona} from 'klona/json'; import {includes} from './polyfill.js'; import { EVENTS, S2S } from './constants.js'; import {GreedyPromise} from './utils/promise.js'; @@ -609,7 +609,7 @@ export function shuffle(array) { } export function deepClone(obj) { - return clone(obj); + return klona(obj) || {}; } export function inIframe() { From 3870124588e92a2728a4577ea73f5772223b4da3 Mon Sep 17 00:00:00 2001 From: Viktor Dreiling <34981284+3link@users.noreply.github.com> Date: Wed, 8 May 2024 15:57:04 +0200 Subject: [PATCH 716/760] LiveIntent User ID Module: Stabilize Tests (#11463) * Filter for relevant events in tests * Fix expectation --- test/spec/modules/liveIntentIdSystem_spec.js | 83 +++++++++++--------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 084b4de212a..373142db82e 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -10,6 +10,18 @@ const PUBLISHER_ID = '89899'; const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; const responseHeader = {'Content-Type': 'application/json'} +function requests(...urlRegExps) { + return server.requests.filter((request) => urlRegExps.some((regExp) => request.url.match(regExp))) +} + +function rpRequests() { + return requests(/https:\/\/rp.liadm.com.*/) +} + +function idxRequests() { + return requests(/https:\/\/idx.liadm.com.*/) +} + describe('LiveIntentId', function() { let logErrorStub; let uspConsentDataStub; @@ -57,7 +69,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1%2C2.*/); const response = { unifiedId: 'a_unified_id', @@ -83,7 +95,7 @@ describe('LiveIntentId', function() { }) liveIntentIdSubmodule.getId(defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); @@ -94,7 +106,7 @@ describe('LiveIntentId', function() { emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) done(); }, 300); }); @@ -102,7 +114,7 @@ describe('LiveIntentId', function() { it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.contain('tv=$prebid.version$') + expect(rpRequests()[0].url).to.contain('tv=$prebid.version$') done(); }, 300); }); @@ -119,7 +131,7 @@ describe('LiveIntentId', function() { } }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(requests(/https:\/\/collector.liveintent.com.*/)[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); done(); }, 300); }); @@ -127,7 +139,7 @@ describe('LiveIntentId', function() { it('should fire an event with the provided distributorId', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); done(); }, 300); }); @@ -135,8 +147,9 @@ describe('LiveIntentId', function() { it('should fire an event without the provided distributorId when appId is provided', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); - expect(server.requests[0].url).to.not.match(/.*did=*/); + const request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(request.url).to.not.match(/.*did=*/); done(); }, 300); }); @@ -153,7 +166,7 @@ describe('LiveIntentId', function() { }) liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); + expect(rpRequests()[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); @@ -164,7 +177,7 @@ describe('LiveIntentId', function() { emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); done(); }, 300); }); @@ -177,7 +190,7 @@ describe('LiveIntentId', function() { it('should fire an event when decode', function(done) { liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests[0].url).to.be.not.null + expect(rpRequests()[0].url).to.be.not.null done(); }, 300); }); @@ -188,7 +201,7 @@ describe('LiveIntentId', function() { liveIntentIdSubmodule.getId(defaultConfigParams); liveIntentIdSubmodule.decode({}, defaultConfigParams); setTimeout(() => { - expect(server.requests.length).to.be.eq(1); + expect(rpRequests().length).to.be.eq(1); done(); }, 300); }); @@ -198,7 +211,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 204, @@ -212,7 +225,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&cd=.localhost&resolve=nonId'); request.respond( 204, @@ -226,7 +239,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?cd=.localhost&resolve=nonId'); request.respond( 204, @@ -246,7 +259,7 @@ describe('LiveIntentId', function() { } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?cd=.localhost&resolve=nonId'); request.respond( 200, @@ -261,7 +274,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 200, @@ -276,7 +289,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); request.respond( 503, @@ -293,7 +306,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&resolve=nonId`); request.respond( 200, @@ -316,7 +329,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&_thirdPC=third-pc&resolve=nonId`); request.respond( 200, @@ -338,7 +351,7 @@ describe('LiveIntentId', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); request.respond( 200, @@ -355,12 +368,12 @@ describe('LiveIntentId', function() { }); it('should decode a unifiedId to lipbId and remove it', function() { - const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }); + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); }); it('should decode a nonId to lipbId', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'data' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); }); @@ -371,7 +384,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId&resolve=foo`); request.respond( 200, @@ -382,54 +395,54 @@ describe('LiveIntentId', function() { }); it('should decode a uid2 to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { - const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }); + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a medianet id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sovrn id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a magnite id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an index id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an openx id to a separate object when present', function () { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an pubmatic id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a thetradedesk id to a separate object when present', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); @@ -440,7 +453,7 @@ describe('LiveIntentId', function() { ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); - let request = server.requests[0]; + let request = idxRequests()[0]; expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=uid2`); request.respond( 200, From 5438945e9d8de671a2b15db2f5fee1b9aa5b885d Mon Sep 17 00:00:00 2001 From: Ruslan S Date: Wed, 8 May 2024 22:29:22 +0800 Subject: [PATCH 717/760] Smaato: Change server response type (#11450) --- modules/smaatoBidAdapter.js | 46 ++++---------- test/spec/modules/smaatoBidAdapter_spec.js | 74 ++-------------------- 2 files changed, 18 insertions(+), 102 deletions(-) diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 9a0a0f75623..8f325aa13c9 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,7 +19,7 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_2.0' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.0' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; @@ -160,12 +160,8 @@ export const spec = { } else { switch (smtAdType) { case 'Img': - resultingBid.ad = createImgAd(bid.adm); - resultingBid.mediaType = BANNER; - bids.push(resultingBid); - break; case 'Richmedia': - resultingBid.ad = createRichmediaAd(bid.adm); + resultingBid.ad = createBannerAd(bid); resultingBid.mediaType = BANNER; bids.push(resultingBid); break; @@ -370,37 +366,17 @@ const converter = ortbConverter({ } }); -const createImgAd = (adm) => { - const image = JSON.parse(adm).image; - +const createBannerAd = (bid) => { let clickEvent = ''; - image.clicktrackers.forEach(src => { - clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; - }) - - let markup = `
`; - - image.impressiontrackers.forEach(src => { - markup += ``; - }); - - return markup + '
'; -}; - -const createRichmediaAd = (adm) => { - const rich = JSON.parse(adm).richmedia; - let clickEvent = ''; - rich.clicktrackers.forEach(src => { - clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; - }) - - let markup = `
${rich.mediadata.content}`; - - rich.impressiontrackers.forEach(src => { - markup += ``; - }); + if (bid.ext && bid.ext.curls) { + let clicks = '' + bid.ext.curls.forEach(src => { + clicks += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`; + }) + clickEvent = `onclick="${clicks}"` + } - return markup + '
'; + return `
${bid.adm}
`; }; const createNativeAd = (adm) => { diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 28fd06c4b8d..2ac2a1e5c33 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -13,7 +13,6 @@ import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; const ADTYPE_IMG = 'Img'; -const ADTYPE_RICHMEDIA = 'Richmedia'; const ADTYPE_VIDEO = 'Video'; const ADTYPE_NATIVE = 'Native'; @@ -1300,43 +1299,7 @@ describe('smaatoBidAdapterTest', () => { switch (adType) { case ADTYPE_IMG: - adm = JSON.stringify( - { - image: { - img: { - url: 'https://prebid/static/ad.jpg', - w: 320, - h: 50, - ctaurl: 'https://prebid/track/ctaurl' - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); - break; - case ADTYPE_RICHMEDIA: - adm = JSON.stringify( - { - richmedia: { - mediadata: { - content: '

RICHMEDIA CONTENT

', - w: 800, - h: 600 - }, - impressiontrackers: [ - 'https://prebid/track/imp/1', - 'https://prebid/track/imp/2' - ], - clicktrackers: [ - 'https://prebid/track/click/1' - ] - } - }); + adm = '' break; case ADTYPE_VIDEO: adm = ''; @@ -1372,7 +1335,10 @@ describe('smaatoBidAdapterTest', () => { 'nurl': 'https://prebid/nurl', 'price': 0.01, 'w': 350, - 'h': 50 + 'h': 50, + 'ext': { + curls: ['https://prebid/track/click/1'] + } } ], seat: 'CM6523' @@ -1398,7 +1364,7 @@ describe('smaatoBidAdapterTest', () => { }); describe('non ad pod', () => { - it('single image response', () => { + it('single banner response', () => { const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_IMG), buildBidRequest()); expect(bids).to.deep.equal([ @@ -1407,33 +1373,7 @@ describe('smaatoBidAdapterTest', () => { cpm: 0.01, width: 350, height: 50, - ad: '
', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - mediaType: 'banner', - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'banner' - } - } - ]); - }); - - it('single richmedia response', () => { - const bids = spec.interpretResponse(buildOpenRtbBidResponse(ADTYPE_RICHMEDIA), buildBidRequest()); - - expect(bids).to.deep.equal([ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - ad: '

RICHMEDIA CONTENT

', + ad: '
', ttl: 300, creativeId: 'CR69381', dealId: '12345', From 8c5c9d52ef996d90958b41b02808677e0ff7f4bb Mon Sep 17 00:00:00 2001 From: Giuseppe Cera <117671343+giuseppe-exads@users.noreply.github.com> Date: Wed, 8 May 2024 17:10:03 +0100 Subject: [PATCH 718/760] EXADS Bid Adapter : update bidder code before adapter is published (#11464) * First commit * fix: readme.md * fix: changed exads urls * fix: Tools and suggestions related to the doc * fix: from code review * fix: from code review * fix: from code review * fix: error from code review - native example * fox: from code review * fix: from code review * fix: from code review * fix: native img set as mandatory * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: from code review * fix: bidfloor and bidfloorcur set as optional * fix: dsa * fix: mananing multiple responses * fix: unit test after code review * fix: fixing native snippet code * fix: from code review * fix: video events after code review * fix: video module into documentation * fix: impression tracker for native * fix: afeter code review * fix: unit tests * fix: added badv and bcat * fix: video -> mimes and protocols * fix * fix: removed image_output and video_output params, forcing always html for rtb banner * fix: gulp * fix: added site.name * fix: removed EXADS dir * fix: after linting * fix: unit tests * fix: final dsa solution * fix: dsa * fix: fix instream example * fix: doc media type context * fix: documented the endpoint param into native section * fix: related to markdown lint validation (#2) * fix: from CR (#3) * fix: changed bidder code to exads * fix: userAgent --------- Co-authored-by: tfoliveira --- modules/exadsBidAdapter.js | 9 ++++----- modules/exadsBidAdapter.md | 8 ++++---- test/spec/modules/exadsBidAdapter_spec.js | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/modules/exadsBidAdapter.js b/modules/exadsBidAdapter.js index 20ead7a7e6b..e50c141f4b0 100644 --- a/modules/exadsBidAdapter.js +++ b/modules/exadsBidAdapter.js @@ -2,7 +2,7 @@ import * as utils from '../src/utils.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER = 'exadsadserver'; +const BIDDER = 'exads'; const PARTNERS = { ORTB_2_4: 'ortb_2_4' @@ -332,12 +332,11 @@ function getEnvParams() { envParams.osName = 'MacOS'; } else if (envParams.userAgent.match(/Unix/i)) { envParams.osName = 'Unix'; - // TODO: what is userAgent.userAgent supposed to be? - } else if (envParams.userAgent.userAgent?.match(/Android/i)) { + } else if (envParams.userAgent.match(/Android/i)) { envParams.osName = 'Android'; - } else if (envParams.userAgent.userAgent?.match(/iPhone|iPad|iPod/i)) { + } else if (envParams.userAgent.match(/iPhone|iPad|iPod/i)) { envParams.osName = 'iOS'; - } else if (envParams.userAgent.userAgent?.match(/Linux/i)) { + } else if (envParams.userAgent.match(/Linux/i)) { envParams.osName = 'Linux'; } else { envParams.osName = 'Unknown'; diff --git a/modules/exadsBidAdapter.md b/modules/exadsBidAdapter.md index 06b873d8da8..4c8eedffdd0 100644 --- a/modules/exadsBidAdapter.md +++ b/modules/exadsBidAdapter.md @@ -123,7 +123,7 @@ adUnits = } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -162,7 +162,7 @@ adUnits = } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -237,7 +237,7 @@ adUnits = [{ } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', @@ -365,7 +365,7 @@ adUnits = [{ } }, bids: [{ - bidder: 'exadsadserver', + bidder: 'exads', params: { zoneId: 12345, fid: '829a896f011475d50da0d82cfdd1af8d9cdb07ff', diff --git a/test/spec/modules/exadsBidAdapter_spec.js b/test/spec/modules/exadsBidAdapter_spec.js index 9253f21ddf1..ca24dad3959 100644 --- a/test/spec/modules/exadsBidAdapter_spec.js +++ b/test/spec/modules/exadsBidAdapter_spec.js @@ -3,7 +3,7 @@ import { spec, imps } from 'modules/exadsBidAdapter.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('exadsBidAdapterTest', function () { - const bidder = 'exadsadserver'; + const bidder = 'exads'; const partners = { ORTB_2_4: 'ortb_2_4' From 85fd44e1e29939bbda2614553fa9ff787579cdbc Mon Sep 17 00:00:00 2001 From: Harry King-Riches <109534328+harrykingriches@users.noreply.github.com> Date: Wed, 8 May 2024 18:49:47 +0100 Subject: [PATCH 719/760] Rubicon Bid Adapter: Provide backwards compatibility for transparency object (#11426) * Provide backwards compatibility for transparency object When the property `transparency.params` gets passed in the `ortb2` object, the Rubicon adapter throws an error. This change will provide backwards compatibility for the `params` property by adding it to the `dsaparams` property. If `params` or `dsaparams` does not exist, the value will be turned into an empty string. The same is done for the `domain` property. This ensures that the Bid Adapter will not error if these properties do not exist. * Refactor RubiconBidAdapter to fix transparency object compatibility * Fix transparency object compatibility in RubiconBidAdapter * Fix transparency object compatibility in RubiconBidAdapter * Refactor dsatransparency check --------- Co-authored-by: Harry King-Riches <109534328+Strife9224@users.noreply.github.com> --- modules/rubiconBidAdapter.js | 18 ++++++- test/spec/modules/rubiconBidAdapter_spec.js | 58 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index c03065cd5a5..9e47807bdc0 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -982,11 +982,25 @@ function applyFPD(bidRequest, mediaType, data) { 'transparency', (transparency) => { if (Array.isArray(transparency) && transparency.length) { data['dsatransparency'] = transparency.reduce((param, transp) => { + // make sure domain is there, otherwise skip entry + const domain = transp.domain || ''; + if (!domain) { + return param; + } + + // make sure dsaParam array is there (try both 'dsaparams' and 'params', but prefer dsaparams) + const dsaParamArray = transp.dsaparams || transp.params; + if (!Array.isArray(dsaParamArray) || dsaParamArray.length === 0) { + return param; + } + + // finally we will add this one, if param has been added already, add our seperator if (param) { param += '~~' } - return param += `${transp.domain}~${transp.dsaparams.join('_')}` - }, '') + + return param += `${domain}~${dsaParamArray.join('_')}`; + }, ''); } } ]) diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 55e8909f6c8..494943f9f7d 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -1735,6 +1735,64 @@ describe('the rubicon adapter', function () { } } } + it('should send valid dsaparams but filter out invalid ones', function () { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + ortb2Clone.regs.ext.dsa.transparency = [ + { + domain: 'testdomain.com', + dsaparams: [1], + }, + { + domain: '', + dsaparams: [2], + } + ]; + + const expectedTransparency = 'testdomain.com~1'; + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({ ...b, ortb2: ortb2Clone })), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.equal(expectedTransparency); + }) + it('should send dsaparams if \"ortb2.regs.ext.dsa.transparancy[0].params\"', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: 'testdomain.com', + dsaparams: [1], + }]; + + const expectedTransparency = 'testdomain.com~1'; + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.equal(expectedTransparency); + }) + it('should pass an empty transparency param if \"ortb2.regs.ext.dsa.transparency[0].params\" is empty', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: 'testdomain.com', + params: [], + }]; + + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + expect(data['dsatransparency']).to.be.undefined + }) + it('should send an empty transparency if \"ortb2.regs.ext.dsa.transparency[0].domain\" is empty', function() { + const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); + + ortb2Clone.regs.ext.dsa.transparency = [{ + domain: '', + dsaparams: [1], + }]; + + const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); + const data = parseQuery(request.data); + + expect(data['dsatransparency']).to.be.undefined + }) it('should send dsa signals if \"ortb2.regs.ext.dsa\"', function() { const expectedTransparency = 'testdomain.com~1~~testdomain2.com~1_2' const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest) From b76cddda19812501f67772296e4936cc216ec3f3 Mon Sep 17 00:00:00 2001 From: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com> Date: Wed, 8 May 2024 22:36:40 +0300 Subject: [PATCH 720/760] Playdigo: new adapter (#11378) * init adapter * add gpp support * upd --- modules/playdigoBidAdapter.js | 199 +++++++++ modules/playdigoBidAdapter.md | 78 ++++ test/spec/modules/playdigoBidAdapter_spec.js | 446 +++++++++++++++++++ 3 files changed, 723 insertions(+) create mode 100644 modules/playdigoBidAdapter.js create mode 100644 modules/playdigoBidAdapter.md create mode 100644 test/spec/modules/playdigoBidAdapter_spec.js diff --git a/modules/playdigoBidAdapter.js b/modules/playdigoBidAdapter.js new file mode 100644 index 00000000000..6c4ea6492d9 --- /dev/null +++ b/modules/playdigoBidAdapter.js @@ -0,0 +1,199 @@ +import { logMessage, logError } from '../src/utils.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'playdigo'; +const AD_URL = 'https://server.playdigo.com/pbjs'; + +function isBidResponseValid(bid) { + if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { + return false; + } + + switch (bid.mediaType) { + case BANNER: + return Boolean(bid.width && bid.height && bid.ad); + case VIDEO: + return Boolean(bid.vastUrl || bid.vastXml); + case NATIVE: + return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); + default: + return false; + } +} + +function getPlacementReqData(bid) { + const { params, bidId, mediaTypes } = bid; + const schain = bid.schain || {}; + const { placementId, endpointId } = params; + const bidfloor = getBidFloor(bid); + + const placement = { + bidId, + schain, + bidfloor + }; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } + + if (mediaTypes && mediaTypes[BANNER]) { + placement.adFormat = BANNER; + placement.sizes = mediaTypes[BANNER].sizes; + } else if (mediaTypes && mediaTypes[VIDEO]) { + placement.adFormat = VIDEO; + placement.playerSize = mediaTypes[VIDEO].playerSize; + placement.minduration = mediaTypes[VIDEO].minduration; + placement.maxduration = mediaTypes[VIDEO].maxduration; + placement.mimes = mediaTypes[VIDEO].mimes; + placement.protocols = mediaTypes[VIDEO].protocols; + placement.startdelay = mediaTypes[VIDEO].startdelay; + placement.placement = mediaTypes[VIDEO].placement; + placement.skip = mediaTypes[VIDEO].skip; + placement.skipafter = mediaTypes[VIDEO].skipafter; + placement.minbitrate = mediaTypes[VIDEO].minbitrate; + placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; + placement.delivery = mediaTypes[VIDEO].delivery; + placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; + placement.api = mediaTypes[VIDEO].api; + placement.linearity = mediaTypes[VIDEO].linearity; + } else if (mediaTypes && mediaTypes[NATIVE]) { + placement.native = mediaTypes[NATIVE]; + placement.adFormat = NATIVE; + } + + return placement; +} + +function getBidFloor(bid) { + if (!bid.getFloor || typeof bid.getFloor !== 'function') { + return 0; + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor.floor; + } catch (err) { + logError(err); + return 0; + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); + + if (mediaTypes && mediaTypes[BANNER]) { + valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); + } else if (mediaTypes && mediaTypes[NATIVE]) { + valid = valid && Boolean(mediaTypes[NATIVE]); + } else { + valid = false; + } + return valid; + }, + + buildRequests: (validBidRequests = [], bidderRequest = {}) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + let deviceWidth = 0; + let deviceHeight = 0; + + let winLocation; + try { + const winTop = window.top; + deviceWidth = winTop.screen.width; + deviceHeight = winTop.screen.height; + winLocation = winTop.location; + } catch (e) { + logMessage(e); + winLocation = window.location; + } + + const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; + let refferLocation; + try { + refferLocation = refferUrl && new URL(refferUrl); + } catch (e) { + logMessage(e); + } + let location = refferLocation || winLocation; + const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; + const host = location.host; + const page = location.pathname; + const secure = location.protocol === 'https:' ? 1 : 0; + const placements = []; + const request = { + deviceWidth, + deviceHeight, + language, + secure, + host, + page, + placements, + coppa: config.getConfig('coppa') === true ? 1 : 0, + ccpa: bidderRequest.uspConsent || undefined, + tmax: config.getConfig('bidderTimeout') + }; + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(getPlacementReqData(bid)); + } + + return { + method: 'POST', + url: AD_URL, + data: request + }; + }, + + interpretResponse: (serverResponse) => { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + return response; + } +}; + +registerBidder(spec); diff --git a/modules/playdigoBidAdapter.md b/modules/playdigoBidAdapter.md new file mode 100644 index 00000000000..1c63cce79a1 --- /dev/null +++ b/modules/playdigoBidAdapter.md @@ -0,0 +1,78 @@ +# Overview + +``` +Module Name: Playdigo Bidder Adapter +Module Type: Playdigo Bidder Adapter +Maintainer: yr@playdigo.com +``` + +# Description + +One of the easiest way to gain access to Playdigo demand sources - Playdigo header bidding adapter. +Playdigo header bidding adapter connects with Playdigo demand sources to fetch bids for display placements + +# Test Parameters +``` + var adUnits = [ + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'playdigo', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js new file mode 100644 index 00000000000..80fc3c96e81 --- /dev/null +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -0,0 +1,446 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/playdigoBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'playdigo' + +describe('PlaydigoBidAdapter', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + } + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + } + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com' + } + }; + + describe('isBidRequestValid', function () { + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; + }); + it('Should return false if at least one of parameters is not present', function () { + expect(spec.isBidRequestValid(invalidBid)).to.be.false; + }); + }); + + describe('buildRequests', function () { + let serverRequest = spec.buildRequests(bids, bidderRequest); + + it('Creates a ServerRequest object with method, URL and data', function () { + expect(serverRequest).to.exist; + expect(serverRequest.method).to.exist; + expect(serverRequest.url).to.exist; + expect(serverRequest.data).to.exist; + }); + + it('Returns POST method', function () { + expect(serverRequest.method).to.equal('POST'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); + + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + } + } + ] + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } + }); + + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; + }); + + it('Returns data with uspConsent and without gdprConsent', function () { + bidderRequest.uspConsent = '1---'; + delete bidderRequest.gdprConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data.ccpa).to.exist; + expect(data.ccpa).to.be.a('string'); + expect(data.ccpa).to.equal(bidderRequest.uspConsent); + expect(data.gdpr).to.not.exist; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + describe('interpretResponse', function () { + it('Should interpret banner response', function () { + const banner = { + body: [{ + mediaType: 'banner', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let bannerResponses = spec.interpretResponse(banner); + expect(bannerResponses).to.be.an('array').that.is.not.empty; + let dataItem = bannerResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; + + let dataItem = videoResponses[0]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.5); + expect(dataItem.vastUrl).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should interpret native response', function () { + const native = { + body: [{ + mediaType: 'native', + native: { + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], + }, + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + }] + }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; + + let dataItem = nativeResponses[0]; + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); + expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') + expect(dataItem.requestId).to.equal('23fhj33i987f'); + expect(dataItem.cpm).to.equal(0.4); + expect(dataItem.native.clickUrl).to.equal('test.com'); + expect(dataItem.native.title).to.equal('Test'); + expect(dataItem.native.image).to.equal('test.com'); + expect(dataItem.native.impressionTrackers).to.be.an('array').that.is.not.empty; + expect(dataItem.native.impressionTrackers[0]).to.equal('test.com'); + expect(dataItem.ttl).to.equal(120); + expect(dataItem.creativeId).to.equal('2'); + expect(dataItem.netRevenue).to.be.true; + expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + }); + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + + let serverResponses = spec.interpretResponse(invBanner); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid video response is passed', function () { + const invVideo = { + body: [{ + mediaType: 'video', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invVideo); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid native response is passed', function () { + const invNative = { + body: [{ + mediaType: 'native', + clickUrl: 'test.com', + title: 'Test', + impressionTrackers: ['test.com'], + ttl: 120, + requestId: '23fhj33i987f', + creativeId: '2', + netRevenue: true, + currency: 'USD', + }] + }; + let serverResponses = spec.interpretResponse(invNative); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + it('Should return an empty array if invalid response is passed', function () { + const invalid = { + body: [{ + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] + }; + let serverResponses = spec.interpretResponse(invalid); + expect(serverResponses).to.be.an('array').that.is.empty; + }); + }); +}); From 3c814105d86e66dcde8353754493840f953ebd8d Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 8 May 2024 22:38:07 +0300 Subject: [PATCH 721/760] Twist Digital Bid Adapter: initial release (#11370) * Added twist digital bid adapter. * Add maintainer email to twistDigitalBidAdapter * Update default subdomain in twistDigitalBidAdapter. * remove unneeded dealId. --- modules/twistDigitalBidAdapter.js | 463 +++++++++ modules/twistDigitalBidAdapter.md | 42 + .../modules/twistDigitalBidAdapter_spec.js | 937 ++++++++++++++++++ 3 files changed, 1442 insertions(+) create mode 100644 modules/twistDigitalBidAdapter.js create mode 100644 modules/twistDigitalBidAdapter.md create mode 100644 test/spec/modules/twistDigitalBidAdapter_spec.js diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js new file mode 100644 index 00000000000..f509e68f9a2 --- /dev/null +++ b/modules/twistDigitalBidAdapter.js @@ -0,0 +1,463 @@ +import { + _each, + deepAccess, + isFn, + parseSizesInput, + parseUrl, + uniques, + isArray, + formatQS, + triggerPixel +} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {bidderSettings} from '../src/bidderSettings.js'; +import {config} from '../src/config.js'; +import {chunk} from '../libraries/chunk/chunk.js'; + +const GVLID = 1292; +const DEFAULT_SUB_DOMAIN = 'exchange'; +const BIDDER_CODE = 'twistdigital'; +const BIDDER_VERSION = '1.0.0'; +const CURRENCY = 'USD'; +const TTL_SECONDS = 60 * 5; +const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; + +export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.twist.win`; +} + +export function extractCID(params) { + return params.cId || params.CID || params.cID || params.CId || params.cid || params.ciD || params.Cid || params.CiD; +} + +export function extractPID(params) { + return params.pId || params.PID || params.pID || params.PId || params.pid || params.piD || params.Pid || params.PiD; +} + +export function extractSubDomain(params) { + return params.subDomain || params.SubDomain || params.Subdomain || params.subdomain || params.SUBDOMAIN || params.subDOMAIN; +} + +function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueDealId = getUniqueDealId(hashUrl); + const pId = extractPID(params); + const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); + const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); + const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: BIDDER_VERSION, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + isStorageAllowed: isStorageAllowed, + gpid: gpid, + cat: cat, + contentData, + userData: userData, + pagecat: pagecat, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + webSessionId: webSessionId + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (bidderRequest.fledgeEnabled) { + const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); + if (fledge) { + data.fledge = fledge; + } + } + + _each(ext, (value, key) => { + data['ext.' + key] = value; + }); + + return data; +} + +function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const {params} = bid; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); + const dto = { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: data + }; + return dto; +} + +function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { + const {params} = bidRequests[0]; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = bidRequests.map(bid => { + const sizes = parseSizesInput(bid.sizes); + return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) + }); + const chunkSize = Math.min(20, config.getConfig('twistdigital.chunkSize') || 10); + + const chunkedData = chunk(data, chunkSize); + return chunkedData.map(chunk => { + return { + method: 'POST', + url: `${createDomain(subDomain)}/prebid/multi/${cId}`, + data: { + bids: chunk + } + }; + }); +} + +function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + switch (idSystemProviderName) { + case 'digitrustid': + payloadRef[key] = deepAccess(userId, 'data.id'); + break; + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'parrableId': + payloadRef[key] = userId.eid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = config.getConfig('bidderTimeout'); + + const singleRequestMode = config.getConfig('twistdigital.singleRequest'); + + const requests = []; + + if (singleRequestMode) { + // banner bids are sent as a single request + const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); + if (bannerBidRequests.length > 0) { + const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); + requests.push(...singleRequests); + } + + // video bids are sent as a single request for each bid + + const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); + videoBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } else { + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } + return requests; +} + +function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const singleRequestMode = config.getConfig('twistdigital.singleRequest'); + const reqBidId = deepAccess(request, 'data.bidId'); + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach((result, i) => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + bidId, + nurl, + advertiserDomains, + metaData, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: (singleRequestMode && bidId) ? bidId : reqBidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (nurl) { + response.nurl = nurl; + } + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + + return output; + } catch (e) { + return []; + } +} + +function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + let syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + const {gppString, applicableSections} = gppConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; + + if (gppString && applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppString); + params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); + } + + if (iframeEnabled) { + syncs.push({ + type: 'iframe', + url: `https://sync.twist.win/api/sync/iframe/${params}` + }); + } + if (pixelEnabled) { + syncs.push({ + type: 'image', + url: `https://sync.twist.win/api/sync/image/${params}` + }); + } + return syncs; +} + +/** + * @param {Bid} bid + */ +function onBidWon(bid) { + if (!bid.nurl) { + return; + } + const wonBid = { + adId: bid.adId, + creativeId: bid.creativeId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + netRevenue: bid.netRevenue, + mediaType: bid.mediaType, + timeToRespond: bid.timeToRespond, + status: bid.status, + }; + const qs = formatQS(wonBid); + const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; + triggerPixel(url); +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function getUniqueDealId(key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getStorageItem(key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key)); + } catch (e) { + } + + return null; +} + +export function setStorageItem(key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon +}; + +registerBidder(spec); diff --git a/modules/twistDigitalBidAdapter.md b/modules/twistDigitalBidAdapter.md new file mode 100644 index 00000000000..8722608c5dd --- /dev/null +++ b/modules/twistDigitalBidAdapter.md @@ -0,0 +1,42 @@ +# Overview + +**Module Name:** Twist Digital Bidder Adapter + +**Module Type:** Bidder Adapter + +**Maintainer:** yoni@twist.win + + + + + + + + +# Description + +Module that connects to Twist Digital demand sources. + +# Test Parameters +```js +var adUnits = [ + { + code: 'test-ad', + sizes: [[300, 250]], + bids: [ + { + bidder: 'twistdigital', + params: { + cId: '562524b21b1c1f08117fc7f9', + pId: '59ac17c192832d0011283fe3', + bidFloor: 0.0001, + ext: { + param1: 'loremipsum', + param2: 'dolorsitamet' + } + } + } + ] + } +]; +``` diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js new file mode 100644 index 00000000000..7d263f6d4f0 --- /dev/null +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -0,0 +1,937 @@ +import {expect} from 'chai'; +import { + spec as adapter, + createDomain, + hashCode, + extractPID, + extractCID, + extractSubDomain, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId, + webSessionId +} from 'modules/twistDigitalBidAdapter.js'; +import * as utils from 'src/utils.js'; +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']; + +const SUB_DOMAIN = 'openrtb'; + +const BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': 'div-gpt-ad-12345-0', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '59db6b3b4ffaa70004f45cdc', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1, + 'ext': { + 'param1': 'loremipsum', + 'param2': 'dolorsitamet' + } + }, + 'placementCode': 'div-gpt-ad-1460505748561-0', + 'sizes': [[300, 250], [300, 600]], + 'bidderRequestId': '1fdb5ff1b6eaa7', + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'mediaTypes': [BANNER], + 'ortb2Imp': { + 'ext': { + tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'gpid': '1234567890' + } + } +}; + +const VIDEO_BID = { + 'bidId': '2d52001cabd527', + 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', + 'bidderRequestId': '12a8ae9ada9c13', + ortb2Imp: { + ext: { + tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + } + }, + 'bidRequestsCount': 4, + 'bidderRequestsCount': 3, + 'bidderWinsCount': 1, + 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', + 'params': { + 'subDomain': SUB_DOMAIN, + 'cId': '635509f7ff6642d368cb9837', + 'pId': '59ac17c192832d0011283fe3', + 'bidFloor': 0.1 + }, + 'sizes': [[545, 307]], + 'mediaTypes': { + 'video': { + 'playerSize': [[545, 307]], + 'context': 'instream', + 'mimes': [ + 'video/mp4', + 'application/javascript' + ], + 'protocols': [2, 3, 5, 6], + 'maxduration': 60, + 'minduration': 0, + 'startdelay': 0, + 'linearity': 1, + 'api': [2], + 'placement': 1 + } + } +} + +const BIDDER_REQUEST = { + 'gdprConsent': { + 'consentString': 'consent_string', + 'gdprApplies': true + }, + 'gppString': 'gpp_string', + 'gppSid': [7], + 'uspConsent': 'consent_string', + 'refererInfo': { + 'page': 'https://www.greatsite.com', + 'ref': 'https://www.somereferrer.com' + }, + 'ortb2': { + 'site': { + 'cat': ['IAB2'], + 'pagecat': ['IAB2-2'], + 'content': { + 'data': [{ + 'name': 'example.com', + 'ext': { + 'segtax': 7 + }, + 'segments': [ + {'id': 'segId1'}, + {'id': 'segId2'} + ] + }] + } + }, + 'regs': { + 'gpp': 'gpp_string', + 'gpp_sid': [7] + }, + 'device': { + 'sua': { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + } + }, + user: { + data: [ + { + ext: {segtax: 600, segclass: '1'}, + name: 'example.com', + segment: [{id: '243'}], + }, + ], + } + }, +}; + +const SERVER_RESPONSE = { + body: { + cid: 'testcid123', + results: [{ + 'ad': '', + 'bidId': '2d52001cabd527-response', + 'price': 0.8, + 'creativeId': '12610997325162499419', + 'exp': 30, + 'width': 300, + 'height': 250, + 'advertiserDomains': ['securepubads.g.doubleclick.net'], + 'cookies': [{ + 'src': 'https://sync.com', + 'type': 'iframe' + }, { + 'src': 'https://sync.com', + 'type': 'img' + }] + }] + } +}; + +const VIDEO_SERVER_RESPONSE = { + body: { + 'cid': '635509f7ff6642d368cb9837', + 'results': [{ + 'ad': '', + 'advertiserDomains': ['twistdigital.com'], + 'exp': 60, + 'width': 545, + 'height': 307, + 'mediaType': 'video', + 'creativeId': '12610997325162499419', + 'price': 2, + 'cookies': [] + }] + } +} + +const REQUEST = { + data: { + width: 300, + height: 250, + bidId: '2d52001cabd527' + } +}; + +function getTopWindowQueryParams() { + try { + const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +describe('TwistDigitalBidAdapter', function () { + describe('validtae spec', function () { + it('exists and is a function', function () { + expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.buildRequests).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); + }); + + it('exists and is a function', function () { + expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); + }); + + it('exists and is a string', function () { + expect(adapter.code).to.exist.and.to.be.a('string'); + }); + + it('exists and contains media types', function () { + expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); + expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); + }); + }); + + describe('validate bid requests', function () { + it('should require cId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + pId: 'pid' + } + }); + expect(isValid).to.be.false; + }); + + it('should require pId', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid' + } + }); + expect(isValid).to.be.false; + }); + + it('should validate correctly', function () { + const isValid = adapter.isBidRequestValid({ + params: { + cId: 'cid', + pId: 'pid' + } + }); + expect(isValid).to.be.true; + }); + }); + + describe('build requests', function () { + let sandbox; + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true, + } + }; + sandbox = sinon.sandbox.create(); + sandbox.stub(Date, 'now').returns(1000); + }); + + it('should build video request', function () { + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + config.setConfig({ + bidderTimeout: 3000 + }); + const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, + data: { + adUnitCode: '63550ad1ff6642d368cba59dh5884270560', + bidFloor: 0.1, + bidId: '2d52001cabd527', + bidderVersion: adapter.version, + cat: ['IAB2'], + pagecat: ['IAB2-2'], + cb: 1000, + gdpr: 1, + gdprConsent: 'consent_string', + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', + bidderRequestId: '12a8ae9ada9c13', + gpid: '', + prebidVersion: version, + publisherId: '59ac17c192832d0011283fe3', + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + res: `${window.top.screen.width}x${window.top.screen.height}`, + schain: VIDEO_BID.schain, + sizes: ['545x307'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + '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, + webSessionId: webSessionId, + mediaTypes: { + video: { + api: [2], + context: 'instream', + linearity: 1, + maxduration: 60, + mimes: [ + 'video/mp4', + 'application/javascript' + ], + minduration: 0, + placement: 1, + playerSize: [[545, 307]], + protocols: [2, 3, 5, 6], + startdelay: 0 + } + } + } + }) + ; + }); + + it('should build banner request for each size', function () { + config.setConfig({ + bidderTimeout: 3000 + }); + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + const requests = adapter.buildRequests([BID], BIDDER_REQUEST); + expect(requests).to.have.length(1); + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + 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 + } + }); + }); + + it('should build single banner request for multiple bids', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); + + const BID2 = utils.deepClone(BID); + BID2.bidId = '2d52001cabd528'; + BID2.adUnitCode = 'div-gpt-ad-12345-1'; + BID2.sizes = [[300, 250]]; + + const REQUEST_DATA = { + gdprConsent: 'consent_string', + gdpr: 1, + usPrivacy: 'consent_string', + gppString: 'gpp_string', + gppSid: [7], + bidRequestsCount: 4, + bidderRequestsCount: 3, + bidderWinsCount: 1, + bidderTimeout: 3000, + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + bidderRequestId: '1fdb5ff1b6eaa7', + sizes: ['300x250', '300x600'], + sua: { + 'source': 2, + 'platform': { + 'brand': 'Android', + 'version': ['8', '0', '0'] + }, + 'browsers': [ + {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, + {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, + {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} + ], + 'mobile': 1, + 'model': 'SM-G955U', + 'bitness': '64', + 'architecture': '' + }, + url: 'https%3A%2F%2Fwww.greatsite.com', + referrer: 'https://www.somereferrer.com', + cb: 1000, + bidFloor: 0.1, + bidId: '2d52001cabd527', + adUnitCode: 'div-gpt-ad-12345-0', + publisherId: '59ac17c192832d0011283fe3', + uniqueDealId: `${hashUrl}_${Date.now().toString()}`, + bidderVersion: adapter.version, + prebidVersion: version, + schain: BID.schain, + res: `${window.top.screen.width}x${window.top.screen.height}`, + mediaTypes: [BANNER], + uqs: getTopWindowQueryParams(), + 'ext.param1': 'loremipsum', + 'ext.param2': 'dolorsitamet', + isStorageAllowed: true, + 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 + }; + + const REQUEST_DATA2 = utils.deepClone(REQUEST_DATA); + REQUEST_DATA2.bidId = '2d52001cabd528'; + REQUEST_DATA2.adUnitCode = 'div-gpt-ad-12345-1'; + REQUEST_DATA2.sizes = ['300x250']; + + const requests = adapter.buildRequests([BID, BID2], BIDDER_REQUEST); + expect(requests).to.have.length(1); + + expect(requests[0]).to.deep.equal({ + method: 'POST', + url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, + data: {bids: [REQUEST_DATA, REQUEST_DATA2]} + }); + }); + + it('should return seperated requests for video and banner if singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, VIDEO_BID], BIDDER_REQUEST); + expect(requests).to.have.length(2); + }); + + it('should chunk requests if requests exceed chunkSize and singleRequest is true', function () { + config.setConfig({ + bidderTimeout: 3000, + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const requests = adapter.buildRequests([BID, BID, BID, BID], BIDDER_REQUEST); + 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(); + sandbox.restore(); + }); + }); + + describe('getUserSyncs', function () { + it('should have valid user sync with iframeEnabled', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with cid on response', function () { + const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); + expect(result).to.deep.equal([{ + type: 'iframe', + url: 'https://sync.twist.win/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + }]); + }); + + it('should have valid user sync with pixelEnabled', function () { + const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); + + expect(result).to.deep.equal([{ + 'url': 'https://sync.twist.win/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'type': 'image' + }]); + }) + }); + + describe('interpret response', function () { + it('should return empty array when there is no response', function () { + const responses = adapter.interpretResponse(null); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no ad', function () { + const responses = adapter.interpretResponse({price: 1, ad: ''}); + expect(responses).to.be.empty; + }); + + it('should return empty array when there is no price', function () { + const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); + expect(responses).to.be.empty; + }); + + it('should return an array of interpreted banner responses', function () { + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 0.8, + width: 300, + height: 250, + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 30, + ad: '', + meta: { + advertiserDomains: ['securepubads.g.doubleclick.net'] + } + }); + }); + + it('should get meta from response metaData', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].metaData = { + advertiserDomains: ['twistdigital.com'], + agencyName: 'Agency Name', + }; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses[0].meta).to.deep.equal({ + advertiserDomains: ['twistdigital.com'], + agencyName: 'Agency Name' + }); + }); + + it('should return an array of interpreted video responses', function () { + const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0]).to.deep.equal({ + requestId: '2d52001cabd527', + cpm: 2, + width: 545, + height: 307, + mediaType: 'video', + creativeId: '12610997325162499419', + currency: 'USD', + netRevenue: true, + ttl: 60, + vastXml: '', + meta: { + advertiserDomains: ['twistdigital.com'] + } + }); + }); + + it('should populate requestId from response in case of singleRequest true', function () { + config.setConfig({ + twistdigital: { + singleRequest: true, + chunkSize: 2 + } + }); + + const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].requestId).to.equal('2d52001cabd527-response'); + + config.resetConfig(); + }); + + it('should take default TTL', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + delete serverResponse.body.results[0].exp; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].ttl).to.equal(300); + }); + + it('should add nurl if exists on response', function () { + const serverResponse = utils.deepClone(SERVER_RESPONSE); + serverResponse.body.results[0].nurl = 'https://test.com/win-notice?test=123'; + const responses = adapter.interpretResponse(serverResponse, REQUEST); + expect(responses).to.have.length(1); + expect(responses[0].nurl).to.equal('https://test.com/win-notice?test=123'); + }); + }); + + describe('user id system', function () { + TEST_ID_SYSTEMS.forEach((idSystemProvider) => { + const id = Date.now().toString(); + const bid = utils.deepClone(BID); + + const userId = (function () { + switch (idSystemProvider) { + case 'lipb': + return {lipbid: id}; + case 'parrableId': + return {eid: id}; + case 'id5id': + return {uid: id}; + default: + return id; + } + })(); + + bid.userId = { + [idSystemProvider]: userId + }; + + it(`should include 'uid.${idSystemProvider}' in request params`, function () { + const requests = adapter.buildRequests([bid], BIDDER_REQUEST); + expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); + }); + }); + }); + + describe('alternate param names extractors', function () { + it('should return undefined when param not supported', function () { + const cid = extractCID({'c_id': '1'}); + const pid = extractPID({'p_id': '1'}); + const subDomain = extractSubDomain({'sub_domain': 'prebid'}); + expect(cid).to.be.undefined; + expect(pid).to.be.undefined; + expect(subDomain).to.be.undefined; + }); + + it('should return value when param supported', function () { + const cid = extractCID({'cID': '1'}); + const pid = extractPID({'Pid': '2'}); + const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + expect(cid).to.be.equal('1'); + expect(pid).to.be.equal('2'); + expect(subDomain).to.be.equal('prebid'); + }); + }); + + describe('deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + }); + + describe('unique deal id', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + const key = 'myKey'; + let uniqueDealId; + beforeEach(() => { + uniqueDealId = getUniqueDealId(key, 0); + }) + + it('should get current unique deal id', function (done) { + // waiting some time so `now` will become past + setTimeout(() => { + const current = getUniqueDealId(key); + expect(current).to.be.equal(uniqueDealId); + done(); + }, 200); + }); + + it('should get new unique deal id on expiration', function (done) { + setTimeout(() => { + const current = getUniqueDealId(key, 100); + expect(current).to.not.be.equal(uniqueDealId); + done(); + }, 200) + }); + }); + + describe('storage utils', function () { + before(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + twistdigital: { + storageAllowed: true + } + }; + }); + after(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }); + it('should get value from storage with create param', function () { + const now = Date.now(); + const clock = useFakeTimers({ + shouldAdvanceTime: true, + now + }); + setStorageItem('myKey', 2020); + const {value, created} = getStorageItem('myKey'); + expect(created).to.be.equal(now); + expect(value).to.be.equal(2020); + expect(typeof value).to.be.equal('number'); + expect(typeof created).to.be.equal('number'); + clock.restore(); + }); + + it('should get external stored value', function () { + const value = 'superman' + window.localStorage.setItem('myExternalKey', value); + const item = getStorageItem('myExternalKey'); + expect(item).to.be.equal(value); + }); + + it('should parse JSON value', function () { + const data = JSON.stringify({event: 'send'}); + const {event} = tryParseJSON(data); + expect(event).to.be.equal('send'); + }); + + it('should get original value on parse fail', function () { + const value = 21; + const parsed = tryParseJSON(value); + expect(typeof parsed).to.be.equal('number'); + expect(parsed).to.be.equal(value); + }); + }); + + describe('validate onBidWon', function () { + beforeEach(function () { + sinon.stub(utils, 'triggerPixel'); + }); + afterEach(function () { + utils.triggerPixel.restore(); + }); + + it('should call triggerPixel if nurl exists', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + nurl: 'https://test.com/win-notice?test=123', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.true; + + const url = utils.triggerPixel.args[0]; + + expect(url[0]).to.be.equal('https://test.com/win-notice?test=123&adId=2d52001cabd527&creativeId=12610997325162499419&auctionId=1fdb5ff1b6eaa7&transactionId=c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf&adUnitCode=div-gpt-ad-12345-0&cpm=0.8¤cy=USD&originalCpm=0.8&originalCurrency=USD&netRevenue=true&mediaType=banner&timeToRespond=100&status=rendered'); + }); + + it('should not call triggerPixel if nurl does not exist', function () { + const bid = { + adUnitCode: 'div-gpt-ad-12345-0', + adId: '2d52001cabd527', + auctionId: '1fdb5ff1b6eaa7', + transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + status: 'rendered', + timeToRespond: 100, + cpm: 0.8, + originalCpm: 0.8, + creativeId: '12610997325162499419', + currency: 'USD', + originalCurrency: 'USD', + height: 250, + mediaType: 'banner', + netRevenue: true, + requestId: '2d52001cabd527', + ttl: 30, + width: 300 + }; + adapter.onBidWon(bid); + expect(utils.triggerPixel.called).to.be.false; + }); + }); +}); From 3c4ebab30c514d5f20b9ceca175e20f6cae66668 Mon Sep 17 00:00:00 2001 From: Jorge Algaba Aranda Date: Thu, 9 May 2024 16:11:31 +0200 Subject: [PATCH 722/760] Telaria bid adapter typo (#11471) --- modules/telariaBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/telariaBidAdapter.md b/modules/telariaBidAdapter.md index 6a5e24e9a5e..3a6c86ec2f6 100644 --- a/modules/telariaBidAdapter.md +++ b/modules/telariaBidAdapter.md @@ -10,7 +10,7 @@ Maintainer: github@telaria.com Connects to Telaria's exchange. -Telaria bid adapter supports insteream Video. +Telaria bid adapter supports instream Video. # Test Parameters ``` From c902d6ccf58b8979bc7502a0095df1438136745c Mon Sep 17 00:00:00 2001 From: Love Sharma Date: Thu, 9 May 2024 10:23:21 -0400 Subject: [PATCH 723/760] chore: code cleanup [PB-2828] (#11468) Co-authored-by: Love Sharma --- modules/ixBidAdapter.js | 153 +--------------------- test/spec/modules/ixBidAdapter_spec.js | 174 +------------------------ 2 files changed, 8 insertions(+), 319 deletions(-) diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 248dbea0046..f56e2790ad6 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -18,15 +18,12 @@ import { } from '../src/utils.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { EVENTS } from '../src/constants.js'; import { getStorageManager } from '../src/storageManager.js'; -import * as events from '../src/events.js'; import { find } from '../src/polyfill.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { Renderer } from '../src/Renderer.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'ix'; const ALIAS_BIDDER_CODE = 'roundel'; @@ -49,17 +46,6 @@ const PRICE_TO_DOLLAR_FACTOR = { const IFRAME_USER_SYNC_URL = 'https://js-sec.indexww.com/um/ixmatch.html'; const FLOOR_SOURCE = { PBJS: 'p', IX: 'x' }; const IMG_USER_SYNC_URL = 'https://dsum.casalemedia.com/pbusermatch?origin=prebid'; -export const ERROR_CODES = { - BID_SIZE_INVALID_FORMAT: 1, - BID_SIZE_NOT_INCLUDED: 2, - PROPERTY_NOT_INCLUDED: 3, - SITE_ID_INVALID_VALUE: 4, - BID_FLOOR_INVALID_FORMAT: 5, - IX_FPD_EXCEEDS_MAX_SIZE: 6, - EXCEEDS_MAX_SIZE: 7, - PB_FPD_EXCEEDS_MAX_SIZE: 8, - VIDEO_DURATION_INVALID: 9 -}; const FIRST_PARTY_DATA = { SITE: [ 'id', 'name', 'domain', 'cat', 'sectioncat', 'pagecat', 'page', 'ref', 'search', 'mobile', @@ -110,7 +96,6 @@ const VIDEO_PARAMS_ALLOW_LIST = [ ]; const LOCAL_STORAGE_KEY = 'ixdiag'; export const LOCAL_STORAGE_FEATURE_TOGGLES_KEY = `${BIDDER_CODE}_features`; -let hasRegisteredHandler = false; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const FEATURE_TOGGLES = { // Update with list of CFTs to be requested from Exchange @@ -262,8 +247,7 @@ export function bidToVideoImp(bid) { if (imp.video.minduration > imp.video.maxduration) { logError( - `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]`, - { bidder: BIDDER_CODE, code: ERROR_CODES.VIDEO_DURATION_INVALID } + `IX Bid Adapter: video minduration [${imp.video.minduration}] cannot be greater than video maxduration [${imp.video.maxduration}]` ); return {}; } @@ -883,13 +867,6 @@ function enrichRequest(r, bidderRequest, impressions, validBidRequests, userEids r.ext.ixdiag.syncsPerBidder = config.getConfig('userSync').syncsPerBidder; } - // Get cached errors stored in LocalStorage - const cachedErrors = getCachedErrors(); - - if (!isEmpty(cachedErrors)) { - r.ext.ixdiag.err = cachedErrors; - } - // Add number of available imps to ixDiag. r.ext.ixdiag.imps = Object.keys(impressions).length; @@ -1546,104 +1523,6 @@ function createMissingBannerImp(bid, imp, newSize) { return newImp; } -/** - * @typedef {Array[message: string, err: Object]} ErrorData - * @property {string} message - The error message. - * @property {object} err - The error object. - * @property {string} err.bidder - The bidder of the error. - * @property {string} err.code - The error code. - */ - -/** - * Error Event handler that receives type and arguments in a data object. - * - * @param {ErrorData} data - */ -function storeErrorEventData(data) { - if (!storage.localStorageIsEnabled()) { - return; - } - - let currentStorage; - - try { - currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); - } catch (e) { - logWarn('ix can not read ixdiag from localStorage.'); - } - - const todayDate = new Date(); - - Object.keys(currentStorage).map((errorDate) => { - const date = new Date(errorDate); - - if (date.setDate(date.getDate() + 7) - todayDate < 0) { - delete currentStorage[errorDate]; - } - }); - - if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { - const todayString = todayDate.toISOString().slice(0, 10); - - const errorCode = data.arguments[1].code; - - if (errorCode) { - currentStorage[todayString] = currentStorage[todayString] || {}; - - if (!Number(currentStorage[todayString][errorCode])) { - currentStorage[todayString][errorCode] = 0; - } - - currentStorage[todayString][errorCode]++; - }; - } - - storage.setDataInLocalStorage(LOCAL_STORAGE_KEY, JSON.stringify(currentStorage)); -} - -/** - * Event handler for storing data into local storage. It will only store data if - * local storage premissions are avaliable - */ -function localStorageHandler(data) { - if (data.type === 'ERROR' && data.arguments && data.arguments[1] && data.arguments[1].bidder === BIDDER_CODE) { - storeErrorEventData(data); - } -} - -/** - * Get ixdiag stored in LocalStorage and format to be added to request payload - * - * @returns {Object} Object with error codes and counts - */ -function getCachedErrors() { - if (!storage.localStorageIsEnabled()) { - return; - } - - const errors = {}; - let currentStorage; - - try { - currentStorage = JSON.parse(storage.getDataFromLocalStorage(LOCAL_STORAGE_KEY) || '{}'); - } catch (e) { - logError('ix can not read ixdiag from localStorage.'); - return null; - } - - Object.keys(currentStorage).forEach((date) => { - Object.keys(currentStorage[date]).forEach((code) => { - if (typeof currentStorage[date][code] === 'number') { - errors[code] = errors[code] - ? errors[code] + currentStorage[date][code] - : currentStorage[date][code]; - } - }); - }); - - return errors; -} - /** * * Initialize IX Outstream Renderer @@ -1738,12 +1617,6 @@ export const spec = { * @return {boolean} True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!hasRegisteredHandler) { - events.on(EVENTS.AUCTION_DEBUG, localStorageHandler); - events.on(EVENTS.AD_RENDER_FAILED, localStorageHandler); - hasRegisteredHandler = true; - } - const paramsVideoRef = deepAccess(bid, 'params.video'); const paramsSize = deepAccess(bid, 'params.size'); const mediaTypeBannerSizes = deepAccess(bid, 'mediaTypes.banner.sizes'); @@ -1765,14 +1638,14 @@ export const spec = { // since there is an ix bidder level size, make sure its valid const ixSize = getFirstSize(paramsSize); if (!ixSize) { - logError('IX Bid Adapter: size has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_INVALID_FORMAT }); + logError('IX Bid Adapter: size has invalid format.'); return false; } // check if the ix bidder level size, is present in ad unit level if (!includesSize(bid.sizes, ixSize) && !(includesSize(mediaTypeVideoPlayerSize, ixSize)) && !(includesSize(mediaTypeBannerSizes, ixSize))) { - logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_SIZE_NOT_INCLUDED }); + logError('IX Bid Adapter: bid size is not included in ad unit sizes or player size.'); return false; } } @@ -1784,19 +1657,19 @@ export const spec = { if (bid.params.siteId !== undefined) { if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { - logError('IX Bid Adapter: siteId must be string or number type.', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + logError('IX Bid Adapter: siteId must be string or number type.'); return false; } if (typeof bid.params.siteId !== 'string' && isNaN(Number(bid.params.siteId))) { - logError('IX Bid Adapter: siteId must valid value', { bidder: BIDDER_CODE, code: ERROR_CODES.SITE_ID_INVALID_VALUE }); + logError('IX Bid Adapter: siteId must valid value'); return false; } } if (hasBidFloor || hasBidFloorCur) { if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { - logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.', { bidder: BIDDER_CODE, code: ERROR_CODES.BID_FLOOR_INVALID_FORMAT }); + logError('IX Bid Adapter: bidFloor / bidFloorCur parameter has invalid format.'); return false; } } @@ -1815,7 +1688,7 @@ export const spec = { if (errorList.length) { errorList.forEach((err) => { - logError(err, { bidder: BIDDER_CODE, code: ERROR_CODES.PROPERTY_NOT_INCLUDED }); + logError(err); }); return false; } @@ -1997,18 +1870,6 @@ export const spec = { } }, - /** - * Covert bid param types for S2S - * @param {Object} params bid params - * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol - * @return {Object} params bid params - */ - transformBidParams: function (params, isOpenRtb) { - return convertTypes({ - 'siteID': 'number' - }, params); - }, - /** * Determine which user syncs should occur * @param {object} syncOptions diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 7655868ffc3..88cfb93850f 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -2,7 +2,7 @@ import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { expect } from 'chai'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import { spec, storage, ERROR_CODES, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo } from '../../../modules/ixBidAdapter.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; describe('IndexexchangeAdapter', function () { @@ -4867,178 +4867,6 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('LocalStorage error codes', () => { - let TODAY = new Date().toISOString().slice(0, 10); - const key = 'ixdiag'; - - let sandbox; - let setDataInLocalStorageStub; - let getDataFromLocalStorageStub; - let removeDataFromLocalStorageStub; - let localStorageValues = {}; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - setDataInLocalStorageStub = sandbox.stub(storage, 'setDataInLocalStorage').callsFake((key, value) => localStorageValues[key] = value) - getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => localStorageValues[key]) - removeDataFromLocalStorageStub = sandbox.stub(storage, 'removeDataFromLocalStorage').callsFake((key) => delete localStorageValues[key]) - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - }); - - afterEach(() => { - setDataInLocalStorageStub.restore(); - getDataFromLocalStorageStub.restore(); - removeDataFromLocalStorageStub.restore(); - localStorageValues = {}; - sandbox.restore(); - - config.setConfig({ - ortb2: {}, - ix: {}, - }) - }); - - it('should not log error in LocalStorage when there is no logError called.', () => { - const bid = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; - expect(spec.isBidRequestValid(bid)).to.be.true; - expect(localStorageValues[key]).to.be.undefined; - }); - - it('should log ERROR_CODES.BID_SIZE_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); - }); - - it('should log ERROR_CODES.BID_SIZE_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = [407, 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_NOT_INCLUDED]: 1 } }); - }); - - it('should log ERROR_CODES.PROPERTY_NOT_INCLUDED in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video = {}; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); - }); - - it('should log ERROR_CODES.SITE_ID_INVALID_VALUE in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.siteId = false; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 } }); - }); - - it('should log ERROR_CODES.BID_FLOOR_INVALID_FORMAT in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.bidFloor = true; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_FLOOR_INVALID_FORMAT]: 1 } }); - }); - - it('should log ERROR_CODES.VIDEO_DURATION_INVALID in LocalStorage when there is logError called.', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video.minduration = 1; - bid.params.video.maxduration = 0; - - expect(spec.isBidRequestValid(bid)).to.be.true; - spec.buildRequests([bid]); - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.VIDEO_DURATION_INVALID]: 2 } }); - }); - - it('should increment errors for errorCode', () => { - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.video = {}; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 4 } }); - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.PROPERTY_NOT_INCLUDED]: 8 } }); - }); - - it('should add new errorCode to ixdiag.', () => { - let bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1 } }); - - bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.siteId = false; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ - [TODAY]: { - [ERROR_CODES.BID_SIZE_INVALID_FORMAT]: 1, - [ERROR_CODES.SITE_ID_INVALID_VALUE]: 1 - } - }); - }); - - it('should clear errors with successful response', () => { - const ixdiag = { [TODAY]: { '1': 1, '3': 8, '4': 1 } }; - setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); - - expect(JSON.parse(localStorageValues[key])).to.deep.equal(ixdiag); - - const request = DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]; - expect(spec.isBidRequestValid(request)).to.be.true; - - const data = { - id: '345', - imp: [ - { - id: '1a2b3c4e', - } - ], - ext: { - ixdiag: { - err: { - '4': 8 - } - } - } - }; - - const validBidRequest = DEFAULT_MULTIFORMAT_BANNER_VALID_BID[0]; - - spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data, validBidRequest }); - - expect(localStorageValues[key]).to.be.undefined; - }); - - it('should clear errors after 7 day expiry errorCode', () => { - const EXPIRED_DATE = '2019-12-12'; - - const ixdiag = { [EXPIRED_DATE]: { '1': 1, '3': 8, '4': 1 }, [TODAY]: { '3': 8, '4': 1 } }; - setDataInLocalStorageStub(key, JSON.stringify(ixdiag)); - - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(JSON.parse(localStorageValues[key])[EXPIRED_DATE]).to.be.undefined; - expect(JSON.parse(localStorageValues[key])).to.deep.equal({ [TODAY]: { '1': 1, '3': 8, '4': 1 } }) - }); - - it('should not save error data into localstorage if consent is not given', () => { - config.setConfig({ deviceAccess: false }); - storage.localStorageIsEnabled.restore(); // let core manage device access - const bid = utils.deepClone(DEFAULT_MULTIFORMAT_VIDEO_VALID_BID[0]); - bid.params.size = ['400', 100]; - expect(spec.isBidRequestValid(bid)).to.be.false; - expect(localStorageValues[key]).to.be.undefined; - }); - }); describe('combine imps test', function () { it('base test', function () { const imps = [ From 0a5e9de3edfb781c49adf11bbebdba391cac93ba Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 May 2024 18:46:59 +0000 Subject: [PATCH 724/760] Prebid 8.48.0 release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a373144ebd..15543c5ecc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.48.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 28ce27dce05..b642b5dff4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0-pre", + "version": "8.48.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From ef42addac923de9464a4180cd32e585e470382e9 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 9 May 2024 18:47:00 +0000 Subject: [PATCH 725/760] Increment version to 8.49.0-pre --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15543c5ecc3..5198c143442 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0", + "version": "8.49.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index b642b5dff4d..6e668a9cad9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "8.48.0", + "version": "8.49.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 37ab555c840803dde2ef4971ee53955afbdecea6 Mon Sep 17 00:00:00 2001 From: mkomorski Date: Fri, 10 May 2024 14:32:51 +0200 Subject: [PATCH 726/760] 9789 tmax/timeout set to value from bidderRequest (#11469) Co-authored-by: mkomorski Co-authored-by: Demetrio Girardi --- modules/advangelistsBidAdapter.js | 4 ++-- modules/axonixBidAdapter.js | 2 +- modules/bluebillywigBidAdapter.js | 2 +- modules/brightMountainMediaBidAdapter.js | 2 +- modules/lkqdBidAdapter.js | 2 +- modules/mgidXBidAdapter.js | 2 +- modules/oguryBidAdapter.js | 2 +- modules/rhythmoneBidAdapter.js | 2 +- modules/visxBidAdapter.js | 3 +-- test/spec/modules/advangelistsBidAdapter_spec.js | 6 +++--- test/spec/modules/bluebillywigBidAdapter_spec.js | 2 +- test/spec/modules/lkqdBidAdapter_spec.js | 2 +- test/spec/modules/mgidXBidAdapter_spec.js | 3 ++- test/spec/modules/oguryBidAdapter_spec.js | 1 + 14 files changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index 8e5be83f166..a916e07a963 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -236,7 +236,7 @@ function createVideoRequestData(bid, bidderRequest) { }, 'at': 2, 'site': {}, - 'tmax': 3000, + 'tmax': Math.min(3000, bidderRequest.timeout), 'cur': ['USD'], 'id': bid.bidId, 'imp': [], @@ -322,7 +322,7 @@ function createBannerRequestData(bid, bidderRequest) { }, 'at': 2, 'site': {}, - 'tmax': 3000, + 'tmax': Math.min(3000, bidderRequest.timeout), 'cur': ['USD'], 'id': bid.bidId, 'imp': [], diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index 87c3aff444a..b1ccef155de 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -120,7 +120,7 @@ export const spec = { prebidVersion: '$prebid.version$', screenHeight: screen.height, screenWidth: screen.width, - tmax: config.getConfig('bidderTimeout'), + tmax: bidderRequest.timeout, ua: navigator.userAgent, }; diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js index d4bde9b3f2c..0718f512cdd 100644 --- a/modules/bluebillywigBidAdapter.js +++ b/modules/bluebillywigBidAdapter.js @@ -278,7 +278,7 @@ export const spec = { const request = { id: bidderRequest.bidderRequestId, source: {tid: bidderRequest.ortb2?.source?.tid}, - tmax: BB_CONSTANTS.DEFAULT_TIMEOUT, + tmax: Math.min(BB_CONSTANTS.DEFAULT_TIMEOUT, bidderRequest.timeout), imp: imps, test: DEV_MODE ? 1 : 0, ext: { diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 6db06744c24..98b97286631 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { site: buildSite(bidderRequest), device: buildDevice(), cur: [CURRENCY], - tmax: 1000, + tmax: Math.min(1000, bidderRequest.timeout), regs: buildRegs(bidderRequest), user: {}, source: {}, diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 1dbe89f5a49..d4b1cdea0d1 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -67,7 +67,7 @@ export const spec = { }, test: 0, at: 2, - tmax: bid.params.timeout || config.getConfig('bidderTimeout') || 100, + tmax: bidderRequest.timeout, cur: ['USD'], regs: { ext: { diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index ac25a419de1..f073fb4c576 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -166,7 +166,7 @@ export const spec = { placements, coppa: config.getConfig('coppa') === true ? 1 : 0, ccpa: bidderRequest.uspConsent || undefined, - tmax: config.getConfig('bidderTimeout') + tmax: bidderRequest.timeout }; if (bidderRequest.gdprConsent) { diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 9937391f6e7..3cf78da4e3a 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -81,7 +81,7 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { function buildRequests(validBidRequests, bidderRequest) { const openRtbBidRequestBanner = { id: bidderRequest.bidderRequestId, - tmax: DEFAULT_TIMEOUT, + tmax: Math.min(DEFAULT_TIMEOUT, bidderRequest.timeout), at: 1, regs: { ext: { diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index 749ab92c0dc..3ab8b79df81 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -161,7 +161,7 @@ function RhythmOneBidAdapter() { } }, at: 1, - tmax: 1000, + tmax: Math.min(1000, bidderRequest.timeout), regs: { ext: { gdpr: deepAccess(bidderRequest, 'gdprConsent.gdprApplies') ? Boolean(bidderRequest.gdprConsent.gdprApplies & 1) : false diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index a86c958392e..beffcf5da95 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -114,8 +114,7 @@ export const spec = { } } - const bidderTimeout = Number(config.getConfig('bidderTimeout')) || timeout; - const tmax = timeout ? Math.min(bidderTimeout, timeout) : bidderTimeout; + const tmax = timeout; const source = { ext: { wrapperType: 'Prebid_js', diff --git a/test/spec/modules/advangelistsBidAdapter_spec.js b/test/spec/modules/advangelistsBidAdapter_spec.js index 143d85a1ab6..57ad2d0e898 100755 --- a/test/spec/modules/advangelistsBidAdapter_spec.js +++ b/test/spec/modules/advangelistsBidAdapter_spec.js @@ -44,19 +44,19 @@ describe('advangelistsBidAdapter', function () { describe('spec.buildRequests', function () { it('should create a POST request for each bid', function () { const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].method).to.equal('POST'); }); it('should create a POST request for each bid in video request', function () { const bidRequest = bidRequestsVid[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].method).to.equal('POST'); }); it('should have domain in request', function () { const bidRequest = bidRequests[0]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], { timeout: 1000 }); expect(requests[0].data.site.domain).length !== 0; }); }); diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js index 4b58e3507db..b6fb11c4750 100644 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ b/test/spec/modules/bluebillywigBidAdapter_spec.js @@ -298,7 +298,7 @@ describe('BlueBillywigAdapter', () => { expect(payload.id).to.exist; expect(payload.source).to.be.an('object'); expect(payload.source.tid).to.equal(validBidderRequest.ortb2.source.tid); - expect(payload.tmax).to.equal(BB_CONSTANTS.DEFAULT_TIMEOUT); + expect(payload.tmax).to.equal(3000); expect(payload.imp).to.be.an('array'); expect(payload.test).to.be.a('number'); expect(payload).to.have.nested.property('ext.prebid.targeting'); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 7fee9bf6e41..4ff69ce5e2a 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -140,7 +140,7 @@ describe('lkqdBidAdapter', () => { }); it('should not populate unspecified parameters', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, { timeout: 1000 }); const serverRequestObject = requests[0]; expect(serverRequestObject.data.device.dnt).to.be.a('undefined'); diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index e0b1e1a84e9..9efaf94c954 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -83,7 +83,8 @@ describe('MGIDXBidAdapter', function () { }, refererInfo: { referer: 'https://test.com' - } + }, + timeout: 1000 }; describe('isBidRequestValid', function () { diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index aad753571a8..ea923a18e57 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -80,6 +80,7 @@ describe('OguryBidAdapter', function () { bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, + timeout: 1000 }; describe('isBidRequestValid', function () { From b6159c056c5cdd4f7857679785ce2e07392acb36 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Mon, 13 May 2024 03:13:42 -0400 Subject: [PATCH 727/760] Various Adapters: Delete s2s transform bid params for adapters with no server equivalent (#11402) * Brightcom adapter: remove adapters (#10925) * Update adagioBidAdapter.js * Update adrelevantisBidAdapter.js * Update big-richmediaBidAdapter.js * Update craftBidAdapter.js * Update goldbachBidAdapter.js * Update winrBidAdapter.js * Update vibrantmediaBidAdapter.js * Update winrBidAdapter.js * Update goldbachBidAdapter.js * Update craftBidAdapter.js * Update adrelevantisBidAdapter.js * Revert "Brightcom adapter: remove adapters (#10925)" (#11404) This reverts commit 878f73773bc50f4c258b3c66f4bb41c6de926866. * Update goldbachBidAdapter.js * Update goldbachBidAdapter.js * Update adagioBidAdapter_spec.js * Update big-richmediaBidAdapter_spec.js * Update vibrantmediaBidAdapter_spec.js * Update vibrantmediaBidAdapter_spec.js --------- Co-authored-by: Alexandru --- modules/adagioBidAdapter.js | 14 -------- modules/adrelevantisBidAdapter.js | 26 +------------- modules/big-richmediaBidAdapter.js | 5 --- modules/craftBidAdapter.js | 22 +----------- modules/goldbachBidAdapter.js | 28 +-------------- modules/vibrantmediaBidAdapter.js | 11 ------ modules/winrBidAdapter.js | 36 +------------------ test/spec/modules/adagioBidAdapter_spec.js | 27 -------------- .../modules/big-richmediaBidAdapter_spec.js | 25 ------------- .../modules/vibrantmediaBidAdapter_spec.js | 19 +++------- 10 files changed, 8 insertions(+), 205 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 6e3c38e4e85..86f0b0e09e3 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1275,20 +1275,6 @@ export const spec = { return syncs; }, - - /** - * Handle custom logic in s2s context - * - * @param {*} params - * @param {boolean} isOrtb Is an s2s context - * @param {*} adUnit - * @param {*} bidRequests - * @returns {object} updated params - */ - transformBidParams(params, isOrtb, adUnit, bidRequests) { - // We do not have a prebid server adapter. So let's return unchanged params. - return params; - } }; initAdagio(); diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 68cd859e24e..7d18135f2e8 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -18,9 +18,7 @@ import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -187,28 +185,6 @@ export const spec = { } return bids; - }, - - transformBidParams: function(params, isOpenRtb) { - params = convertTypes({ - 'placementId': 'number', - 'keywords': transformBidderParamKeywords - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; } }; diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index ecb1724c2a1..858dad2ffde 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -112,11 +112,6 @@ export const spec = { return baseAdapter.getUserSyncs(syncOptions, responses, gdprConsent); }, - transformBidParams: function (params, isOpenRtb) { - if (!baseAdapter.transformBidParams) { return params; } - return baseAdapter.transformBidParams(params, isOpenRtb); - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index 74e732d313f..c751a18cc84 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -6,9 +6,7 @@ import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -101,24 +99,6 @@ export const spec = { } }, - transformBidParams: function(params, isOpenRtb) { - params = convertTypes({ - 'sitekey': 'string', - 'placementId': 'string', - 'keywords': transformBidderParamKeywords, - }, params); - if (isOpenRtb) { - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - return params; - }, - onBidWon: function(bid) { ajax(bid._prebidWon, null, null, { method: 'POST', diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index 9f9913b7023..df0336c6cd4 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -24,9 +24,8 @@ import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -386,31 +385,6 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': transformBidderParamKeywords, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid diff --git a/modules/vibrantmediaBidAdapter.js b/modules/vibrantmediaBidAdapter.js index 8809aae32bd..348d17d2395 100644 --- a/modules/vibrantmediaBidAdapter.js +++ b/modules/vibrantmediaBidAdapter.js @@ -97,17 +97,6 @@ export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, - /** - * Transforms the 'raw' bid params into ones that this adapter can use, prior to creating the bid request. - * - * @param {object} bidParams the params to transform. - * - * @returns {object} the bid params. - */ - transformBidParams: function(bidParams) { - return bidParams; - }, - /** * Determines whether or not the given bid request is valid. For all bid requests passed to the buildRequests * function, each will have been passed to this function and this function will have returned true. diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index cf1158474b4..6cde0412071 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -14,9 +14,8 @@ import {BANNER} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import {getANKeywordParam, transformBidderParamKeywords} from '../libraries/appnexusUtils/anKeywords.js'; +import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -312,39 +311,6 @@ export const spec = { ]; } }, - - transformBidParams: function (params, isOpenRtb) { - params = convertTypes( - { - member: 'string', - invCode: 'string', - placementId: 'number', - keywords: transformBidderParamKeywords, - publisherId: 'number', - }, - params - ); - - if (isOpenRtb) { - params.use_pmt_rule = - typeof params.usePaymentRule === 'boolean' - ? params.usePaymentRule - : false; - if (params.usePaymentRule) { - delete params.usePaymentRule; - } - - Object.keys(params).forEach((paramKey) => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, }; function formatRequest(payload, bidderRequest) { diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 13c02cc9bae..19ccb49eea1 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1465,33 +1465,6 @@ describe('Adagio bid adapter', () => { }); }); - describe('transformBidParams', function() { - it('Compute additional params in s2s mode', function() { - const adUnit = { - code: 'adunit-code', - params: { - organizationId: '1000' - } - }; - const bid01 = new BidRequestBuilder({ - 'mediaTypes': { - banner: { sizes: [[300, 250]] }, - video: { - context: 'outstream', - playerSize: [300, 250], - renderer: { - url: 'https://url.tld', - render: () => true - } - } - } - }).withParams().build(); - - const params = spec.transformBidParams({ param01: 'test' }, true, adUnit, [{ bidderCode: 'adagio', auctionId: bid01.auctionId, bids: [bid01] }]); - expect(params.param01).eq('test'); - }); - }); - describe('Adagio features when prebid in top.window', function() { it('should return all expected features when all expected bidder params are available', function() { sandbox.stub(window.top.document, 'getElementById').returns( diff --git a/test/spec/modules/big-richmediaBidAdapter_spec.js b/test/spec/modules/big-richmediaBidAdapter_spec.js index c3a9a8ef6c1..f37975c3bc1 100644 --- a/test/spec/modules/big-richmediaBidAdapter_spec.js +++ b/test/spec/modules/big-richmediaBidAdapter_spec.js @@ -271,31 +271,6 @@ describe('bigRichMediaAdapterTests', function () { }); }); - describe('transformBidParams', function() { - it('cast placementId to number', function() { - const adUnit = { - code: 'adunit-code', - params: { - placementId: '456' - } - }; - const bid = { - params: { - placementId: '456' - }, - sizes: [[300, 250]], - mediaTypes: { - banner: { sizes: [[300, 250]] } - } - }; - - const params = spec.transformBidParams({ placementId: '456' }, true, adUnit, [{ bidderCode: 'bigRichmedia', auctionId: bid.auctionId, bids: [bid] }]); - - expect(params.placement_id).to.exist; - expect(params.placement_id).to.be.a('number'); - }); - }); - describe('onBidWon', function() { it('Should not have any error', function() { const result = spec.onBidWon({}); diff --git a/test/spec/modules/vibrantmediaBidAdapter_spec.js b/test/spec/modules/vibrantmediaBidAdapter_spec.js index c6ce7d52fb3..cf9487ebf25 100644 --- a/test/spec/modules/vibrantmediaBidAdapter_spec.js +++ b/test/spec/modules/vibrantmediaBidAdapter_spec.js @@ -62,12 +62,6 @@ describe('VibrantMediaBidAdapter', function () { }); }); - describe('transformBidParams', function () { - it('transforms bid params correctly', function () { - expect(spec.transformBidParams(VALID_VIDEO_BID_PARAMS)).to.deep.equal(VALID_VIDEO_BID_PARAMS); - }); - }) - let bidRequest; beforeEach(function () { @@ -1077,13 +1071,9 @@ describe('VibrantMediaBidAdapter', function () { describe('Flow tests', function () { describe('For successive API calls to the public functions', function () { it('should succeed with one media type per bid', function () { - const transformedBannerBidParams = spec.transformBidParams(VALID_BANNER_BID_PARAMS); - const transformedVideoBidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); - const transformedNativeBidParams = spec.transformBidParams(VALID_NATIVE_BID_PARAMS); - const bannerBid = { bidder: 'vibrantmedia', - params: transformedBannerBidParams, + params: VALID_BANNER_BID_PARAMS, mediaTypes: { banner: { sizes: DEFAULT_BID_SIZES, @@ -1097,7 +1087,7 @@ describe('VibrantMediaBidAdapter', function () { }; const videoBid = { bidder: 'vibrantmedia', - params: transformedVideoBidParams, + params: VALID_VIDEO_BID_PARAMS, mediaTypes: { video: { context: OUTSTREAM, @@ -1112,7 +1102,7 @@ describe('VibrantMediaBidAdapter', function () { }; const nativeBid = { bidder: 'vibrantmedia', - params: transformedNativeBidParams, + params: VALID_NATIVE_BID_PARAMS, mediaTypes: { native: { image: { @@ -1178,10 +1168,9 @@ describe('VibrantMediaBidAdapter', function () { }); it('should succeed with multiple media types for a single bid', function () { - const bidParams = spec.transformBidParams(VALID_VIDEO_BID_PARAMS); const bid = { bidder: 'vibrantmedia', - params: bidParams, + params: VALID_VIDEO_BID_PARAMS, mediaTypes: { banner: { sizes: DEFAULT_BID_SIZES From 3a5f38618e112c059706e063329217fe83032701 Mon Sep 17 00:00:00 2001 From: Nick Llerandi Date: Mon, 13 May 2024 10:46:56 -0400 Subject: [PATCH 728/760] KRAK-4688: Adds support for PAAPI module (#34) (#11480) * adds support for paapi module * slight revision --- modules/kargoBidAdapter.js | 17 +++++++- test/spec/modules/kargoBidAdapter_spec.js | 49 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index f3b3166ccad..cafbbd982fa 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -196,6 +196,7 @@ function buildRequests(validBidRequests, bidderRequest) { function interpretResponse(response, bidRequest) { const bids = response.body; + const fledgeAuctionConfigs = []; const bidResponses = []; if (isEmpty(bids) || typeof bids !== 'object') { @@ -237,9 +238,23 @@ function interpretResponse(response, bidRequest) { } bidResponses.push(bidResponse); + + if (adUnit.auctionConfig) { + fledgeAuctionConfigs.push({ + bidId: bidID, + config: adUnit.auctionConfig + }) + } } - return bidResponses; + if (fledgeAuctionConfigs.length > 0) { + return { + bids: bidResponses, + fledgeAuctionConfigs + } + } else { + return bidResponses; + } } function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 7aa853ad902..ee89d6468a5 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1807,6 +1807,55 @@ describe('kargo adapter tests', function() { advertiserDomains: [ 'https://foo.com', 'https://bar.com' ] }); }); + + it('should return fledgeAuctionConfigs if provided in bid response', function () { + const auctionConfig = { + seller: 'https://kargo.com', + decisionLogicUrl: 'https://kargo.com/decision_logic.js', + interestGroupBuyers: ['https://some_buyer.com'], + perBuyerSignals: { + 'https://some_buyer.com': { a: 1 } + } + } + + const body = response.body; + for (const key in body) { + if (body.hasOwnProperty(key)) { + if (key % 2 !== 0) { // Add auctionConfig to every other object + body[key].auctionConfig = auctionConfig; + } + } + } + + let result = spec.interpretResponse(response, bidderRequest); + + // Test properties of bidResponses + result.bids.forEach(bid => { + expect(bid).to.have.property('requestId'); + expect(bid).to.have.property('cpm'); + expect(bid).to.have.property('width'); + expect(bid).to.have.property('height'); + expect(bid).to.have.property('ttl'); + expect(bid).to.have.property('creativeId'); + expect(bid.netRevenue).to.be.true; + expect(bid).to.have.property('meta').that.is.an('object'); + }); + + // Test properties of fledgeAuctionConfigs + expect(result.fledgeAuctionConfigs).to.have.lengthOf(3); + + const expectedBidIds = ['1', '3', '5']; // Expected bidIDs + result.fledgeAuctionConfigs.forEach(config => { + expect(config).to.have.property('bidId'); + expect(expectedBidIds).to.include(config.bidId); + + expect(config).to.have.property('config').that.is.an('object'); + expect(config.config).to.have.property('seller', 'https://kargo.com'); + expect(config.config).to.have.property('decisionLogicUrl', 'https://kargo.com/decision_logic.js'); + expect(config.config.interestGroupBuyers).to.be.an('array').that.includes('https://some_buyer.com'); + expect(config.config.perBuyerSignals).to.have.property('https://some_buyer.com').that.deep.equals({ a: 1 }); + }); + }); }); describe('getUserSyncs', function() { From 1604431acfcd64ceebb6b7fab7c02d55a24cb53a Mon Sep 17 00:00:00 2001 From: Mikael Lundin Date: Mon, 13 May 2024 20:32:22 +0200 Subject: [PATCH 729/760] Site Ext Data to kvs. (#11465) --- modules/adnuntiusBidAdapter.js | 17 +- test/spec/modules/adnuntiusBidAdapter_spec.js | 161 +++++++++++------- 2 files changed, 113 insertions(+), 65 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 9c8aa5dd5c4..060f87c0f9c 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -146,6 +146,15 @@ const storageTool = (function () { return segments } + const getKvsFromOrtb = function (ortb2) { + const siteData = deepAccess(ortb2, 'site.ext.data'); + if (siteData) { + return siteData + } else { + return null + } + } + return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; @@ -163,18 +172,19 @@ const storageTool = (function () { }); } metaInternal.segments = getSegmentsFromOrtb(ortb2); + metaInternal.kv = getKvsFromOrtb(ortb2); }, saveToStorage: function (serverData, network) { setMetaInternal(serverData, network); }, getUrlRelatedData: function () { // getting the URL information is theoretically not network-specific - const { segments, usi, voidAuIdsArray } = metaInternal; - return { segments, usi, voidAuIdsArray }; + const { segments, kv, usi, voidAuIdsArray } = metaInternal; + return { segments, kv, usi, voidAuIdsArray }; }, getPayloadRelatedData: function (network) { // getting the payload data should be network-specific - const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({...a, [entry.key]: entry.value}), {}); + const { segments, kv, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); return payloadRelatedData; } }; @@ -252,6 +262,7 @@ export const spec = { } const targeting = bid.params.targeting || {}; + if (urlRelatedMetaData.kv) targeting.kv = urlRelatedMetaData.kv; const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); if (maxDeals > 0) { diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index cad5e9ea1cf..c288bfb4f12 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -1,18 +1,18 @@ // import or require modules necessary for the test, e.g.: -import {expect} from 'chai'; // may prefer 'assert' in place of 'expect' -import {misc, spec} from 'modules/adnuntiusBidAdapter.js'; -import {newBidder} from 'src/adapters/bidderFactory.js'; -import {config} from 'src/config.js'; +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { misc, spec } from 'modules/adnuntiusBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; -import {getStorageManager} from 'src/storageManager.js'; -import {getGlobal} from '../../../src/prebidGlobal'; +import { getStorageManager } from 'src/storageManager.js'; +import { getGlobal } from '../../../src/prebidGlobal'; -describe('adnuntiusBidAdapter', function() { +describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const usi = utils.generateUUID() - const meta = [{key: 'valueless'}, {value: 'keyless'}, {key: 'voidAuIds'}, {key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp()}, {exp: misc.getUnixTimestamp(1)}]}, {key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1)}, {key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1)}, {key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp()}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius'}, {key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius'}, {key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp()}] + const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp() }, { exp: misc.getUnixTimestamp(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1) }, { key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1) }, { key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp() }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp() }] let storage; // need this to make the restore work correctly -- something to do with stubbing static prototype methods @@ -24,7 +24,7 @@ describe('adnuntiusBidAdapter', function() { storageAllowed: true } }; - storage = getStorageManager({bidderCode: 'adnuntius'}); + storage = getStorageManager({ bidderCode: 'adnuntius' }); }); beforeEach(() => { @@ -35,7 +35,7 @@ describe('adnuntiusBidAdapter', function() { getGlobal().bidderSettings = {}; }); - afterEach(function() { + afterEach(function () { config.resetConfig(); if (stub1.restore) { @@ -453,20 +453,20 @@ describe('adnuntiusBidAdapter', function() { } } - describe('inherited functions', function() { - it('exists and is a function', function() { + describe('inherited functions', function () { + it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); - describe('isBidRequestValid', function() { - it('should return true when required params found', function() { + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bidderRequests[0])).to.equal(true); }); }); - describe('buildRequests', function() { - it('Test requests', function() { + describe('buildRequests', function () { + it('Test requests', function () { stub1 = sinon.stub(URLSearchParams.prototype, 'has').callsFake(() => { return true; }); @@ -485,7 +485,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); }); - it('Test requests with no local storage', function() { + it('Test requests with no local storage', function () { storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{}])); const request = spec.buildRequests(bidderRequests, {}); expect(request.length).to.equal(1); @@ -504,8 +504,8 @@ describe('adnuntiusBidAdapter', function() { expect(request2[0].url).to.equal(ENDPOINT_URL_BASE); }); - it('Test request changes for voided au ids', function() { - storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{key: 'voidAuIds', value: [{auId: '11118b6bc', exp: misc.getUnixTimestamp(1)}, {auId: '0000000000000023', exp: misc.getUnixTimestamp(1)}]}])); + it('Test request changes for voided au ids', function () { + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp(1) }, { auId: '0000000000000023', exp: misc.getUnixTimestamp(1) }] }])); const bRequests = bidderRequests.concat([{ bidId: 'adn-11118b6bc', bidder: 'adnuntius', @@ -556,7 +556,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]},{"auId":"13","targetId":"adn-13","dimensions":[[164,140],[10,1400]]}]}'); }); - it('Test Video requests', function() { + it('Test Video requests', function () { const request = spec.buildRequests(videoBidderRequest, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -566,12 +566,12 @@ describe('adnuntiusBidAdapter', function() { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function() { + it('should pass segments if available in config', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {invalidSegment: 'invalid'}, {id: 123}, {id: ['3332']}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { invalidSegment: 'invalid' }, { id: 123 }, { id: ['3332'] }] }, { name: 'other', @@ -580,18 +580,55 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function() { + it('should pass site data ext as key values to ad server', function () { + const ortb2 = { + site: { + ext: { + data: { + '12345': 'true', + '45678': 'true' + } + } + } + }; + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(data.adUnits[0].kv).to.have.property('12345'); + expect(data.adUnits[0].kv['12345']).to.equal('true'); + expect(data.adUnits[0].kv).to.have.property('45678'); + expect(data.adUnits[0].kv['45678']).to.equal('true'); + }); + + it('should skip passing site data ext if missing', function () { + const ortb2 = { + site: { + ext: { + } + } + }; + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(data.adUnits[0]).to.not.have.property('kv'); + }); + + it('should skip segments in config if not either id or array of strings', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] }, { name: 'other', @@ -602,34 +639,34 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); }); - describe('user privacy', function() { - it('should send GDPR Consent data if gdprApplies', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); - it('should pass segments if available in config', function() { + it('should pass segments if available in config', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }] }, { name: 'other', @@ -638,18 +675,18 @@ describe('adnuntiusBidAdapter', function() { } } - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should skip segments in config if not either id or array of strings', function() { + it('should skip segments in config if not either id or array of strings', function () { const ortb2 = { user: { data: [{ name: 'adnuntius', - segment: [{id: 'segment1'}, {id: 'segment2'}, {id: 'segment3'}] + segment: [{ id: 'segment1' }, { id: 'segment2' }, { id: 'segment3' }] }, { name: 'other', @@ -660,20 +697,20 @@ describe('adnuntiusBidAdapter', function() { } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); }); - it('should user user ID if present in ortb2.user.id field', function() { + it('should user user ID if present in ortb2.user.id field', function () { const ortb2 = { user: { id: usi } }; - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {ortb2})); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); @@ -721,24 +758,24 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('user privacy', function() { - it('should send GDPR Consent data if gdprApplies', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: true, consentString: 'consentString'}}); + describe('user privacy', function () { + it('should send GDPR Consent data if gdprApplies', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function() { - let request = spec.buildRequests(bidderRequests, {gdprConsent: {gdprApplies: undefined, consentString: 'consentString'}}); + it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + let request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL); }); }); - describe('use cookie', function() { - it('should send noCookie in url if set to false.', function() { + describe('use cookie', function () { + it('should send noCookie in url if set to false.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -753,8 +790,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('validate auId', function() { - it('should fail when auId is not hexadecimal', function() { + describe('validate auId', function () { + it('should fail when auId is not hexadecimal', function () { const invalidRequest = { bidId: 'adn-000000000008b6bc', bidder: 'adnuntius', @@ -766,7 +803,7 @@ describe('adnuntiusBidAdapter', function() { expect(valid).to.equal(false); }); - it('should pass when auId is hexadecimal', function() { + it('should pass when auId is hexadecimal', function () { const invalidRequest = { bidId: 'adn-000000000008b6bc', bidder: 'adnuntius', @@ -779,8 +816,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('request deals', function() { - it('Should set max deals.', function() { + describe('request deals', function () { + it('Should set max deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'] }); @@ -797,7 +834,7 @@ describe('adnuntiusBidAdapter', function() { expect(bidderRequests[1].params).to.not.have.property('maxBids'); expect(data.adUnits[1].maxDeals).to.equal(undefined); }); - it('Should allow a maximum of 5 deals.', function() { + it('Should allow a maximum of 5 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -820,7 +857,7 @@ describe('adnuntiusBidAdapter', function() { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(5); }); - it('Should allow a minumum of 0 deals.', function() { + it('Should allow a minumum of 0 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -843,7 +880,7 @@ describe('adnuntiusBidAdapter', function() { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(undefined); }); - it('Should set max deals using bidder config.', function() { + it('Should set max deals using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -856,7 +893,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=2'); }); - it('Should allow a maximum of 5 deals when using bidder config.', function() { + it('Should allow a maximum of 5 deals when using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -869,7 +906,7 @@ describe('adnuntiusBidAdapter', function() { expect(request[0]).to.have.property('url') expect(request[0].url).to.equal(ENDPOINT_URL + '&ds=5'); }); - it('Should allow a minimum of 0 deals when using bidder config.', function() { + it('Should allow a minimum of 0 deals when using bidder config.', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -885,8 +922,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('interpretResponse', function() { - it('should return valid response when passed valid server response', function() { + describe('interpretResponse', function () { + it('should return valid response when passed valid server response', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -960,7 +997,7 @@ describe('adnuntiusBidAdapter', function() { expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); }); - it('should not process valid response when passed alt bidder that is an adndeal', function() { + it('should not process valid response when passed alt bidder that is an adndeal', function () { const altBidder = { bid: [ { @@ -978,7 +1015,7 @@ describe('adnuntiusBidAdapter', function() { serverResponse.body.adUnits[0].deals = deals; }); - it('should return valid response when passed alt bidder', function() { + it('should return valid response when passed alt bidder', function () { const altBidder = { bid: [ { @@ -1015,8 +1052,8 @@ describe('adnuntiusBidAdapter', function() { }); }); - describe('interpretVideoResponse', function() { - it('should return valid response when passed valid server response', function() { + describe('interpretVideoResponse', function () { + it('should return valid response when passed valid server response', function () { const interpretedResponse = spec.interpretResponse(serverVideoResponse, videoBidRequest); const ad = serverVideoResponse.body.adUnits[0].ads[0] const deal = serverVideoResponse.body.adUnits[0].deals[0] From 48c884c7ded1ac420b8bd125df4477cc8aa246de Mon Sep 17 00:00:00 2001 From: Chris Huie Date: Tue, 14 May 2024 11:19:30 -0600 Subject: [PATCH 730/760] Remove Email (#11484) --- modules/waardexBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/waardexBidAdapter.md b/modules/waardexBidAdapter.md index 6978281d40e..165321c2057 100644 --- a/modules/waardexBidAdapter.md +++ b/modules/waardexBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Waardex Bid Adapter Module Type: Bidder Adapter -Maintainer: info@prebid.org +Maintainer: ``` # Description From ddb66fd245265214e6333c4807046f4facc11517 Mon Sep 17 00:00:00 2001 From: Olivier Date: Tue, 14 May 2024 19:31:26 +0200 Subject: [PATCH 731/760] AdagioBidAdapter: validate `plcmt` video param () (#11487) --- modules/adagioBidAdapter.js | 7 ++++++- test/spec/modules/adagioBidAdapter_spec.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 86f0b0e09e3..ac2e578de5c 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -64,7 +64,11 @@ export const ORTB_VIDEO_PARAMS = { 'w': (value) => isInteger(value), 'h': (value) => isInteger(value), 'startdelay': (value) => isInteger(value), - 'placement': (value) => isInteger(value), + 'placement': (value) => { + logWarn(LOG_PREFIX, 'The OpenRTB video param `placement` is deprecated and should not be used anymore.'); + return isInteger(value) + }, + 'plcmt': (value) => isInteger(value), 'linearity': (value) => isInteger(value), 'skip': (value) => [1, 0].includes(value), 'skipmin': (value) => isInteger(value), @@ -964,6 +968,7 @@ export const spec = { autoFillParams(bid); + // Note: `bid.params.placement` is not related to the video param `placement`. if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { logWarn(`${LOG_PREFIX} at least one required param is missing.`); // internal.enqueue(debugData()); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 19ccb49eea1..b11c0ab4a8e 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -493,7 +493,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: 3, + plcmt: 4, protocols: [8] } }).build(); @@ -508,7 +508,7 @@ describe('Adagio bid adapter', () => { skipafter: 4, minduration: 10, maxduration: 30, - placement: 3, + plcmt: 4, protocols: [8], w: 300, h: 250 From 0612325a0db1991a62aa92304959ee0cb6e3092f Mon Sep 17 00:00:00 2001 From: Ilia Medvedev Date: Wed, 15 May 2024 01:03:05 +0400 Subject: [PATCH 732/760] AdsYield Bid Adapter: move to limelight (#11483) --- modules/admixerBidAdapter.js | 1 - modules/limelightDigitalBidAdapter.js | 2 +- test/spec/modules/admixerBidAdapter_spec.js | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index f5f0b5bf665..de7b941d614 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -10,7 +10,6 @@ const ENDPOINT_URL = 'https://inv-nets.admixer.net/prebid.1.2.aspx'; const ALIASES = [ {code: 'go2net', endpoint: 'https://ads.go2net.com.ua/prebid.1.2.aspx'}, 'adblender', - {code: 'adsyield', endpoint: 'https://ads.adsyield.com/prebid.1.2.aspx'}, {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index c816f800fda..6c589f536c2 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -32,7 +32,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll', 'iionads', 'apester'], + aliases: ['pll', 'iionads', 'apester', 'adsyield'], supportedMediaTypes: [BANNER, VIDEO], /** diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index 85538efc957..e254d2f2ff7 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -175,12 +175,6 @@ describe('AdmixerAdapter', function () { expect(request.url).to.equal('https://inv-nets.admixer.net/prebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); - it('build request for adsyield', function () { - const requestParams = requestParamsFor('adsyield'); - const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); - expect(request.url).to.equal('https://ads.adsyield.com/prebid.1.2.aspx'); - expect(request.method).to.equal('POST'); - }); it('build request for futureads', function () { const requestParams = requestParamsFor('futureads'); const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); From f47540b629f265c2ad5214162c46c1df851a25ac Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 15 May 2024 14:07:24 +0300 Subject: [PATCH 733/760] Vidazoo Bid Adapter : rector gpid value assignment (#11491) * Refactor gpid value assignment in vidazooBidAdapter. * Refactor gpid value assignment in vidazooBidAdapter. --- modules/vidazooBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index ea1555e5327..c5e35c6b138 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -83,7 +83,7 @@ function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout const ptrace = getCacheOpt(); const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); From 7a507b7601153d14731d574a9d45e6cc3f814166 Mon Sep 17 00:00:00 2001 From: Saar Amrani Date: Wed, 15 May 2024 14:15:44 +0300 Subject: [PATCH 734/760] Refactor gpid extraction logic. (#11492) --- modules/kueezRtbBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index 264592cd7d6..ba33f3ade9a 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -65,7 +65,7 @@ function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { const pId = extractPID(params); const subDomain = extractSubDomain(params); - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); if (isFn(bid.getFloor)) { const floorInfo = bid.getFloor({ From 85cee26c5f9723cac86c73493dfa952939f0663d Mon Sep 17 00:00:00 2001 From: sangarbe Date: Wed, 15 May 2024 14:13:54 +0200 Subject: [PATCH 735/760] Seedtag Bid Adapter : allows sending bcat and badv ortb2 params in request payload (#11490) * sends bcat and badv ortb2 params in request payload * adds tests for bcat and badv --- modules/seedtagBidAdapter.js | 8 +++++ test/spec/modules/seedtagBidAdapter_spec.js | 36 +++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index e17ff1301a3..284e62e70fe 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -324,6 +324,14 @@ export const spec = { payload.user.eids = validBidRequests[0].userIdAsEids } + if (bidderRequest.ortb2?.bcat) { + payload.bcat = bidderRequest.ortb2?.bcat + } + + if (bidderRequest.ortb2?.badv) { + payload.badv = bidderRequest.ortb2?.badv + } + const payloadString = JSON.stringify(payload); return { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 2012c78d239..ed3e2c9be0b 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -590,6 +590,42 @@ describe('Seedtag Adapter', function () { expect(data.user.eids).to.deep.equal(userIdAsEids); }) }); + + describe('Blocking params', function () { + it('should add bcat param to payload when bidderRequest has ortb2 bcat info', function () { + const blockedCategories = ['IAB1', 'IAB2'] + var ortb2 = { + bcat: blockedCategories + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(blockedCategories); + }); + + it('should add badv param to payload when bidderRequest has ortb2 badv info', function () { + const blockedAdvertisers = ['blocked.com'] + var ortb2 = { + badv: blockedAdvertisers + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.badv).to.deep.equal(blockedAdvertisers); + }); + + it('should not add bcat and badv params to payload when bidderRequest does not have ortb2 badv and bcat info', function () { + var ortb2 = {} + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.be.undefined; + expect(data.badv).to.be.undefined; + }); + }); }) describe('interpret response method', function () { it('should return a void array, when the server response are not correct.', function () { From 542694585269e3796d65205283783915a8cf9f70 Mon Sep 17 00:00:00 2001 From: rcheptanariu <35690143+rcheptanariu@users.noreply.github.com> Date: Wed, 15 May 2024 15:31:45 +0300 Subject: [PATCH 736/760] Invibes Bid Adapter : reading page referer and cookie handlid (#11477) * InvibesBidAdapter - added cookie and referer read * InvibesBidAdapter - unit tests * InvibesBidAdapter - tab fix * InvibesBidAdapter - null checks * InvibesBidAdapter - fix { after if --- modules/invibesBidAdapter.js | 16 ++++++-- test/spec/modules/invibesBidAdapter_spec.js | 44 +++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 2c37c0edad9..7ba2b8225b0 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -14,7 +14,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 11, + PREBID_VERSION: 12, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -40,7 +40,7 @@ export const spec = { buildRequests: buildRequest, /** * @param {*} responseObj - * @param {requestParams} bidRequests + * @param {*} requestParams * @return {Bid[]} An array of bids which */ interpretResponse: function (responseObj, requestParams) { @@ -131,7 +131,6 @@ function buildRequest(bidRequests, bidderRequest) { window.invibes.placementIds.push(bidRequest.params.placementId); - _placementIds.push(bidRequest.params.placementId); _placementIds.push(bidRequest.params.placementId); _adUnitCodes.push(bidRequest.adUnitCode); _domainId = _domainId || bidRequest.params.domainId; @@ -180,9 +179,18 @@ function buildRequest(bidRequests, bidderRequest) { isLocalStorageEnabled: storage.hasLocalStorage(), preventPageViewEvent: preventPageViewEvent, isPlacementRefresh: isPlacementRefresh, - isInfiniteScrollPage: isInfiniteScrollPage, + isInfiniteScrollPage: isInfiniteScrollPage }; + if (bidderRequest.refererInfo && bidderRequest.refererInfo.ref) { + data.pageReferrer = bidderRequest.refererInfo.ref.substring(0, 300); + } + + let hid = invibes.getCookie('handIid'); + if (hid) { + data.handIid = hid; + } + let lid = readFromLocalStorage('ivbsdid'); if (!lid) { let str = invibes.getCookie('ivbsdid'); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 056255c7738..ba2d8f5255a 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -560,6 +560,50 @@ describe('invibesBidAdapter:', function () { expect(request.data.lId).to.exist; }); + it('does not send handIid when it doesnt exist in cookie', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; + global.document.cookie = ''; + let bidderRequest = { + gdprConsent: { + vendorData: { + vendorConsents: { + 436: true + } + } + }, + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + SetBidderAccess(); + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.handIid).to.not.exist; + }); + + it('sends handIid when comes on cookie', function () { + top.window.invibes.optIn = 1; + top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; + global.document.cookie = 'handIid=abcdefghijkk'; + let bidderRequest = { + gdprConsent: { + vendorData: { + vendorConsents: { + 436: true + } + } + }, + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + SetBidderAccess(); + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.handIid).to.equal('abcdefghijkk'); + }); + it('should send purpose 1', function () { let bidderRequest = { gdprConsent: { From dd398f171c77b72801798fd1b92bd7ea9bf8c03e Mon Sep 17 00:00:00 2001 From: Dejan Grbavcic Date: Thu, 16 May 2024 12:48:05 +0200 Subject: [PATCH 737/760] Brid Bid Adapter: switching to plcmt (#11502) * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo bid adapter * TargetVideo Bid Adapter: Add GDPR/USP support * TargetVideo Bid Adapter: Add GDPR/USP support tests * TargetVideo Bid Adapter: Updating margin rule * Add Brid bid adapter * Brid adapter requested changes * BridBidAdapter: switching to plcmt --- modules/bridBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/bridBidAdapter.js b/modules/bridBidAdapter.js index e784ea517ac..f3fe1541886 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -15,7 +15,7 @@ const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; const GVLID = 934; const TIME_TO_LIVE = 300; const VIDEO_PARAMS = [ - 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'plcmt', 'playbackmethod', 'protocols', 'startdelay' ]; From 2c55a6a080c1981d74e85bb7e0d9709a620f97ec Mon Sep 17 00:00:00 2001 From: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Date: Thu, 16 May 2024 12:59:09 +0200 Subject: [PATCH 738/760] Tappx Bid Adapter : change to plcmt (#11504) * Tappx Refactor: Optimizing and adding more checkers and tests * Fix: fixed site referrer for iframes using external sites error #13231 * Adapt new placement params --------- Co-authored-by: Jordi Arnau Co-authored-by: Felipe N --- modules/tappxBidAdapter.js | 6 +++--- modules/tappxBidAdapter.md | 2 +- test/spec/modules/tappxBidAdapter_spec.js | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index f0c275acfb6..b2939bffedf 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -5,8 +5,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; -import {parseDomain} from '../src/refererDetection.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import { parseDomain } from '../src/refererDetection.js'; +import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -39,7 +39,7 @@ const VIDEO_CUSTOM_PARAMS = { 'h': DATA_TYPES.NUMBER, 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, - 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, 'skip': DATA_TYPES.NUMBER diff --git a/modules/tappxBidAdapter.md b/modules/tappxBidAdapter.md index 55f18531f28..e0d1816cf6a 100644 --- a/modules/tappxBidAdapter.md +++ b/modules/tappxBidAdapter.md @@ -80,7 +80,7 @@ Ads sizes available: [300,250], [320,50], [320,480], [480,320], [728,90], [768,1 protocols: [ 2, 3 ], // Optional battr: [ 13, 14 ], // Optional linearity: 1, // Optional - placement: 2, // Optional + plcmt: 2, // Optional minbitrate: 10, // Optional maxbitrate: 10 // Optional }, diff --git a/test/spec/modules/tappxBidAdapter_spec.js b/test/spec/modules/tappxBidAdapter_spec.js index 46fac8de1e2..1dd3f1b3c50 100644 --- a/test/spec/modules/tappxBidAdapter_spec.js +++ b/test/spec/modules/tappxBidAdapter_spec.js @@ -124,8 +124,8 @@ const c_CONSENTSTRING = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; const c_VALIDBIDREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000x179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6eFJ7otPYix179MZAzMqUWsFonu7Drm3eDDBMYtj5SPoWQnl89Upk3WTlCvEnKI9SshX0p6e', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_rEXbz6UYtYEJelYrDaZOLkh8WcF9J0ZHmEHFKZEBlLXsgP6xqXU3BCj4Ay0Z6fw_jSOaHxMHwd-voRHqFA4Q9NwAxFcVLyPWnNGZ9VbcSAPos1wupq7Xu3MIm-Bw_0vxjhZdWNy4chM9x3i', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': 'xTtLUY7GwqX2MMqSHo9RQ2YUOIBFhlASOR43I9KjvgtcrxIys3RxME96M02LTjWR', 'parrableId': {'eid': '02.YoqC9lWZh8.C8QTSiJTNgI6Pp0KCM5zZgEgwVMSsVP5W51X8cmiUHQESq9WRKB4nreqZJwsWIcNKlORhG4u25Wm6lmDOBmQ0B8hv0KP6uVQ97aouuH52zaz2ctVQTORUKkErPRPcaCJ7dKFcrNoF2i6WOR0S5Nk'}, 'pubcid': 'b1254-152f-12F5-5698-dI1eljK6C7WA', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; const c_VALIDBIDAPPREQUESTS = [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1, 'app': {'name': 'Tappx Test', 'bundle': 'com.test.tappx', 'domain': 'tappx.com', 'publisher': { 'name': 'Tappx', 'domain': 'tappx.com' }}}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}, {'source': 'intentiq.com', 'uids': [{'id': 'GIF89a\u0001\u0000\u0001\u0000�\u0000\u0000���\u0000\u0000\u0000!�\u0004\u0001\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0001\u0000\u0001\u0000\u0000\u0002\u0002D\u0001\u0000;', 'atype': 1}]}, {'source': 'crwdcntrl.net', 'uids': [{'id': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'atype': 1}]}, {'source': 'parrable.com', 'uids': [{'id': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0', 'atype': 1}]}, {'source': 'pubcid.org', 'uids': [{'id': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'atype': 1}]}, {'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}]; const c_BIDDERREQUEST_B = {'bidderCode': 'tappx', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'bidderRequestId': '1c674c14a3889c', 'bids': [{'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com\/rtb\/v2\/', 'tappxkey': 'pub-1234-android-1234', 'endpoint': 'ZZ1234PBJS', 'bidfloor': 0.005, 'test': 1}, 'userId': {'haloId': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'id5id': {'uid': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'ext': {'linkType': 0}}, 'intentIqId': 'GIF89a\u0000\u0000\u0000\u0000�\u0000\u0000���\u0000\u0000\u0000?�\u0000\u0000\u0000\u0000\u0000\u0000,\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000A\u0000\u0000;', 'lotamePanoramaId': '8003916b61a95b185690ec103bdf4945a70213e01818a5e5d8690b542730755a', 'parrableId': {'eid': '01.1617088921.7faa68d9570a50ea8e4f359e9b99ca4b7509e948a6175b3e5b0b8cbaf5b62424104ccfb0191ca79366de8368ed267b89a68e236df5f41f96f238e4301659e9023fec05e46399fb1ad0a0'}, 'pubcid': 'b7143795-852f-42f0-8864-5ecbea1ade4e', 'pubProvidedId': [{'source': 'domain.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 1, 'ext': {'stype': 'ppuid'}}]}, {'source': '3rdpartyprovided.com', 'uids': [{'id': 'value read from cookie or local storage', 'atype': 3, 'ext': {'stype': 'sha256email'}}]}]}, 'userIdAsEids': [{'source': 'audigent.com', 'uids': [{'id': '0000fgclxw05ycn0608xiyi90bwpa0c0evvlif0hv1x0i0ku88il0ntek0o0qskvir0trr70u0wqxiix0zq3u1012pa5j315ogh1618nmsj91bmt41c1elzfjf1hl5r1i1kkc2jl', 'atype': 1}]}, {'source': 'id5-sync.com', 'uids': [{'id': 'ID5@iu-PJX_OQ0d6FJjKS8kYfUpHriD_qpoXJUngedfpNva812If1fHEqHHkamLC89txVxk1i9WGqeQrTX97HFCgv9QDa1M_bkHUBsAWFm-D5r1rYrsfMFFiyqwCAEzqNbvsUZXOYCAQSjPcLxR4of22w-U9_JDRThCGRDV3Fmvc38E', 'atype': 1, 'ext': {'linkType': 0}}]}], 'ortb2Imp': {'ext': {'data': {'adserver': {'name': 'gam', 'adslot': '/19968336/header-bid-tag-0'}, 'pbadslot': '/19968336/header-bid-tag-0'}}}, 'mediaTypes': {'banner': {'sizes': [[320, 480], [320, 50]]}}, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '71c0d86b-4b47-4aff-a6da-1af0b1712439', 'sizes': [[320, 480], [320, 50]], 'bidId': '264d7969b125a5', 'bidderRequestId': '1c674c14a3889c', 'auctionId': '13a8a3a9-ed3a-4101-9435-4699ee77bb62', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}], 'auctionStart': 1617088922120, 'timeout': 700, 'refererInfo': {'page': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', 'reachedTop': true, 'isAmp': false, 'numIframes': 0, 'stack': ['http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true'], 'canonicalUrl': null}, 'gdprConsent': {'consentString': c_CONSENTSTRING, 'vendorData': {'metadata': 'BO-JeiTPABAOkAAABAENABA', 'gdprApplies': true, 'hasGlobalScope': false, 'cookieVersion': 1, 'created': '2020-12-09T09:22:09.900Z', 'lastUpdated': '2021-01-14T15:44:03.600Z', 'cmpId': 0, 'cmpVersion': 1, 'consentScreen': 0, 'consentLanguage': 'EN', 'vendorListVersion': 1, 'maxVendorId': 0, 'purposeConsents': {}, 'vendorConsents': {}}, 'gdprApplies': true, 'apiVersion': 1}, 'uspConsent': '1YCC', 'start': 1611308859099}; -const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} -const c_BIDDERREQUEST_VOutstream = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"context": "outstream","playerSize":[640, 480],"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"placement":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'placement': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +const c_BIDDERREQUEST_V = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"plcmt":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'plcmt': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} +const c_BIDDERREQUEST_VOutstream = {'method': 'POST', 'url': 'https://testing.ssp.tappx.com/rtb/v2//VZ12TESTCTV?type_cnn=prebidjs&v=0.1.10329', 'data': '{"site":{"name":"localhost","bundle":"localhost","domain":"localhost"},"user":{"ext":{}},"id":"0fecfa84-c541-49f8-8c45-76b90fddc30e","test":1,"at":1,"tmax":1000,"bidder":"tappx","imp":[{"video":{"context": "outstream","playerSize":[640, 480],"mimes":["video/mp4","application/javascript"],"minduration":3,"maxduration":30,"startdelay":5,"playbackmethod":[1,3],"api":[1,2],"protocols":[2,3],"battr":[13,14],"linearity":1,"plcmt":2,"minbitrate":10,"maxbitrate":10,"w":320,"h":250},"id":"2398241a5a860b","tagid":"localhost_typeAdBanVid_windows","secure":1,"bidfloor":0.005,"ext":{"bidder":{"tappxkey":"pub-1234-desktop-1234","endpoint":"vz34906po","host":"https://vz34906po.pub.tappx.com/rtb/","bidfloor":0.005}}}],"device":{"os":"windows","ip":"peer","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36","h":864,"w":1536,"dnt":0,"language":"en","make":"Google Inc."},"params":{"host":"tappx.com","bidfloor":0.005},"regs":{"gdpr":0,"ext":{}}}', 'bids': {'bidder': 'tappx', 'params': {'host': 'testing.ssp.tappx.com/rtb/v2/', 'tappxkey': 'pub-1234-desktop-1234', 'endpoint': 'VZ12TESTCTV', 'bidfloor': 0.005, 'test': true}, 'crumbs': {'pubcid': 'dccfe922-3823-4676-b7b2-e5ed8743154e'}, 'ortb2Imp': {'ext': {'data': {'pbadslot': 'video-ad-div'}}}, 'renderer': {'options': {'text': 'Tappx Outstream Video'}}, 'mediaTypes': {'video': {'mimes': ['video/mp4', 'application/javascript'], 'minduration': 3, 'maxduration': 30, 'startdelay': 5, 'playbackmethod': [1, 3], 'api': [1, 2], 'protocols': [2, 3], 'battr': [13, 14], 'linearity': 1, 'plcmt': 2, 'minbitrate': 10, 'maxbitrate': 10, 'w': 320, 'h': 250}}, 'adUnitCode': 'video-ad-div', 'transactionId': 'ed41c805-d14c-49c3-954d-26b98b2aa2c2', 'sizes': [[320, 250]], 'bidId': '28f49c71b13f2f', 'bidderRequestId': '1401710496dc7', 'auctionId': 'e807363f-3095-43a8-a4a6-f44196cb7318', 'src': 'client', 'bidRequestsCount': 1, 'bidderRequestsCount': 1, 'bidderWinsCount': 0}} describe('Tappx bid adapter', function () { /** @@ -323,14 +323,14 @@ describe('Tappx bid adapter', function () { * INTERPRET RESPONSE TESTS */ describe('interpretResponse', function () { - it('receive banner reponse with single placement', function () { + it('receive banner reponse with single plcmt', function () { const bids = spec.interpretResponse(c_SERVERRESPONSE_B, c_BIDDERREQUEST_B); const bid = bids[0]; expect(bid.cpm).to.exist; expect(bid.ad).to.match(/^ +``` + +# Protected Audience API (FLEDGE) + +In order to enable PAAPI auctions follow the instructions below: + +1. Add the fledgeForGpt and paapi modules to your prebid bundle. +2. Add the following configuration for the module +``` +pbjs.que.push(function() { + pbjs.setConfig({ + fledgeForGpt: { + enabled: true, + bidders: ['medianet'], + defaultForSlots: 1 + } + }); +}); +``` +For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 4a221e97444..cc1a15fd733 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -174,6 +174,37 @@ let VALID_BID_REQUEST = [{ }, 'bidRequestsCount': 1 }], + // Protected Audience API Request + VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'bidRequestsCount': 1 + }], + VALID_BID_REQUEST_WITH_USERID = [{ 'bidder': 'medianet', 'params': { @@ -875,6 +906,75 @@ let VALID_BID_REQUEST = [{ }], 'tmax': config.getConfig('bidderTimeout') }, + // Protected Audience API Valid Payload + VALID_PAYLOAD_PAAPI = { + 'site': { + 'domain': 'media.net', + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest', + 'topMostLocation': 'http://media.net/topmost', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, + 'usp_applies': false, + 'coppa_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + }, + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [ + { + 'id': '28f8f8130a583e', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ext': { + 'ae': 1, + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'display_count': 1, + 'coordinates': { + 'top_left': { + 'x': 50, + 'y': 50 + }, + 'bottom_right': { + 'x': 100, + 'y': 100 + } + }, + 'viewability': 1, + 'visibility': 1 + }, + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'domain': 'media.net', + 'isTop': true, + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest' + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'banner': [ + { + 'w': 300, + 'h': 250 + } + ], + 'tagid': 'crid' + } + ], + 'tmax': 3000 + }, VALID_VIDEO_BID_REQUEST = [{ 'bidder': 'medianet', @@ -1104,6 +1204,126 @@ let VALID_BID_REQUEST = [{ } } }, + // Protected Audience API Response + SERVER_RESPONSE_PAAPI = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'paApiAuctionConfigs': [ + { + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + // Protected Audience API OpenRTB Response + SERVER_RESPONSE_PAAPI_ORTB = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'igi': [{ + 'igs': [ + { + 'impid': '28f8f8130a583e', + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + }], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID = { body: { 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', @@ -1547,6 +1767,19 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERID); }); + it('should have valid payload when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAAPI); + }); + + it('should send whatever is set in ortb2imp.ext.ae in all bid requests when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let data = JSON.parse(bidReq.data); + expect(data).to.deep.equal(VALID_PAYLOAD_PAAPI); + expect(data.imp[0].ext).to.have.property('ae'); + expect(data.imp[0].ext.ae).to.equal(1); + }); + describe('build requests: when page meta-data is available', () => { beforeEach(() => { spec.clearMnData(); @@ -1721,6 +1954,32 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); expect(bids).to.deep.equal(validBids); }); + + it('should return fledgeAuctionConfigs if PAAPI response is received', function() { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); + }); + + it('should return fledgeAuctionConfigs if openRTB PAAPI response received', function () { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('fledgeAuctionConfigs'); + expect(response.fledgeAuctionConfigs[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) + }); + + it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); + + it('should have the correlation between fledgeAuctionConfigs[0].bidId and bidreq.imp[0].id for openRTB response', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, fledgeEnabled: true}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(bidRes.fledgeAuctionConfigs[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); }); describe('onTimeout', function () { From b4d6a8280416a42a9a81c48986e7ef231b7396f0 Mon Sep 17 00:00:00 2001 From: asurovenko-zeta <80847074+asurovenko-zeta@users.noreply.github.com> Date: Tue, 21 May 2024 17:55:54 +0200 Subject: [PATCH 756/760] ZetaGlobalSsp Analytics Adapter: refactoring (#11479) * ZetaGlobalSsp Analytics Adapter: refactoring * test * improve test * - * BidTimeout event * bidTimeoutHandler * - * ortb2 to only device --------- Co-authored-by: Surovenko Alexey Co-authored-by: Alexey Surovenko --- modules/zeta_global_sspAnalyticsAdapter.js | 151 ++++++------------ .../zeta_global_sspAnalyticsAdapter_spec.js | 116 ++++++++------ 2 files changed, 116 insertions(+), 151 deletions(-) diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index f0933d2f62f..2ba119b4d35 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import {logInfo, logError} from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import {logError} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -10,13 +10,9 @@ const ADAPTER_CODE = 'zeta_global_ssp'; const BASE_URL = 'https://ssp.disqus.com/prebid/event'; const LOG_PREFIX = 'ZetaGlobalSsp-Analytics: '; -const cache = { - auctions: {} -}; - /// /////////// VARIABLES //////////////////////////////////// -let publisherId; // int +let zetaParams; /// /////////// HELPER FUNCTIONS ///////////////////////////// @@ -28,154 +24,96 @@ function sendEvent(eventType, event) { ); } -function getZetaParams(event) { - if (event.adUnits) { - for (const i in event.adUnits) { - const unit = event.adUnits[i]; - if (unit.bids) { - for (const j in unit.bids) { - const bid = unit.bids[j]; - if (bid.bidder === ADAPTER_CODE && bid.params) { - return bid.params; - } - } - } - } - } - return null; -} - /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { - let eventType = EVENTS.AD_RENDER_SUCCEEDED - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - adId: args.adId, + zetaParams: zetaParams, + domain: args.doc?.location?.host, + page: args.doc?.location?.host + args.doc?.location?.pathname, bid: { adId: args.bid?.adId, - auctionId: args.bid?.auctionId, - adUnitCode: args.bid?.adUnitCode, - bidId: args.bid?.bidId, requestId: args.bid?.requestId, - bidderCode: args.bid?.bidderCode, - mediaTypes: args.bid?.mediaTypes, - sizes: args.bid?.sizes, - adserverTargeting: args.bid?.adserverTargeting, - cpm: args.bid?.cpm, + auctionId: args.bid?.auctionId, creativeId: args.bid?.creativeId, + bidder: args.bid?.bidderCode, mediaType: args.bid?.mediaType, - renderer: args.bid?.renderer, size: args.bid?.size, + adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, - params: args.bid?.params - }, - doc: { - location: args.doc?.location + cpm: args.bid?.cpm } } - - // set zetaParams from cache - if (event.bid && event.bid.auctionId) { - const zetaParams = cache.auctions[event.bid.auctionId]; - if (zetaParams) { - event.bid.params = [ zetaParams ]; - } - } - - sendEvent(eventType, event); + sendEvent(EVENTS.AD_RENDER_SUCCEEDED, event); } function auctionEndHandler(args) { - let eventType = EVENTS.AUCTION_END; - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - auctionId: args.auctionId, - adUnits: args.adUnits, + zetaParams: zetaParams, bidderRequests: args.bidderRequests?.map(br => ({ bidderCode: br?.bidderCode, - refererInfo: br?.refererInfo, + domain: br?.refererInfo?.domain, + page: br?.refererInfo?.page, bids: br?.bids?.map(b => ({ - adUnitCode: b?.adUnitCode, - auctionId: b?.auctionId, bidId: b?.bidId, - requestId: b?.requestId, - bidderCode: b?.bidderCode, - mediaTypes: b?.mediaTypes, - sizes: b?.sizes, + auctionId: b?.auctionId, bidder: b?.bidder, - params: b?.params + mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), + size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s) })) })), bidsReceived: args.bidsReceived?.map(br => ({ adId: br?.adId, - adserverTargeting: { - hb_adomain: br?.adserverTargeting?.hb_adomain - }, - cpm: br?.cpm, + requestId: br?.requestId, creativeId: br?.creativeId, + bidder: br?.bidder, mediaType: br?.mediaType, - renderer: br?.renderer, size: br?.size, + adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, - adUnitCode: br?.adUnitCode, - auctionId: br?.auctionId, - bidId: br?.bidId, - requestId: br?.requestId, - bidderCode: br?.bidderCode, - mediaTypes: br?.mediaTypes, - sizes: br?.sizes, - bidder: br?.bidder, - params: br?.params + cpm: br?.cpm })) } + sendEvent(EVENTS.AUCTION_END, event); +} - // save zetaParams to cache - const zetaParams = getZetaParams(event); - if (zetaParams && event.auctionId) { - cache.auctions[event.auctionId] = zetaParams; +function bidTimeoutHandler(args) { + const event = { + zetaParams: zetaParams, + timeouts: args.map(t => ({ + bidId: t?.bidId, + auctionId: t?.auctionId, + bidder: t?.bidder, + mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), + size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + timeout: t?.timeout, + device: t?.ortb2?.device + })) } - - sendEvent(eventType, event); + sendEvent(EVENTS.BID_TIMEOUT, event); } /// /////////// ADAPTER DEFINITION /////////////////////////// -let baseAdapter = adapter({ analyticsType: 'endpoint' }); +let baseAdapter = adapter({analyticsType: 'endpoint'}); let zetaAdapter = Object.assign({}, baseAdapter, { enableAnalytics(config = {}) { - let error = false; - - if (typeof config.options === 'object') { - if (config.options.sid) { - publisherId = Number(config.options.sid); - } + if (config.options && config.options.sid) { + zetaParams = config.options; + baseAdapter.enableAnalytics.call(this, config); } else { logError(LOG_PREFIX + 'Config not found'); - error = true; - } - - if (!publisherId) { - logError(LOG_PREFIX + 'Missing sid (publisher id)'); - error = true; - } - - if (error) { logError(LOG_PREFIX + 'Analytics is disabled due to error(s)'); - } else { - baseAdapter.enableAnalytics.call(this, config); } }, disableAnalytics() { - publisherId = undefined; + zetaParams = undefined; baseAdapter.disableAnalytics.apply(this, arguments); }, - track({ eventType, args }) { + track({eventType, args}) { switch (eventType) { case EVENTS.AD_RENDER_SUCCEEDED: adRenderSucceededHandler(args); @@ -183,6 +121,9 @@ let zetaAdapter = Object.assign({}, baseAdapter, { case EVENTS.AUCTION_END: auctionEndHandler(args); break; + case EVENTS.BID_TIMEOUT: + bidTimeoutHandler(args); + break; } } }); diff --git a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js index fa4b50d7693..604bc780d6b 100644 --- a/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js +++ b/test/spec/modules/zeta_global_sspAnalyticsAdapter_spec.js @@ -1,6 +1,6 @@ import zetaAnalyticsAdapter from 'modules/zeta_global_sspAnalyticsAdapter.js'; import {config} from 'src/config'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {server} from '../../mocks/xhr.js'; import {logError} from '../../../src/utils'; @@ -113,6 +113,8 @@ const SAMPLE_EVENTS = { 'auctionStart': 1638441234544, 'timeout': 400, 'refererInfo': { + 'page': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'domain': 'test-zeta-ssp.net:63342', 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'reachedTop': true, 'isAmp': false, @@ -172,6 +174,8 @@ const SAMPLE_EVENTS = { 'auctionStart': 1638441234544, 'timeout': 400, 'refererInfo': { + 'page': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + 'domain': 'test-zeta-ssp.net:63342', 'referer': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'reachedTop': true, 'isAmp': false, @@ -280,7 +284,7 @@ const SAMPLE_EVENTS = { 'location': { 'href': 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', 'protocol': 'http:', - 'host': 'localhost:63342', + 'host': 'test-zeta-ssp.net', 'hostname': 'localhost', 'port': '63342', 'pathname': '/zeta-ssp/ssp/_dev/examples/page_banner.html', @@ -322,12 +326,6 @@ const SAMPLE_EVENTS = { 'bidder': 'zeta_global_ssp', 'adUnitCode': '/19968336/header-bid-tag-0', 'timeToRespond': 123, - 'pbLg': '2.00', - 'pbMg': '2.20', - 'pbHg': '2.25', - 'pbAg': '2.25', - 'pbDg': '2.25', - 'pbCg': '', 'size': '480x320', 'adserverTargeting': { 'hb_bidder': 'zeta_global_ssp', @@ -349,11 +347,11 @@ const SAMPLE_EVENTS = { } } -describe('Zeta Global SSP Analytics Adapter', function() { +describe('Zeta Global SSP Analytics Adapter', function () { let sandbox; let requests; - beforeEach(function() { + beforeEach(function () { sandbox = sinon.sandbox.create(); requests = server.requests; sandbox.stub(events, 'getEvents').returns([]); @@ -372,11 +370,15 @@ describe('Zeta Global SSP Analytics Adapter', function() { expect(utils.logError.called).to.equal(true); }); - describe('handle events', function() { - beforeEach(function() { + describe('handle events', function () { + beforeEach(function () { zetaAnalyticsAdapter.enableAnalytics({ options: { - sid: 111 + sid: 111, + tags: { + position: 'top', + shortname: 'name' + } } }); }); @@ -385,21 +387,7 @@ describe('Zeta Global SSP Analytics Adapter', function() { zetaAnalyticsAdapter.disableAnalytics(); }); - it('Move ZetaParams through analytics events', function() { - this.timeout(3000); - - events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); - events.emit(EVENTS.AD_RENDER_SUCCEEDED, SAMPLE_EVENTS.AD_RENDER_SUCCEEDED); - - expect(requests.length).to.equal(2); - const auctionEnd = JSON.parse(requests[0].requestBody); - const auctionSucceeded = JSON.parse(requests[1].requestBody); - - expect(auctionSucceeded.bid.params[0]).to.be.deep.equal(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].params); - expect(SAMPLE_EVENTS.AUCTION_END.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); - }); - - it('Keep only needed fields', function() { + it('Handle events', function () { this.timeout(3000); events.emit(EVENTS.AUCTION_END, SAMPLE_EVENTS.AUCTION_END); @@ -409,24 +397,60 @@ describe('Zeta Global SSP Analytics Adapter', function() { const auctionEnd = JSON.parse(requests[0].requestBody); const auctionSucceeded = JSON.parse(requests[1].requestBody); - expect(auctionEnd.adUnitCodes).to.be.undefined; - expect(auctionEnd.adUnits[0].bids[0].bidder).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.auctionEnd).to.be.undefined; - expect(auctionEnd.auctionId).to.be.equal('75e394d9'); - expect(auctionEnd.bidderRequests[0].bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.bidderRequests[0].bids[0].bidId).to.be.equal('206be9a13236af'); - expect(auctionEnd.bidderRequests[0].bids[0].adUnitCode).to.be.equal('/19968336/header-bid-tag-0'); - expect(auctionEnd.bidsReceived[0].bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionEnd.bidsReceived[0].adserverTargeting.hb_adomain).to.be.equal('example.adomain'); - expect(auctionEnd.bidsReceived[0].auctionId).to.be.equal('75e394d9'); - - expect(auctionSucceeded.adId).to.be.equal('5759bb3ef7be1e8'); - expect(auctionSucceeded.bid.auctionId).to.be.equal('75e394d9'); - expect(auctionSucceeded.bid.requestId).to.be.equal('206be9a13236af'); - expect(auctionSucceeded.bid.bidderCode).to.be.equal('zeta_global_ssp'); - expect(auctionSucceeded.bid.creativeId).to.be.equal('456456456'); - expect(auctionSucceeded.bid.size).to.be.equal('480x320'); - expect(auctionSucceeded.doc.location.hostname).to.be.equal('localhost'); + expect(auctionEnd).to.be.deep.equal({ + zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, + bidderRequests: [{ + bidderCode: 'zeta_global_ssp', + domain: 'test-zeta-ssp.net:63342', + page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + bids: [{ + bidId: '206be9a13236af', + auctionId: '75e394d9', + bidder: 'zeta_global_ssp', + mediaType: 'BANNER', + size: '300x250' + }] + }, { + bidderCode: 'appnexus', + domain: 'test-zeta-ssp.net:63342', + page: 'http://test-zeta-ssp.net:63342/zeta-ssp/ssp/_dev/examples/page_banner.html', + bids: [{ + bidId: '41badc0e164c758', + auctionId: '75e394d9', + bidder: 'appnexus', + mediaType: 'BANNER', + size: '300x250' + }] + }], + bidsReceived: [{ + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + }] + }); + expect(auctionSucceeded).to.be.deep.equal({ + zetaParams: {sid: 111, tags: {position: 'top', shortname: 'name'}}, + domain: 'test-zeta-ssp.net', + page: 'test-zeta-ssp.net/zeta-ssp/ssp/_dev/examples/page_banner.html', + bid: { + adId: '5759bb3ef7be1e8', + requestId: '206be9a13236af', + auctionId: '75e394d9', + creativeId: '456456456', + bidder: 'zeta_global_ssp', + mediaType: 'banner', + size: '480x320', + adomain: 'example.adomain', + timeToRespond: 123, + cpm: 2.258302852806723 + } + }); }); }); }); From 39787dc1c67c4ccbe2c2aaf0b4be69102e7b2c69 Mon Sep 17 00:00:00 2001 From: xwang202 <57196235+xwang202@users.noreply.github.com> Date: Wed, 22 May 2024 00:09:21 +0800 Subject: [PATCH 757/760] FreeWheel Bid Adapter: remove .innerText for PrebidJS 9.0 (#11532) * freewheel add schain in the request * FreeWheel-SSP-Adapter: remove the innerText for 9.0 release * update test --- modules/freewheel-sspBidAdapter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index e11aa3f8fb7..0ac848bf62a 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -76,7 +76,7 @@ function getPricing(xmlNode) { var priceNode = pricingExtNode.querySelector('Price'); princingData = { currency: priceNode.getAttribute('currency'), - price: priceNode.textContent || priceNode.innerText + price: priceNode.textContent }; } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); @@ -110,7 +110,7 @@ function getAdvertiserDomain(xmlNode) { // Currently we only return one Domain if (brandExtNode) { var domainNode = brandExtNode.querySelector('Domain'); - domain.push(domainNode.textContent || domainNode.innerText); + domain.push(domainNode.textContent); } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing StickyBrand extension.'); } From 8590378e9e46c0f159bd38f26723fa3f8b4823e0 Mon Sep 17 00:00:00 2001 From: balajimediafuse <87535823+balajimediafuse@users.noreply.github.com> Date: Tue, 21 May 2024 22:03:07 +0530 Subject: [PATCH 758/760] Mediafuse Bid Adapter : remove transformBidParams function (#11534) * removed transformBidParams function * fix lint issues --------- Co-authored-by: Chris Huie --- modules/mediafuseBidAdapter.js | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 5e31f60d3b5..d969314f406 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -29,11 +29,9 @@ import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; import { getANKewyordParamFromMaps, - getANKeywordParam, - transformBidderParamKeywords + getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -351,31 +349,6 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': transformBidderParamKeywords, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid From 040dc1c49d8e0690440a4760f9e01c3a0b64dadf Mon Sep 17 00:00:00 2001 From: decemberWP <155962474+decemberWP@users.noreply.github.com> Date: Wed, 22 May 2024 03:19:34 +0200 Subject: [PATCH 759/760] cee Id System : initial ID module release (#11510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tests for sspBC adapter Update tests for sspBC adapter: - change userSync test (due to tcf param appended in v4.6) - add tests for onBidWon and onTimeout * [sspbc-adapter] 5.3 updates: content-type for notifications * [sspbc-adapter] pass CTA to native bid * [sspbc-5.3] keep pbsize for detected adunits * [maintenance] - remove old test for sspBc bid adaptor * [sspbc-5.3] increment adaptor ver * [sspbc-adapter] maintenance update to sspBCBidAdapter * remove yarn.lock * Delete package-lock.json * remove package-lock.jsonfrom pull request * [sspbc-adapter] send pageViewId in request * [sspbc-adapter] update pageViewId test * [sspbc-adapter] add viewabiility tracker to native ads * [sspbc-adapter] add support for bid.admNative property * [sspbc-adapter] ensure that placement id length is always 3 (improves matching response to request) * [sspbc-adapter] read publisher id and custom ad label, then send them to banner creative * [sspbc-adapter] adlabel and pubid are set as empty strings, if not present in bid response * [sspbc-adapter] jstracker data fix * [sspbc-adapter] jstracker data fix * [sspbc-adapter] send tagid in notifications * [sspbc-adapter] add gvlid to spec; prepare getUserSyncs for iframe + image sync * update remote repo * cleanup of grupawp/prebid master branch * update sspBC adapter to v 5.9 * update tests for sspBC bid adapter * [sspbc-adapter] add support for topicsFPD module * [sspbc-adapter] change topic segment ids to int * add pirIdSystem * pirIdSystem * piridSystem - preCR * fix after CR * name change --------- Co-authored-by: wojciech-bialy-wpm <67895844+wojciech-bialy-wpm@users.noreply.github.com> Co-authored-by: Wojciech Biały Co-authored-by: Wojciech Biały --- modules/ceeIdSystem.js | 62 +++++++++++++++++++++ modules/ceeIdSystem.md | 27 ++++++++++ test/spec/modules/ceeIdSystem_spec.js | 77 +++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 modules/ceeIdSystem.js create mode 100644 modules/ceeIdSystem.md create mode 100644 test/spec/modules/ceeIdSystem_spec.js diff --git a/modules/ceeIdSystem.js b/modules/ceeIdSystem.js new file mode 100644 index 00000000000..9c8f1409fd3 --- /dev/null +++ b/modules/ceeIdSystem.js @@ -0,0 +1,62 @@ +/** + * This module adds ceeId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/ceeId + * @requires module:modules/userId + */ + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { submodule } from '../src/hook.js'; +import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomain/index.js'; + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'ceeId'; +const ID_TOKEN = 'WPxid'; +export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); + +/** + * Reads the ID token from local storage or cookies. + * @returns {string|undefined} The ID token, or undefined if not found. + */ +export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); + +/** @type {Submodule} */ +export const ceeIdSubmodule = { + name: MODULE_NAME, + gvlid: 676, + + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {string} value + * @returns {(Object|undefined)} + */ + decode(value) { + return typeof value === 'string' ? { 'ceeId': value } : undefined; + }, + + /** + * performs action to obtain id and return a value + * @function + * @returns {(IdResponse|undefined)} + */ + getId() { + const ceeIdToken = readId(); + + return ceeIdToken ? { id: ceeIdToken } : undefined; + }, + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), + eids: { + 'ceeId': { + source: 'ceeid.eu', + atype: 1 + }, + }, +}; + +submodule('userId', ceeIdSubmodule); diff --git a/modules/ceeIdSystem.md b/modules/ceeIdSystem.md new file mode 100644 index 00000000000..811efe08069 --- /dev/null +++ b/modules/ceeIdSystem.md @@ -0,0 +1,27 @@ +# Overview + +Module Name: ceeIdSystem +Module Type: UserID Module +Maintainer: pawel.grudzien@grupawp.pl + +# Description + +User identification system for WPM + +### Prebid Params example + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360 + } + }] + } +}); +``` diff --git a/test/spec/modules/ceeIdSystem_spec.js b/test/spec/modules/ceeIdSystem_spec.js new file mode 100644 index 00000000000..049e596ad15 --- /dev/null +++ b/test/spec/modules/ceeIdSystem_spec.js @@ -0,0 +1,77 @@ +import { ceeIdSubmodule, storage, readId } from 'modules/ceeIdSystem.js'; +import sinon from 'sinon'; + +describe('ceeIdSystem', () => { + let sandbox; + let getCookieStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId', () => { + it('should return an object with id when ceeIdToken is found', () => { + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(); + + expect(result).to.deep.equal({ id: 'testToken' }); + }); + + it('should return undefined when ceeIdToken is not found', () => { + const result = ceeIdSubmodule.getId(); + + expect(result).to.be.undefined; + }); + }); + + describe('decode', () => { + it('should return an object with ceeId when value is a string', () => { + const result = ceeIdSubmodule.decode('testId'); + + expect(result).to.deep.equal({ ceeId: 'testId' }); + }); + + it('should return undefined when value is not a string', () => { + const result = ceeIdSubmodule.decode({}); + + expect(result).to.be.undefined; + }); + }); + + describe('readId', () => { + it('should return data from local storage when it exists', () => { + getDataFromLocalStorageStub.returns('local_storage_data'); + + const result = readId(); + + expect(result).to.equal('local_storage_data'); + }); + + it('should return data from cookie when local storage data does not exist', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns('cookie_data'); + + const result = readId(); + + expect(result).to.equal('cookie_data'); + }); + + it('should return null when neither local storage data nor cookie data exists', () => { + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns(null); + + const result = readId(); + + expect(result).to.be.null; + }); + }); +}); From adb339981bdbac7a0fa12913ca6005b2502dc6a4 Mon Sep 17 00:00:00 2001 From: Omer Dotan <54346241+omerDotan@users.noreply.github.com> Date: Wed, 22 May 2024 09:42:50 +0300 Subject: [PATCH 760/760] Browsi RTD : add split key (#11445) * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * real time data module, browsi sub module for real time data, new hook bidsBackCallback, fix for config unsubscribe * change timeout&primary ad server only to auctionDelay update docs * support multiple providers * change promise to callbacks configure submodule on submodules.json * bug fixes * use Prebid ajax * tests fix * browsi real time data provider improvements * browsi-rtd-kv * browsi-rtd-kv (lint) * unit tests * lint fix --- modules/browsiRtdProvider.js | 13 ++++++++ test/spec/modules/browsiRtdProvider_spec.js | 33 ++++++++++++++++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 01a38c63b69..46393b37a05 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,6 +13,7 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName + * @property {?string} splitKey */ import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} from '../src/utils.js'; @@ -33,6 +34,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'browsi'; const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); +const RANDOM = Math.floor(Math.random() * 10) + 1; /** @type {ModuleParams} */ let _moduleParams = {}; @@ -58,12 +60,22 @@ export function addBrowsiTag(data) { script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) + script.brwRandom = RANDOM; if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } return script; } +export function setKeyValue(key) { + if (!key || typeof key !== 'string') return false; + window.googletag = window.googletag || {cmd: []}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, RANDOM.toString()); + }); +} + export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -380,6 +392,7 @@ function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); + setKeyValue(_moduleParams.splitKey); } else { logError('missing params for Browsi provider'); } diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 5fcc78f4322..92a08c8fbef 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,9 +1,9 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; -import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' import * as events from '../../../src/events'; import * as sinon from 'sinon'; import {sendPageviewEvent} from '../../../modules/browsiRtdProvider.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; describe('browsi Real time data sub module', function () { const conf = { @@ -55,7 +55,7 @@ describe('browsi Real time data sub module', function () { }); it('should match placement with ad unit', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true @@ -69,7 +69,7 @@ describe('browsi Real time data sub module', function () { }); it('should return correct macro values', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); slot.setTargeting('test', ['test', 'value']); // slot getTargeting doesn't act like GPT so we can't expect real value @@ -90,7 +90,7 @@ describe('browsi Real time data sub module', function () { }); it('should return prediction from server', function () { - makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + mockGpt.makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { p: {'hasPrediction': {ps: {0: 0.234}}}, kn: 'bv', @@ -266,4 +266,29 @@ describe('browsi Real time data sub module', function () { expect(eventsEmitSpy.callCount).to.equal(0); }) }) + + describe('set targeting - invalid params', function () { + it('should return false if key is undefined', function () { + expect(browsiRTD.setKeyValue()).to.equal(false); + }) + it('should return false if key is not string', function () { + expect(browsiRTD.setKeyValue(1)).to.equal(false); + }) + }) + describe('set targeting - valid params', function () { + let slot; + const splitKey = 'splitTest'; + before(() => { + mockGpt.reset(); + window.googletag.pubads().clearTargeting(); + slot = mockGpt.makeSlot({code: '/123/split', divId: 'split'}); + browsiRTD.setKeyValue(splitKey); + window.googletag.cmd.forEach(cmd => cmd()); + }) + it('should place numeric key value on all slots', function () { + const targetingValue = window.googletag.pubads().getTargeting(splitKey); + expect(targetingValue).to.be.an('array').that.is.not.empty; + expect(targetingValue[0]).to.be.a('string'); + }) + }) });