From 645abee2c446eca70d22ebd37c1ef76995d999c7 Mon Sep 17 00:00:00 2001 From: Justas Pupelis Date: Thu, 5 May 2022 23:36:20 +0300 Subject: [PATCH 01/17] Adf adapter: add coppa signal support (#8375) * Coppa signal support in adf adapter * Update Co-authored-by: Justas Pupelis --- modules/adfBidAdapter.js | 5 +++++ test/spec/modules/adfBidAdapter_spec.js | 27 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index f0425a174ff..0b9c72a2cee 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -206,6 +206,11 @@ export const spec = { request.is_debug = !!test; request.test = 1; } + + if (config.getConfig('coppa')) { + deepSetValue(request, 'regs.coppa', 1); + } + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index ed096e7189d..25f7fda4419 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -178,6 +178,33 @@ describe('Adf adapter', function () { assert.equal(request.source.fd, 1); }); + it('should not set coppa when coppa is not provided or is set to false', function () { + config.setConfig({ + }); + 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.regs.coppa, undefined); + + config.setConfig({ + coppa: false + }); + request = JSON.parse(spec.buildRequests(validBidRequests, bidderRequest).data); + + assert.equal(request.regs.coppa, undefined); + }); + + it('should set coppa to 1 when coppa is provided with value true', function () { + config.setConfig({ + coppa: true + }); + let validBidRequests = [{ bidId: 'bidId', params: { test: 1 } }]; + let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); + + assert.equal(request.regs.coppa, 1); + }); + it('should send info about device', function () { config.setConfig({ device: { w: 100, h: 100 } From 2697d17bb9340b7eb478bc8cabd903c2d4938677 Mon Sep 17 00:00:00 2001 From: BizzClick <73241175+BizzClick@users.noreply.github.com> Date: Thu, 5 May 2022 23:37:51 +0300 Subject: [PATCH 02/17] bizzclickAdapter refactiring, remove privacy settings duplication (#8377) --- modules/bizzclickBidAdapter.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/modules/bizzclickBidAdapter.js b/modules/bizzclickBidAdapter.js index 6223626834d..a798671cbaf 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/bizzclickBidAdapter.js @@ -95,6 +95,7 @@ export const spec = { }, regs: { coppa: config.getConfig('coppa') === true ? 1 : 0, + ext: {} }, user: { ext: {} @@ -106,25 +107,15 @@ export const spec = { imp: [impObject], }; - if (bidderRequest && bidderRequest.uspConsent) { - data.regs.ext.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - data.regs.ext.gdpr = gdprApplies ? 1 : 0; - data.user.ext.consent = consentString; - } - - if (bidRequest.schain) { - deepSetValue(data, 'source.ext.schain', bidRequest.schain); - } - 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); From ce156c7d3983ad3e48acd4425cab4cae7ecf6237 Mon Sep 17 00:00:00 2001 From: Yuki Tsujii Date: Fri, 6 May 2022 19:19:31 +0900 Subject: [PATCH 03/17] Bid Richemedia adapter : use a 1x1 creative (#8327) --- modules/big-richmediaBidAdapter.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/big-richmediaBidAdapter.js b/modules/big-richmediaBidAdapter.js index cd8b2462eb8..2ee31e8cfd6 100644 --- a/modules/big-richmediaBidAdapter.js +++ b/modules/big-richmediaBidAdapter.js @@ -8,7 +8,7 @@ const BIDDER_CODE = 'big-richmedia'; const metadataByRequestId = {}; export const spec = { - version: '1.4.0', + version: '1.5.0', code: BIDDER_CODE, gvlid: baseAdapter.GVLID, // use base adapter gvlid supportedMediaTypes: [ BANNER, VIDEO ], @@ -78,6 +78,14 @@ export const spec = { customSelector, isReplayable }; + + // This is a workaround needed for the rendering step (so that the adserver iframe does not get resized to 1800x1000 + // when there is skin demand + if (format === 'skin') { + renderParams.width = 1 + renderParams.height = 1 + } + const encoded = window.btoa(JSON.stringify(renderParams)); bid.ad = ` `; From 2840e3e30b4e99de3e02d6a7f50d26117d05f268 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 May 2022 10:32:41 +0000 Subject: [PATCH 04/17] Prebid 6.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 c4e86036f99..34e98660ede 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0-pre", + "version": "6.23.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 9e5d68ac5e8..6be5102e3c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0-pre", + "version": "6.23.0", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 8e903db12d15690fc727644ccfe53139de6d54ce Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Fri, 6 May 2022 10:32:41 +0000 Subject: [PATCH 05/17] Increment version to 6.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 34e98660ede..c5797787eb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0", + "version": "6.24.0-pre", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 6be5102e3c2..f2d282c77aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "6.23.0", + "version": "6.24.0-pre", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { From 1a2ed86b52b7cfc18787ea3f5a6469551378cf63 Mon Sep 17 00:00:00 2001 From: SmartyAdman <59048845+SmartyAdman@users.noreply.github.com> Date: Fri, 6 May 2022 13:49:58 +0300 Subject: [PATCH 06/17] Adman Bid Adapter: add support for idx UserID(#8370) 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 Co-authored-by: minoru katogi Co-authored-by: minoru katogi Co-authored-by: ADman Media Co-authored-by: SmartyAdman --- modules/admanBidAdapter.js | 1 + test/spec/modules/admanBidAdapter_spec.js | 227 ++++++++++++++++++---- 2 files changed, 190 insertions(+), 38 deletions(-) diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index 241864c50fc..21bcb6cee26 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -110,6 +110,7 @@ export const spec = { if (bid.userId) { getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); + getUserId(placement.eids, bid.userId.idx, 'idx.lat'); } if (traff === VIDEO) { placement.playerSize = bid.mediaTypes[VIDEO].playerSize; diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index 89d140a7f25..feee8e39b45 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,8 +1,9 @@ import {expect} from 'chai'; import {spec} from '../../../modules/admanBidAdapter.js'; +import {deepClone} from '../../../src/utils' describe('AdmanAdapter', function () { - let bid = { + let bidBanner = { bidId: '2dd581a2b6281d', bidder: 'adman', bidderRequestId: '145e1d6a7837c9', @@ -32,6 +33,20 @@ describe('AdmanAdapter', function () { ] } }; + + let bidVideo = deepClone({ + ...bidBanner, + params: { + placementId: 0, + traffic: 'video' + }, + mediaTypes: { + video: { + playerSize: [300, 250] + } + } + }); + let bidderRequest = { bidderCode: 'adman', auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', @@ -40,25 +55,27 @@ describe('AdmanAdapter', function () { auctionStart: 1472239426000, timeout: 5000, uspConsent: '1YN-', + gdprConsent: 'gdprConsent', refererInfo: { referer: 'http://www.example.com', reachedTop: true, }, - bids: [bid] + bids: [bidBanner, bidVideo] } describe('isBidRequestValid', function () { it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bidBanner)).to.be.true; }); it('Should return false when placementId is not a number', function () { - bid.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bid)).to.be.false; + bidBanner.params.placementId = 'aaa'; + expect(spec.isBidRequestValid(bidBanner)).to.be.false; + bidBanner.params.placementId = 0; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests([bidBanner], bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -75,10 +92,11 @@ describe('AdmanAdapter', function () { expect(serverRequest.data.ccpa).to.be.an('string') }) - it('Returns valid data if array of bids is valid', function () { + it('Returns valid BANNER data if array of bids is valid', function () { + serverRequest = spec.buildRequests([bidBanner], bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -97,6 +115,33 @@ describe('AdmanAdapter', function () { expect(placement.bidFloor).to.be.an('number'); } }); + + it('Returns valid VIDEO data if array of bids is valid', function () { + serverRequest = spec.buildRequests([bidVideo], bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data.deviceWidth).to.be.a('number'); + expect(data.deviceHeight).to.be.a('number'); + expect(data.language).to.be.a('string'); + expect(data.secure).to.be.within(0, 1); + expect(data.host).to.be.a('string'); + expect(data.page).to.be.a('string'); + let placements = data['placements']; + for (let i = 0; i < placements.length; i++) { + let placement = placements[i]; + expect(placement).to.have.all.keys('placementId', 'eids', 'bidId', 'traffic', 'sizes', 'schain', 'bidFloor', + 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', + 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); + expect(placement.schain).to.be.an('object') + expect(placement.placementId).to.be.a('number'); + expect(placement.bidId).to.be.a('string'); + expect(placement.traffic).to.be.a('string'); + expect(placement.sizes).to.be.an('array'); + expect(placement.bidFloor).to.be.an('number'); + } + }); + it('Returns empty data if no valid requests are passed', function () { serverRequest = spec.buildRequests([]); let data = serverRequest.data; @@ -105,9 +150,9 @@ describe('AdmanAdapter', function () { }); describe('buildRequests with user ids', function () { - bid.userId = {} - bid.userId.uid2 = { id: 'uid2id123' }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + bidBanner.userId = {} + bidBanner.userId.uid2 = { id: 'uid2id123' }; + let serverRequest = spec.buildRequests([bidBanner], bidderRequest); it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; let placements = data['placements']; @@ -130,31 +175,34 @@ describe('AdmanAdapter', function () { }); describe('interpretResponse', function () { - let resObject = { - body: [ { - requestId: '123', - mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', - netRevenue: true, - currency: 'USD', - meta: { - advertiserDomains: ['google.com'], - advertiserId: 1234 - } - } ] - }; - let serverResponses = spec.interpretResponse(resObject); - it('Returns an array of valid server responses if response object is valid', function () { + it('(BANNER) Returns an array of valid server responses if response object is valid', function () { + const resBannerObject = { + body: [ { + requestId: '123', + mediaType: 'banner', + cpm: 0.3, + width: 320, + height: 50, + ad: '

Hello ad

', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resBannerObject); + expect(serverResponses).to.be.an('array').that.is.not.empty; for (let i = 0; i < serverResponses.length; i++) { let dataItem = serverResponses[i]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta'); + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); expect(dataItem.requestId).to.be.a('string'); expect(dataItem.cpm).to.be.a('number'); expect(dataItem.width).to.be.a('number'); @@ -167,21 +215,124 @@ describe('AdmanAdapter', function () { expect(dataItem.mediaType).to.be.a('string'); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); } - it('Returns an empty array if invalid response is passed', function () { - serverResponses = spec.interpretResponse('invalid_response'); - expect(serverResponses).to.be.an('array').that.is.empty; - }); + }); + + it('(VIDEO) Returns an array of valid server responses if response object is valid', function () { + const resVideoObject = { + body: [ { + requestId: '123', + mediaType: 'video', + cpm: 0.3, + width: 320, + height: 50, + vastUrl: 'https://', + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resVideoObject); + + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'vastUrl', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.vastUrl).to.be.a('string'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + } + }); + + it('(NATIVE) Returns an array of valid server responses if response object is valid', function () { + const resNativeObject = { + body: [ { + requestId: '123', + mediaType: 'native', + cpm: 0.3, + width: 320, + height: 50, + native: { + title: 'title', + image: 'image', + impressionTrackers: [ 'https://' ] + }, + ttl: 1000, + creativeId: '123asd', + netRevenue: true, + currency: 'USD', + adomain: ['example.com'], + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } + } ] + }; + + const serverResponses = spec.interpretResponse(resNativeObject); + + expect(serverResponses).to.be.an('array').that.is.not.empty; + for (let i = 0; i < serverResponses.length; i++) { + let dataItem = serverResponses[i]; + expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'native', 'ttl', 'creativeId', + 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); + expect(dataItem.requestId).to.be.a('string'); + expect(dataItem.cpm).to.be.a('number'); + expect(dataItem.width).to.be.a('number'); + expect(dataItem.height).to.be.a('number'); + expect(dataItem.native).to.be.an('object'); + expect(dataItem.ttl).to.be.a('number'); + expect(dataItem.creativeId).to.be.a('string'); + expect(dataItem.netRevenue).to.be.a('boolean'); + expect(dataItem.currency).to.be.a('string'); + expect(dataItem.mediaType).to.be.a('string'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); + } + }); + + it('Invalid mediaType in response', function () { + const resBadObject = { + body: [ { + mediaType: 'other', + requestId: '123', + cpm: 0.3, + ttl: 1000, + creativeId: '123asd', + currency: 'USD' + } ] + }; + + const serverResponses = spec.interpretResponse(resBadObject); + + expect(serverResponses).to.be.an('array').that.is.empty; }); }); describe('getUserSyncs', function () { - let userSync = spec.getUserSyncs({}); + const gdprConsent = { consentString: 'consentString', gdprApplies: 1 }; + const consentString = { consentString: 'consentString' } + let userSync = spec.getUserSyncs({}, {}, gdprConsent, consentString); it('Returns valid URL and type', function () { expect(userSync).to.be.an('array').with.lengthOf(1); expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('https://sync.admanmedia.com/image?pbjs=1&coppa=0'); + expect(userSync[0].url).to.be.equal('https://sync.admanmedia.com/image?pbjs=1&gdpr=0&gdpr_consent=consentString&ccpa_consent=consentString&coppa=0'); }); }); }); From 0f87f02c1b35a5f753f6adca79479e15dfe545c6 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 6 May 2022 10:00:50 -0700 Subject: [PATCH 07/17] Prebid core: do not enforce adapters' mediaType support for PBS bids (#8357) This fixes adUnit mediaType validation to skip bids that will get routed to PBS (https://github.com/prebid/Prebid.js/issues/8291), and also removes overzealous mediaTytpe validation from the PBS adapter (by the time it gets there, adUnits have already been validated) --- modules/prebidServerBidAdapter/index.js | 11 +++-------- src/prebid.js | 16 +++------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index b1b54d9e313..8279da13054 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -1238,18 +1238,13 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); - // at this point ad units should have a size array either directly or mapped so filter for that - const validAdUnits = adUnits.filter(unit => - unit.mediaTypes && (unit.mediaTypes.native || (unit.mediaTypes.banner && unit.mediaTypes.banner.sizes) || (unit.mediaTypes.video && unit.mediaTypes.video.playerSize)) - ); - // in case config.bidders contains invalid bidders, we only process those we sent requests for - const requestedBidders = validAdUnits + const requestedBidders = adUnits .map(adUnit => adUnit.bids.map(bid => bid.bidder).filter(uniques)) - .reduce(flatten) + .reduce(flatten, []) .filter(uniques); - const ortb2 = new ORTB2(s2sBidRequest, bidRequests, validAdUnits, requestedBidders); + const ortb2 = new ORTB2(s2sBidRequest, bidRequests, adUnits, requestedBidders); const request = ortb2.buildRequest(); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); diff --git a/src/prebid.js b/src/prebid.js index 98655825e89..4a7d01dfe76 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -20,7 +20,7 @@ import { executeRenderer, isRendererRequired } from './Renderer.js'; import { createBid } from './bidfactory.js'; import { storageCallbacks } from './storageManager.js'; import { emitAdRenderSucceeded, emitAdRenderFail } from './adRendering.js'; -import { gdprDataHandler, uspDataHandler } from './adapterManager.js' +import {gdprDataHandler, getS2SBidderSet, uspDataHandler} from './adapterManager.js'; const $$PREBID_GLOBAL$$ = getGlobal(); const CONSTANTS = require('./constants.json'); @@ -578,17 +578,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); - let _s2sConfigs = []; - const s2sBidders = []; - config.getConfig('s2sConfig', config => { - if (config && config.s2sConfig) { - _s2sConfigs = Array.isArray(config.s2sConfig) ? config.s2sConfig : [config.s2sConfig]; - } - }); - - _s2sConfigs.forEach(s2sConfig => { - s2sBidders.push(...s2sConfig.bidders); - }); + const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); adUnits = checkAdUnitSetup(adUnits); @@ -614,7 +604,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo const allBidders = adUnit.bids.map(bid => bid.bidder); const bidderRegistry = adapterManager.bidderRegistry; - const bidders = (s2sBidders) ? allBidders.filter(bidder => !includes(s2sBidders, bidder)) : allBidders; + const bidders = allBidders.filter(bidder => !s2sBidders.has(bidder)); adUnit.transactionId = generateUUID(); From a47650b680dbcb52b369b432bb5fd73cc87c48fc Mon Sep 17 00:00:00 2001 From: Chris Pabst Date: Mon, 9 May 2022 06:25:32 -0600 Subject: [PATCH 08/17] Sovrn Bid Adapter: handle multiple seatbids in response (#8378) * EX-1752 Add seatbid array support in sovrn adapter * Resolved conflicts. * Fix indentation Co-authored-by: mikhalovich --- modules/sovrnBidAdapter.js | 37 +++++++------ test/spec/modules/sovrnBidAdapter_spec.js | 64 +++++++++++++++++++---- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 23571373147..eed9ccb7461 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -64,8 +64,9 @@ export const spec = { /** * Format the bid request object for our endpoint - * @param {BidRequest[]} bidRequests Array of Sovrn bidders * @return object of parameters for Prebid AJAX request + * @param bidReqs + * @param bidderRequest */ buildRequests: function(bidReqs, bidderRequest) { try { @@ -192,14 +193,12 @@ export const spec = { * @return {Bid[]} An array of formatted bids. */ interpretResponse: function({ body: {id, seatbid} }) { + if (!id || !seatbid || !Array.isArray(seatbid)) return [] + try { - let sovrnBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(sovrnBid => { + return seatbid + .filter(seat => seat) + .map(seat => seat.bid.map(sovrnBid => { const bid = { requestId: sovrnBid.impid, cpm: parseFloat(sovrnBid.price), @@ -209,23 +208,23 @@ export const spec = { dealId: sovrnBid.dealid || null, currency: 'USD', netRevenue: true, - ttl: sovrnBid.ext ? (sovrnBid.ext.ttl || 90) : 90, + mediaType: sovrnBid.nurl ? BANNER : VIDEO, + ttl: sovrnBid.ext?.ttl || 90, meta: { advertiserDomains: sovrnBid && sovrnBid.adomain ? sovrnBid.adomain : [] } } - if (!sovrnBid.nurl) { - bid.mediaType = VIDEO - bid.vastXml = decodeURIComponent(sovrnBid.adm) - } else { - bid.mediaType = BANNER + if (sovrnBid.nurl) { bid.ad = decodeURIComponent(`${sovrnBid.adm}`) + } else { + bid.vastXml = decodeURIComponent(sovrnBid.adm) } - sovrnBidResponses.push(bid); - }); - } - return sovrnBidResponses + + return bid + })) + .flat() } catch (e) { - logError('Could not intrepret bidresponse, error deatils:', 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 ee25f729a0a..83a13f0db7b 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -404,9 +404,29 @@ describe('sovrnBidAdapter', function() { 'currency': 'USD', 'netRevenue': true, 'mediaType': 'banner', - 'ad': decodeURIComponent(``), 'ttl': 90, - 'meta': { advertiserDomains: [] } + 'meta': { advertiserDomains: [] }, + 'ad': decodeURIComponent(``), + } + const videoBid = { + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '', + 'adm': 'key%3Dvalue', + 'h': 480, + 'w': 640 + } + const bannerBid = { + 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', + 'crid': 'creativelycreatedcreativecreative', + 'impid': '263c448586f5a1', + 'price': 0.45882675, + 'nurl': '', + 'adm': '', + 'h': 90, + 'w': 728 } beforeEach(function () { response = { @@ -414,14 +434,7 @@ describe('sovrnBidAdapter', function() { 'id': '37386aade21a71', 'seatbid': [{ 'bid': [{ - 'id': 'a_403370_332fdb9b064040ddbec05891bd13ab28', - 'crid': 'creativelycreatedcreativecreative', - 'impid': '263c448586f5a1', - 'price': 0.45882675, - 'nurl': '', - 'adm': '', - 'h': 90, - 'w': 728 + ...bannerBid }] }] } @@ -431,7 +444,6 @@ describe('sovrnBidAdapter', function() { it('should get the correct bid response', function () { const expectedResponse = { ...baseResponse, - 'ad': decodeURIComponent(`>`), 'ttl': 60000, }; @@ -491,6 +503,36 @@ describe('sovrnBidAdapter', function() { expect(result.length).to.equal(0); }); + + it('should get the correct bid response with 2 different bids', function () { + const expectedVideoResponse = { + ...baseResponse, + 'vastXml': decodeURIComponent(videoBid.adm) + } + delete expectedVideoResponse.ad + + const expectedBannerResponse = { + ...baseResponse + } + + response.body.seatbid = [{ bid: [bannerBid] }, { bid: [videoBid] }] + const result = spec.interpretResponse(response) + + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedBannerResponse)) + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedVideoResponse)) + }) + + it('should get the correct bid response with 2 seatbid items', function () { + const expectedResponse = { + ...baseResponse + } + response.body.seatbid = [response.body.seatbid[0], response.body.seatbid[0]] + + const result = spec.interpretResponse(response) + + expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedResponse)) + expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedResponse)) + }) }); describe('interpretResponse video', function () { From 028843dfe2a813d186464cfe44ef89f2870b5eb9 Mon Sep 17 00:00:00 2001 From: Alexander <32703851+pro-nsk@users.noreply.github.com> Date: Mon, 9 May 2022 21:05:14 +0700 Subject: [PATCH 09/17] Alkimi Bid Adapter: add new bid adapter (#8326) * Alkimi bid adapter * Alkimi bid adapter * Alkimi bid adapter * alkimi adapter * onBidWon change * sign utils * auction ID as bid request ID * unit test fixes Co-authored-by: Alexander Bogdanov --- modules/alkimiBidAdapter.js | 119 +++++++++++++++ modules/alkimiBidAdapter.md | 29 ++++ test/spec/modules/alkimiBidAdapter_spec.js | 164 +++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 modules/alkimiBidAdapter.js create mode 100644 modules/alkimiBidAdapter.md create mode 100644 test/spec/modules/alkimiBidAdapter_spec.js diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js new file mode 100644 index 00000000000..261fd9dee68 --- /dev/null +++ b/modules/alkimiBidAdapter.js @@ -0,0 +1,119 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepClone, deepAccess } from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'alkimi'; +export const ENDPOINT = 'https://exchange.alkimi-onboarding.com/bid?prebid=true'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: ['banner', 'video'], + + isBidRequestValid: function (bid) { + return !!(bid.params && bid.params.bidFloor && bid.params.token); + }, + + buildRequests: function (validBidRequests, bidderRequest) { + let bids = []; + let bidIds = []; + validBidRequests.forEach(bidRequest => { + let sizes = prepareSizes(bidRequest.sizes) + + bids.push({ + token: bidRequest.params.token, + pos: bidRequest.params.pos, + bidFloor: bidRequest.params.bidFloor, + width: sizes[0].width, + height: sizes[0].height, + impMediaType: getFormatType(bidRequest) + }) + bidIds.push(bidRequest.bidId) + }) + + const alkimiConfig = config.getConfig('alkimi'); + + let payload = { + requestId: bidderRequest.auctionId, + signRequest: { bids, randomUUID: alkimiConfig && alkimiConfig.randomUUID }, + bidIds, + referer: bidderRequest.refererInfo.referer, + signature: alkimiConfig && alkimiConfig.signature + } + + const options = { + contentType: 'application/json', + customHeaders: { + 'Rtb-Direct': true + } + } + + return { + method: 'POST', + url: ENDPOINT, + data: payload, + options + }; + }, + + interpretResponse: function (serverResponse, request) { + const serverBody = serverResponse.body; + if (!serverBody || typeof serverBody !== 'object') { + return []; + } + + const { prebidResponse } = serverBody; + if (!prebidResponse || typeof prebidResponse !== 'object') { + return []; + } + + let bids = []; + prebidResponse.forEach(bidResponse => { + let bid = deepClone(bidResponse); + bid.cpm = parseFloat(bidResponse.cpm); + + // banner or video + if (VIDEO === bid.mediaType) { + bid.vastXml = bid.ad; + } + + bid.meta = {}; + bid.meta.advertiserDomains = bid.adomain || []; + + bids.push(bid); + }) + + return bids; + }, + + 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; + } + + ajax(winUrl, null); + return true; + } +} + +function prepareSizes(sizes) { + return sizes && sizes.map(size => ({ width: size[0], height: size[1] })); +} + +const getFormatType = bidRequest => { + if (deepAccess(bidRequest, 'mediaTypes.banner')) return 'Banner' + if (deepAccess(bidRequest, 'mediaTypes.video')) return 'Video' + if (deepAccess(bidRequest, 'mediaTypes.audio')) return 'Audio' +} + +registerBidder(spec); diff --git a/modules/alkimiBidAdapter.md b/modules/alkimiBidAdapter.md new file mode 100644 index 00000000000..92a7c2aefe1 --- /dev/null +++ b/modules/alkimiBidAdapter.md @@ -0,0 +1,29 @@ +# Overview + +``` +Module Name: Alkimi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: abogdanov@asteriosoft.com +``` + +# Description + +Connects to Alkimi Bidder for bids. +Alkimi bid adapter supports Banner and Video ads. + +# Test Parameters +``` +const adUnits = [ + { + bids: [ + { + bidder: 'alkimi', + params: { + bidFloor: 0.1, + token: '?????????????????????', // Publisher Token provided by Alkimi + } + } + ] + } +]; +``` diff --git a/test/spec/modules/alkimiBidAdapter_spec.js b/test/spec/modules/alkimiBidAdapter_spec.js new file mode 100644 index 00000000000..58a5a3b54ab --- /dev/null +++ b/test/spec/modules/alkimiBidAdapter_spec.js @@ -0,0 +1,164 @@ +import { expect } from 'chai' +import { ENDPOINT, spec } from 'modules/alkimiBidAdapter.js' +import { newBidder } from 'src/adapters/bidderFactory.js' + +const REQUEST = { + 'bidId': '456', + 'bidder': 'alkimi', + 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'params': { + bidFloor: 0.1, + token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', + pos: 7 + } +} + +const BIDDER_BANNER_RESPONSE = { + 'prebidResponse': [{ + 'ad': '
test
', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46d', + 'cpm': 900.5, + 'currency': 'USD', + 'width': 640, + 'height': 480, + 'ttl': 300, + 'creativeId': 1, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'mediaType': 'banner', + 'adomain': ['test.com'] + }] +} + +const BIDDER_VIDEO_RESPONSE = { + 'prebidResponse': [{ + 'ad': 'vast', + 'requestId': 'e64782a4-8e68-4c38-965b-80ccf115d46z', + 'cpm': 800.4, + 'currency': 'USD', + 'width': 1024, + 'height': 768, + 'ttl': 200, + 'creativeId': 2, + 'netRevenue': true, + 'winUrl': 'http://test.com', + 'mediaType': 'video', + 'adomain': ['test.com'] + }] +} + +const BIDDER_NO_BID_RESPONSE = '' + +describe('alkimiBidAdapter', function () { + const adapter = newBidder(spec) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function') + }) + }) + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(REQUEST)).to.equal(true) + }) + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, REQUEST) + delete bid.params.token + expect(spec.isBidRequestValid(bid)).to.equal(false) + + bid = Object.assign({}, REQUEST) + delete bid.params.bidFloor + expect(spec.isBidRequestValid(bid)).to.equal(false) + + bid = Object.assign({}, REQUEST) + delete bid.params + expect(spec.isBidRequestValid(bid)).to.equal(false) + }) + }) + + describe('buildRequests', function () { + let bidRequests = [REQUEST] + const bidderRequest = spec.buildRequests(bidRequests, { + auctionId: '123', + refererInfo: { + referer: 'http://test.com/path.html' + } + }) + + it('sends bid request to ENDPOINT via POST', function () { + expect(bidderRequest.method).to.equal('POST') + expect(bidderRequest.data.requestId).to.equal('123') + expect(bidderRequest.data.referer).to.equal('http://test.com/path.html') + expect(bidderRequest.data.signRequest.bids).to.deep.contains({ token: 'e64782a4-8e68-4c38-965b-80ccf115d46f', pos: 7, bidFloor: 0.1, width: 300, height: 250, impMediaType: 'Banner' }) + 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.options.customHeaders).to.deep.equal({ 'Rtb-Direct': true }) + expect(bidderRequest.options.contentType).to.equal('application/json') + expect(bidderRequest.url).to.equal(ENDPOINT) + }) + }) + + describe('interpretResponse', function () { + it('handles banner request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_BANNER_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('
test
') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46d') + expect(result[0]).to.have.property('cpm').equal(900.5) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(640) + expect(result[0]).to.have.property('height').equal(480) + expect(result[0]).to.have.property('ttl').equal(300) + expect(result[0]).to.have.property('creativeId').equal(1) + 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('mediaType').equal('banner') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) + }) + + it('handles video request : should get correct bid response', function () { + const result = spec.interpretResponse({ body: BIDDER_VIDEO_RESPONSE }, {}) + + expect(result[0]).to.have.property('ad').equal('vast') + expect(result[0]).to.have.property('requestId').equal('e64782a4-8e68-4c38-965b-80ccf115d46z') + expect(result[0]).to.have.property('cpm').equal(800.4) + expect(result[0]).to.have.property('currency').equal('USD') + expect(result[0]).to.have.property('width').equal(1024) + expect(result[0]).to.have.property('height').equal(768) + 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('mediaType').equal('video') + expect(result[0]).to.have.property('vastXml').equal('vast') + expect(result[0].meta).to.exist.property('advertiserDomains') + expect(result[0].meta).to.have.property('advertiserDomains').lengthOf(1) + }) + + it('handles no bid response : should get empty array', function () { + let result = spec.interpretResponse({ body: undefined }, {}) + expect(result).to.deep.equal([]) + + result = spec.interpretResponse({ body: BIDDER_NO_BID_RESPONSE }, {}) + expect(result).to.deep.equal([]) + }) + }) + + describe('onBidWon', function () { + it('handles banner win: should get true', function () { + const win = BIDDER_BANNER_RESPONSE.prebidResponse[0] + const bidWonResult = spec.onBidWon(win) + + expect(bidWonResult).to.equal(true) + }) + }) +}) From 7f0a56ba06ebca94ec61973bc671fc246e15a4a1 Mon Sep 17 00:00:00 2001 From: natexo-technical-team <91968830+natexo-technical-team@users.noreply.github.com> Date: Mon, 9 May 2022 21:17:58 +0200 Subject: [PATCH 10/17] talkads Bid Adapter: update params access in case of different ad servers (#8390) * Update talkadsBidAdapter.js Update params access * Update talkadsBidAdapter.js update functions headers * Update talkadsBidAdapter.js Delete params attribute * Update talkadsBidAdapter_spec.js Delete params attribute --- modules/talkadsBidAdapter.js | 13 ++++++++----- test/spec/modules/talkadsBidAdapter_spec.js | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/talkadsBidAdapter.js b/modules/talkadsBidAdapter.js index 068dce23b43..dae452b9a7d 100644 --- a/modules/talkadsBidAdapter.js +++ b/modules/talkadsBidAdapter.js @@ -5,11 +5,12 @@ import {ajax} from '../src/ajax.js'; const CURRENCY = 'EUR'; const BIDDER_CODE = 'talkads'; +const GVLID = 1074; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [ NATIVE, BANNER ], - params: null, /** * Determines whether or not the given bid request is valid. @@ -31,7 +32,7 @@ export const spec = { utils.logError('VALIDATION FAILED : the parameter "bidder_url" must be defined'); return false; } - this.params = poBid.params; + return !!(poBid.nativeParams || poBid.sizes); }, // isBidRequestValid @@ -54,6 +55,7 @@ export const spec = { } return loOne; }); + let laParams = paValidBidRequests[0].params; const loServerRequest = { cur: CURRENCY, timeout: poBidderRequest.timeout, @@ -71,7 +73,7 @@ export const spec = { loServerRequest.gdpr.consent = poBidderRequest.gdprConsent.consentString; } } - const lsUrl = this.params.bidder_url + '/' + this.params.tag_id; + const lsUrl = laParams.bidder_url + '/' + laParams.tag_id; return { method: 'POST', url: lsUrl, @@ -86,7 +88,7 @@ export const spec = { * @param poPidRequest Request original server request * @return An array of bids which were nested inside the server. */ - interpretResponse: (poServerResponse, poPidRequest) => { + interpretResponse: function (poServerResponse, poPidRequest) { utils.logInfo('interpretResponse : ', poServerResponse); if (!poServerResponse.body) { return []; @@ -120,8 +122,9 @@ export const spec = { */ onBidWon: function (poBid) { utils.logInfo('onBidWon : ', poBid); + let laParams = poBid.params[0]; if (poBid.pbid) { - ajax(this.params.bidder_url + 'won/' + poBid.pbid); + ajax(laParams.bidder_url + 'won/' + poBid.pbid); } }, // onBidWon }; diff --git a/test/spec/modules/talkadsBidAdapter_spec.js b/test/spec/modules/talkadsBidAdapter_spec.js index 00f52ba7b6a..c48808cbc15 100644 --- a/test/spec/modules/talkadsBidAdapter_spec.js +++ b/test/spec/modules/talkadsBidAdapter_spec.js @@ -207,6 +207,7 @@ describe('TalkAds adapter', function () { ttl: 60, creativeId: 'c123a456', netRevenue: false, + params: [Object.assign({}, commonBidRequest.params)], } spec.onBidWon(loBid) expect(server.requests.length).to.equals(0); @@ -222,7 +223,8 @@ describe('TalkAds adapter', function () { ttl: 60, creativeId: 'c123a456', netRevenue: false, - pbid: '6147833a65749742875ace47' + pbid: '6147833a65749742875ace47', + params: [Object.assign({}, commonBidRequest.params)], } spec.onBidWon(loBid) expect(server.requests[0].url).to.equals('https://test.natexo-programmatic.com/tad/tag/prebidwon/6147833a65749742875ace47'); From 6dbad9a1b28ac4a556efb92b350a3eae2888ae25 Mon Sep 17 00:00:00 2001 From: matthieularere-msq <63732822+matthieularere-msq@users.noreply.github.com> Date: Tue, 10 May 2022 14:35:36 +0200 Subject: [PATCH 11/17] BidWatch Analytics Adapter: add new analytics adapter (#8302) * New Analytics Adapter bidwatch * test for bidwatch Analytics Adapter * change maintainer address * Update bidwatchAnalyticsAdapter.js * Update bidwatchAnalyticsAdapter.js * Update bidwatchAnalyticsAdapter.md * Update bidwatchAnalyticsAdapter.md --- modules/bidwatchAnalyticsAdapter.js | 90 ++++++ modules/bidwatchAnalyticsAdapter.md | 21 ++ .../modules/bidwatchAnalyticsAdapter_spec.js | 288 ++++++++++++++++++ 3 files changed, 399 insertions(+) create mode 100644 modules/bidwatchAnalyticsAdapter.js create mode 100644 modules/bidwatchAnalyticsAdapter.md create mode 100644 test/spec/modules/bidwatchAnalyticsAdapter_spec.js diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js new file mode 100644 index 00000000000..26a8c370af3 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.js @@ -0,0 +1,90 @@ +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import CONSTANTS from '../src/constants.json'; +import { ajax } from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'URL_TO_SERVER_ENDPOINT'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + } +} = CONSTANTS; + +let allEvents = {} +let initOptions = {} +let endpoint = 'https://default' +let objectToSearchForBidderCode = ['bidderRequests', 'bidsReceived', 'noBids'] + +function getAdapterNameForAlias(aliasName) { + return adapterManager.aliasRegistry[aliasName] || aliasName; +} + +function setOriginalBidder(arg) { + Object.keys(arg).forEach(key => { + arg[key]['originalBidder'] = getAdapterNameForAlias(arg[key]['bidderCode']); + if (typeof arg[key]['creativeId'] == 'number') { arg[key]['creativeId'] = arg[key]['creativeId'].toString(); } + }); + return arg +} + +function checkBidderCode(args) { + if (typeof args == 'object') { + for (let i = 0; i < objectToSearchForBidderCode.length; i++) { + if (typeof args[objectToSearchForBidderCode[i]] == 'object') { args[objectToSearchForBidderCode[i]] = setOriginalBidder(args[objectToSearchForBidderCode[i]]) } + } + } + if (typeof args['bidderCode'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidderCode']); } else if (typeof args['bidder'] == 'string') { args['originalBidder'] = getAdapterNameForAlias(args['bidder']); } + if (typeof args['creativeId'] == 'number') { args['creativeId'] = args['creativeId'].toString(); } + return args +} + +function addEvent(eventType, args) { + if (allEvents[eventType] == undefined) { allEvents[eventType] = [] } + if (eventType && args) { args = checkBidderCode(args); } + allEvents[eventType].push(args); +} + +function handleBidWon(args) { + if (typeof allEvents.bidRequested == 'object' && allEvents.bidRequested.length > 0 && allEvents.bidRequested[0].gdprConsent) { args.gdpr = allEvents.bidRequested[0].gdprConsent; } + ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true}); +} + +function handleAuctionEnd() { + ajax(endpoint + '.bidwatch.io/analytics/auctions', null, JSON.stringify(allEvents), {method: 'POST', withCredentials: true}); +} + +let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ + eventType, + args + }) { + addEvent(eventType, args); + switch (eventType) { + case AUCTION_END: + handleAuctionEnd(); + break; + case BID_WON: + handleBidWon(args); + break; + } + }}); + +// save the base class function +bidwatchAnalytics.originEnableAnalytics = bidwatchAnalytics.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +bidwatchAnalytics.enableAnalytics = function (config) { + bidwatchAnalytics.originEnableAnalytics(config); // call the base class function + initOptions = config.options; + if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: bidwatchAnalytics, + code: 'bidwatch' +}); + +export default bidwatchAnalytics; diff --git a/modules/bidwatchAnalyticsAdapter.md b/modules/bidwatchAnalyticsAdapter.md new file mode 100644 index 00000000000..bfa453640b8 --- /dev/null +++ b/modules/bidwatchAnalyticsAdapter.md @@ -0,0 +1,21 @@ +# Overview +Module Name: bidwatch Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: tech@bidwatch.io + +# Description + +Analytics adapter for bidwatch.io. + +# Test Parameters + +``` +{ + provider: 'bidwatch', + options : { + domain: 'test.endpoint' + } +} +``` diff --git a/test/spec/modules/bidwatchAnalyticsAdapter_spec.js b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..1a322d131a9 --- /dev/null +++ b/test/spec/modules/bidwatchAnalyticsAdapter_spec.js @@ -0,0 +1,288 @@ +import bidwatchAnalytics from 'modules/bidwatchAnalyticsAdapter.js'; +import { expect } from 'chai'; +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('BidWatch Analytics', function () { + let timestamp = new Date() - 256; + let auctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; + let timeout = 1500; + + let bidTimeout = [ + { + 'bidId': '5fe418f2d70364', + 'bidder': 'appnexusAst', + 'adUnitCode': 'tag_200124_banner', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b' + } + ]; + + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'appnexus', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'appnexus', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '34a63e5d5378a3', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2 + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'appnexusAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 600, + 'statusMessage': 'Bid available', + 'adId': '7a4ced80f33d33', + 'requestId': '34a63e5d5378a3', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '7a4ced80f33d33', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'appnexus', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'appnexus', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + after(function () { + bidwatchAnalytics.disableAnalytics(); + }); + + describe('main test flow', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + it('should catch events of interest', function () { + sinon.spy(bidwatchAnalytics, 'track'); + + adapterManager.registerAnalyticsAdapter({ + code: 'bidwatch', + adapter: bidwatchAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'bidwatch', + options: { + domain: 'test' + } + }); + events.emit(constants.EVENTS.BID_TIMEOUT, bidTimeout); + events.emit(constants.EVENTS.AUCTION_END, auctionEnd); + events.emit(constants.EVENTS.BID_WON, bidWon); + sinon.assert.callCount(bidwatchAnalytics.track, 3); + }); + }); +}); From c66840c4744ac7e2fdc718d6d8a197251d7b357b Mon Sep 17 00:00:00 2001 From: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Date: Tue, 10 May 2022 15:15:11 +0200 Subject: [PATCH 12/17] Native ads: change asset param (#8371) --- modules/improvedigitalBidAdapter.js | 14 +++- .../modules/improvedigitalBidAdapter_spec.js | 71 +++++++++++++------ 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index d1c35f63f9a..9de2e2b2d32 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -304,7 +304,10 @@ const ID_REQUEST = { } if (deepAccess(bidRequest, 'mediaTypes.native')) { - imp.native = this.buildNativeRequest(bidRequest); + const nativeImp = this.buildNativeRequest(bidRequest); + if (nativeImp) { + imp.native = nativeImp; + } } return imp; @@ -358,7 +361,10 @@ const ID_REQUEST = { }, buildNativeRequest(bidRequest) { - const nativeParams = bidRequest.mediaTypes.native; + const nativeParams = bidRequest.nativeParams; + if (!nativeParams) { + return null; + } const request = { assets: [], } @@ -392,6 +398,10 @@ const ID_REQUEST = { request.assets.push(asset); } } + if (!request.assets.length) { + logWarn('No native assets recognized. Ignoring native ad request'); + return null; + } return { ver: NATIVE_DATA.VERSION, request: JSON.stringify(request) }; }, diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index d721c2d20bc..8004c8d6f62 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { spec } from 'modules/improvedigitalBidAdapter.js'; import { config } from 'src/config.js'; import { deepClone } from 'src/utils.js'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {BANNER, NATIVE, VIDEO} from '../../../src/mediaTypes'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; @@ -50,17 +50,31 @@ describe('Improve Digital Adapter Tests', function () { } }; + const nativeBidRequest = deepClone(simpleBidRequest); + nativeBidRequest.mediaTypes = { native: {} }; + nativeBidRequest.nativeParams = { + title: {required: true}, + body: {required: true} + }; + const multiFormatBidRequest = deepClone(simpleBidRequest); multiFormatBidRequest.mediaTypes = { banner: { sizes: [[300, 250], [160, 600]] }, + native: {}, video: { context: 'outstream', playerSize: [640, 480] } }; + multiFormatBidRequest.nativeParams = { + body: { + required: true + } + }; + const simpleSmartTagBidRequest = { bidder: 'improvedigital', bidId: '1a2b3c', @@ -87,6 +101,10 @@ describe('Improve Digital Adapter Tests', function () { bids: [multiFormatBidRequest] }; + const nativeBidderRequest = { + bids: [nativeBidRequest] + }; + const bidderRequestGdpr = { bids: [simpleBidRequest], gdprConsent: { @@ -197,6 +215,10 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp).to.deep.equal([ { id: '33e9500b21129f', + native: { + request: '{"assets":[{"id":3,"required":1,"data":{"type":2}}]}', + ver: '1.2' + }, secure: 0, ext: { bidder: { @@ -220,6 +242,28 @@ describe('Improve Digital Adapter Tests', function () { getConfigStub.restore(); }); + it('should make a well-formed native request', function () { + const payload = JSON.parse(spec.buildRequests([nativeBidRequest])[0].data); + expect(payload.imp[0].native).to.deep.equal({ + ver: '1.2', + request: '{\"assets\":[{\"id\":0,\"required\":1,\"title\":{\"len\":140}},{\"id\":3,\"required\":1,\"data\":{\"type\":2}}]}' + }); + }); + + it('should not make native request when nativeParams is undefined', function () { + const request = deepClone(nativeBidRequest); + delete request.nativeParams; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + + it('should not make native request when no assets', function () { + const request = deepClone(nativeBidRequest); + request.nativeParams = {}; + const payload = JSON.parse(spec.buildRequests([request])[0].data); + expect(payload.imp[0].native).to.not.exist; + }); + it('should set placementKey and publisherId for smart tags', function () { const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); @@ -238,20 +282,6 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].ext.bidder.keyValues).to.deep.equal(keyValues); }); - // it('should add single size filter', function () { - // const bidRequest = Object.assign({}, simpleBidRequest); - // const size = { - // w: 800, - // h: 600 - // }; - // bidRequest.params.size = size; - // const payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest).data); - // expect(payload.imp[0].banner).to.deep.equal(size); - // // When single size filter is set, format shouldn't be populated. This - // // is to maintain backward compatibily - // expect(payload.imp[0].banner.format).to.not.exist; - // }); - it('should add currency', function () { const bidRequest = Object.assign({}, simpleBidRequest); const getConfigStub = sinon.stub(config, 'getConfig').returns('JPY'); @@ -790,7 +820,7 @@ describe('Improve Digital Adapter Tests', function () { 'id': '52098fad-20c1-476b-a4fa-41e275e5a4a5', 'price': 1.8600000000000003, 'adm': "{\"ver\":\"1.1\",\"imptrackers\":[\"https://secure.adnxs.com/imptr?id=52311&t=2\",\"https://euw-ice.360yield.com/imp_pixel?ic=hcUBlCANx1FabHBf6FR2gC7UO4xEyXahdZAn0-B5qL-bb3A74BJ1smyWIyW7IWcC0SOjSXzVpevTHXxTqJ.sf.Qhahyy6tSo.0j1QWfXlH8sM4-8vKWjMjw-x.IrJJNlwkQ0s1CdwcwTefcLXm5l2E-W19VhACuV7f3mgrZMNjiSw.SjJAfyPC3SIyAMRjYfj53UmjriQ46T7lhmkqxK8wHmksYCdbZc3PZESk8NWl28sxdjNvnYYCFMcJbeav.LOLabyTXfwy-1cEPbQs.IKMRZIKaqccTDPV3wOtzbNv0jQzatd3Nnv-PGFQcjQ-GW3i27W04Fws4kodpFSn-B6VwZAjzLzoyd5gBncyRnAyCplEbgHU5sZ1IyKHWjgCl3ZtRIK5vqrRD5D-xqgSnOi7-phG.CqZWDZ4bMDSfQg2ZnbvUTyGKcEl0WR59dW5izTMV4Fjizcrvr5T-t.zMbGwz.hGnmLIyhTqh.IcwW.GiDLVExlDlix5S1LXIWVsSyrQ==\"],\"assets\":[{\"id\":1,\"data\":{\"value\":\"ImproveDigital\",\"type\":1}},{\"id\":3,\"data\":{\"value\":\"Test content.\",\"type\":2}},{\"id\":0,\"title\":{\"text\":\"Sample Prebid Test Title\"}}],\"link\":{\"url\":\"https://euw-ice.360yield.com/click/hcUBlHOV7YhVse8RyBa0ajjyPa9Vt17e4g-1m3cRj3E67vq-RYux.SiUeAmBfNBcoOqkUc6A15AWmi4yFu5K-BdkaYjildyyk7fNLyR6hWr411kv4vrFwm5jrIBceuHS6K8oN69f.uCo8zGTdR2TbSlldwcpahQPlufZU.6VaMsu4IC53uEiUT5vb7kAw6TTlxuGBNq6zaGryiWEV2.N3YYJDTyYPh8tv-ZFyeFZFm0Gnjv.xWbC.70JcRUVU9UelQaPsTpTWYTXBhJt84YJUw1-GNtaLNVLSjjZbVoA2fsMti5p6OBmF.7u39on2OPgvseIkSmge7Pqg63pRqdP75hp.DAEk6OkcN1jGnwP2DSbvpaSbin5lVqjfO0B-wnQgfQTCUtM5v4JmkNweLhUf9Q-x.nPKLW5SccEk9ZFXzY2-1wpT3PWm8Tix3NRscLPZub9wHzL..pl6ip8cQ9hp16UjwT4H6RMAxL0R7bl-h2pAicGAzYmuO7ntRESKUoIWA==//http%3A%2F%2Fquantum-advertising.com%2Ffr%2F\"},\"jstracker\":\"\"}", - 'impid': '2d7a7db325c6f', + 'impid': '33e9500b21129f', 'cid': '196108' } ], @@ -1043,10 +1073,6 @@ describe('Improve Digital Adapter Tests', function () { // // Native ads it('should return a well-formed native ad bid', function () { - const nativeBidderRequest = JSON.parse(JSON.stringify(bidderRequest)); - nativeBidderRequest.bids[0].bidId = '2d7a7db325c6f'; - delete nativeBidderRequest.bids[0].mediaTypes.banner; - nativeBidderRequest.bids[0].mediaTypes.native = {}; const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: nativeBidderRequest}); // Verify Native Response expect(bids[0].native).to.exist; @@ -1063,6 +1089,11 @@ describe('Improve Digital Adapter Tests', function () { expect(nativeBid.body).to.exist.and.equal('Test content.'); }); + it('should return a well-formed native bid for multi-format ad unit', function () { + const bids = spec.interpretResponse(serverResponseNative, {bidderRequest: multiFormatBidderRequest}); + expect(bids[0].mediaType).to.equal(NATIVE); + }); + // Video it('should return a well-formed instream video bid', function () { const bids = spec.interpretResponse(serverResponseVideo, {bidderRequest: instreamBidderRequest}); From bcfb1270a317bd376e062602e1eeed9b0e3ef677 Mon Sep 17 00:00:00 2001 From: TPMN Admin Date: Tue, 10 May 2022 23:25:32 +0900 Subject: [PATCH 13/17] TPMN Bidder Adapter: write id in first party domain; force syncs with various parties (#8341) * add TPMN UserSync Bidder Adapter(Test Modify) Updating the source code that was forked in the past. make test case more. - add pb7 bidderSettings option - userSync fix. * fix indentation from CircleCI error report * fix indentation from CircleCI error report * fix indentation from CircleCI error report * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. * fix user sync. static user sync without checking uuid. Co-authored-by: changjun --- modules/tpmnBidAdapter.js | 78 +++++++++++++---- test/spec/modules/tpmnBidAdapter_spec.js | 103 ++++++++++++++++++----- 2 files changed, 145 insertions(+), 36 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 006357cd4b9..88e89bcd64b 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,13 +1,16 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { parseUrl, deepAccess } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; import { BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.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}); export const spec = { code: BIDDER_CODE, @@ -18,20 +21,20 @@ 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) { + 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 + '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} - */ + * @param {BidRequest[]} bidRequests + * @param {*} bidderRequest + * @return {ServerRequest} + */ buildRequests: (bidRequests, bidderRequest) => { if (bidRequests.length === 0) { return []; @@ -49,11 +52,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, serverRequest) { if (!Array.isArray(serverResponse.body)) { return []; @@ -63,7 +66,48 @@ export const spec = { // 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}`; + } + } + 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; + }, }; registerBidder(spec); diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index b4f6882dbe1..468769c2573 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -1,9 +1,39 @@ /* eslint-disable no-tabs */ -import { expect } from 'chai'; -import { spec } from 'modules/tpmnBidAdapter.js'; +import {expect} from 'chai'; +import {spec, storage} from 'modules/tpmnBidAdapter.js'; +import {generateUUID} from '../../../src/utils.js'; +import {newBidder} from '../../../src/adapters/bidderFactory'; +import * as sinon from 'sinon'; -describe('tpmnAdapterTests', function() { - describe('isBidRequestValid', function() { +describe('tpmnAdapterTests', function () { + const adapter = newBidder(spec); + const BIDDER_CODE = 'tpmn'; + let sandbox = sinon.sandbox.create(); + let getCookieStub; + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + tpmn: { + storageAllowed: true + } + }; + sandbox = sinon.sandbox.create(); + getCookieStub = sinon.stub(storage, 'getCookie'); + }); + + afterEach(function () { + sandbox.restore(); + getCookieStub.restore(); + $$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', @@ -20,17 +50,18 @@ describe('tpmnAdapterTests', function() { } } }; - it('should return true if a bid is valid banner bid request', function() { + + 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() { + 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() { + it('should return false when required param values have invalid type', function () { let bid = Object.assign({}, bid); bid.params = { 'inventoryId': null, @@ -40,14 +71,14 @@ describe('tpmnAdapterTests', function() { }); }); - describe('buildRequests', function() { - it('should return an empty list if there are no bid requests', function() { + 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() { + it('should generate a POST server request with bidder API url, data', function () { const bid = { adUnitCode: 'temp-unitcode', bidder: 'tpmn', @@ -65,13 +96,15 @@ describe('tpmnAdapterTests', function() { } }; const tempBidRequests = [bid]; - const tempBidderRequest = {refererInfo: { - referer: 'http://localhost/test', - site: { - domain: 'localhost', - page: 'http://localhost/test' + const tempBidderRequest = { + refererInfo: { + referer: 'http://localhost/test', + site: { + domain: 'localhost', + page: 'http://localhost/test' + } } - }}; + }; const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest); expect(builtRequest).to.have.lengthOf(1); @@ -96,7 +129,7 @@ describe('tpmnAdapterTests', function() { }); }); - describe('interpretResponse', function() { + describe('interpretResponse', function () { const bid = { adUnitCode: 'temp-unitcode', bidder: 'tpmn', @@ -115,12 +148,12 @@ describe('tpmnAdapterTests', function() { }; const tempBidRequests = [bid]; - it('should return an empty aray to indicate no valid bids', function() { + 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; }); - it('should return an empty array to indicate no valid bids', function() { + it('should return an empty array to indicate no valid bids', function () { const mockBidResult = { requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8', cpm: 10.0, @@ -141,4 +174,36 @@ describe('tpmnAdapterTests', function() { expect(bidResponses).deep.equal([mockBidResult]); }); }); + + describe('getUserSync', function () { + const KEY_ID = 'uuid'; + const TMP_UUID = generateUUID().replace(/-/g, ''); + + it('getCookie mock Test', () => { + const uuid = storage.getCookie(KEY_ID); + expect(uuid).to.equal(undefined); + }); + + it('getCookie mock Test', () => { + expect(TMP_UUID.length).to.equal(32); + getCookieStub.withArgs(KEY_ID).returns(TMP_UUID); + const uuid = storage.getCookie(KEY_ID); + expect(uuid).to.equal(TMP_UUID); + }); + + it('case 1 -> allow iframe', () => { + const syncs = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}); + expect(syncs.length).to.equal(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('case 2 -> allow pixel with static sync', () => { + const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }); + expect(syncs.length).to.be.equal(4); + expect(syncs[0].type).to.be.equal('image'); + expect(syncs[1].type).to.be.equal('image'); + expect(syncs[2].type).to.be.equal('image'); + expect(syncs[3].type).to.be.equal('image'); + }); + }); }); From a322c34ecb676dbfc8af63685f05d62eb3f80ff6 Mon Sep 17 00:00:00 2001 From: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Date: Wed, 11 May 2022 01:39:08 +0800 Subject: [PATCH 14/17] Jixie Bid Adapter: send device info (#8397) * 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. * to support sending object of device info to backend. yes the backend can handle it even though device was a string so far --- modules/jixieBidAdapter.js | 13 ++++++++++--- test/spec/modules/jixieBidAdapter_spec.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 700d3276e06..90ea17395f7 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,4 @@ -import { logWarn, parseUrl, deepAccess, isArray } from '../src/utils.js'; +import { logWarn, parseUrl, deepAccess, isArray, getDNT } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -60,9 +60,16 @@ function fetchIds_() { return ret; } +// device in the payload had been a simple string ('desktop', 'mobile') +// Now changed to an object. yes the backend is able to handle it. function getDevice_() { - return ((/(ios|ipod|ipad|iphone|android|blackberry|iemobile|opera mini|webos)/i).test(navigator.userAgent) - ? 'mobile' : 'desktop'); + const device = config.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] : ''; + return device; } function pingTracking_(endpointOverride, qpobj) { diff --git a/test/spec/modules/jixieBidAdapter_spec.js b/test/spec/modules/jixieBidAdapter_spec.js index 7af0372c22c..0a6ba272970 100644 --- a/test/spec/modules/jixieBidAdapter_spec.js +++ b/test/spec/modules/jixieBidAdapter_spec.js @@ -268,6 +268,23 @@ describe('jixie Adapter', function () { expect(payload.pricegranularity).to.deep.include(content); }); + it('it should popular the device info when it is available', function () { + let getConfigStub = sinon.stub(config, 'getConfig'); + let content = {w: 500, h: 400}; + getConfigStub.callsFake(function fakeFn(prop) { + if (prop == 'device') { + return content; + } + return null; + }); + const oneSpecialBidReq = Object.assign({}, bidRequests_[0]); + const request = spec.buildRequests([oneSpecialBidReq], bidderRequest_); + const payload = JSON.parse(request.data); + getConfigStub.restore(); + expect(payload.device).to.have.property('ua', navigator.userAgent); + expect(payload.device).to.deep.include(content); + }); + it('should populate eids when supported userIds are available', function () { const oneSpecialBidReq = Object.assign({}, bidRequests_[0], { userId: { From 87b7dc8af973cadd1aaa1c9e21c07d98fad5b7dc Mon Sep 17 00:00:00 2001 From: David Carver <54326287+david-carver@users.noreply.github.com> Date: Tue, 10 May 2022 13:01:12 -0500 Subject: [PATCH 15/17] LKQD Bid Adapter: remove device ip bug (#8400) * LKQD: remove device ip from request * LKQD: remove device IP test --- modules/lkqdBidAdapter.js | 4 +--- test/spec/modules/lkqdBidAdapter_spec.js | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 275ab38915d..e58c643f4f0 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -36,7 +36,6 @@ export const spec = { const serverRequestObjects = []; const UTC_OFFSET = new Date().getTimezoneOffset(); const UA = navigator.userAgent; - const IP = navigator.ip ? navigator.ip : 'prebid.js'; const USP = BIDDER_REQUEST.uspConsent || null; const REFERER = BIDDER_REQUEST.refererInfo ? new URL(BIDDER_REQUEST.refererInfo.referer).hostname : window.location.hostname; const BIDDER_GDPR = BIDDER_REQUEST.gdprConsent && BIDDER_REQUEST.gdprConsent.gdprApplies ? 1 : null; @@ -60,8 +59,7 @@ export const spec = { ua: UA, geo: { utcoffset: UTC_OFFSET - }, - ip: IP + } }, user: { ext: {} diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 6e2c7fe8e2c..7fee9bf6e41 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -226,7 +226,6 @@ describe('lkqdBidAdapter', () => { 'utcoffset': -420, }, 'dnt': 0, - 'ip': '184.103.177.205', 'ifa': 'f4254ada-4174-6cfd-83c7-2f999c457c1d' }, 'user': { From 8a4fd447686e7f4bef04df9103b6d905f33e6d8e Mon Sep 17 00:00:00 2001 From: David Spohr Date: Tue, 10 May 2022 20:57:44 +0200 Subject: [PATCH 16/17] cpex Id System: initial release (#8364) * Adds cpexIdSystem * Fixes cpexIdSystem tests * Added markdown document * Remove unnecessary storage config --- modules/.submodules.json | 1 + modules/cpexIdSystem.js | 49 ++++++++++++++++++++++++++ modules/cpexIdSystem.md | 27 ++++++++++++++ modules/userId/userId.md | 2 ++ test/spec/modules/cpexIdSystem_spec.js | 38 ++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 modules/cpexIdSystem.js create mode 100644 modules/cpexIdSystem.md create mode 100644 test/spec/modules/cpexIdSystem_spec.js diff --git a/modules/.submodules.json b/modules/.submodules.json index b0f19aa0bae..7e21fc0e33b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -7,6 +7,7 @@ "amxIdSystem", "britepoolIdSystem", "connectIdSystem", + "cpexIdSystem", "criteoIdSystem", "dacIdSystem", "deepintentDpesIdSystem", diff --git a/modules/cpexIdSystem.js b/modules/cpexIdSystem.js new file mode 100644 index 00000000000..4600601cb11 --- /dev/null +++ b/modules/cpexIdSystem.js @@ -0,0 +1,49 @@ +/** + * This module adds 'caid' to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/cpexIdSystem + * @requires module:modules/userId + */ + +import { submodule } from '../src/hook.js' +import { getStorageManager } from '../src/storageManager.js' + +window.top.cpexIdVersion = '0.0.3' + +// Returns StorageManager +export const storage = getStorageManager({ gvlid: 570, moduleName: 'cpexId' }) + +// Returns the id string from either cookie or localstorage +const getId = () => { return storage.getCookie('caid') || storage.getDataFromLocalStorage('caid') } + +/** @type {Submodule} */ +export const cpexIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'cpexId', + /** + * Vendor ID of Czech Publisher Exchange + * @type {Number} + */ + gvlid: 570, + /** + * decode the stored id value for passing to bid requests + * @function decode + * @param {(Object|string)} value + * @returns {(Object|undefined)} + */ + decode (value) { return { cpexId: getId() } }, + /** + * 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, consentData) { return { cpexId: getId() } } +} + +submodule('userId', cpexIdSubmodule) diff --git a/modules/cpexIdSystem.md b/modules/cpexIdSystem.md new file mode 100644 index 00000000000..8aceb7fe4ec --- /dev/null +++ b/modules/cpexIdSystem.md @@ -0,0 +1,27 @@ +## CPEx User ID Submodule + +CPExID is provided by [Czech Publisher Exchange](https://www.cpex.cz/), or CPEx. It is a user ID for ad targeting by using first party cookie, or localStorage mechanism. Please contact CPEx before using this ID. + +## Building Prebid with CPExID Support + +First, make sure to add the cpexId to your Prebid.js package with: + +``` +gulp build --modules=cpexIdSystem +``` + +The following configuration parameters are available: + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'cpexId' + }] + } +}); +``` + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | The name of this module. | `"cpexId"` | diff --git a/modules/userId/userId.md b/modules/userId/userId.md index bc0dad6462a..ed5b0adcd71 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -111,6 +111,8 @@ pbjs.setConfig({ name: '_criteoId', expires: 1 } + }, { + name: "cpexId" }, { name: 'mwOpenLinkId', params: { diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/cpexIdSystem_spec.js new file mode 100644 index 00000000000..ee203e6c83a --- /dev/null +++ b/test/spec/modules/cpexIdSystem_spec.js @@ -0,0 +1,38 @@ +import { cpexIdSubmodule, storage } from 'modules/cpexIdSystem.js'; + +describe('cpexId module', function () { + let getCookieStub; + + beforeEach(function (done) { + getCookieStub = sinon.stub(storage, 'getCookie'); + done(); + }); + + afterEach(function () { + getCookieStub.restore(); + }); + + const cookieTestCasesForEmpty = [undefined, null, ''] + + describe('getId()', function () { + it('should return the uid when it exists in cookie', function () { + getCookieStub.withArgs('caid').returns('cpexIdTest'); + const id = cpexIdSubmodule.getId(); + expect(id).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + }); + + cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { + getCookieStub.withArgs('caid').returns(testCase); + const id = cpexIdSubmodule.getId(); + expect(id).to.be.deep.equal({ cpexId: null }); + })); + }); + + describe('decode()', function () { + it('should return the uid when it exists in cookie', function () { + getCookieStub.withArgs('caid').returns('cpexIdTest'); + const decoded = cpexIdSubmodule.decode(); + expect(decoded).to.be.deep.equal({ cpexId: 'cpexIdTest' }); + }); + }); +}); From 04fefef1633a4b1d663f2f5e2af727874c54658a Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 10 May 2022 12:01:31 -0700 Subject: [PATCH 17/17] UserID module: graceful handling of exceptions from ID submodules (#8401) * Do not error out when malformed JSON is set in cookies * UserID module: graceful handling of exceptions from ID submodules --- modules/id5IdSystem.js | 15 ++++++-- modules/userId/index.js | 19 +++++++--- src/utils.js | 11 ++++++ test/spec/modules/id5IdSystem_spec.js | 17 ++++++++- test/spec/modules/userId_spec.js | 51 +++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 9 deletions(-) diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index f2143c1cced..b57be00d3ac 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -5,7 +5,16 @@ * @requires module:modules/userId */ -import { deepAccess, logInfo, deepSetValue, logError, isEmpty, isEmptyStr, logWarn } from '../src/utils.js'; +import { + deepAccess, + logInfo, + deepSetValue, + logError, + isEmpty, + isEmptyStr, + logWarn, + safeJSONParse +} from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; @@ -24,7 +33,7 @@ const LOG_PREFIX = 'User ID - ID5 submodule: '; // cookie in the array is the most preferred to use const LEGACY_COOKIE_NAMES = [ 'pbjs-id5id', 'id5id.1st', 'id5id' ]; -const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); +export const storage = getStorageManager({gvlid: GVLID, moduleName: MODULE_NAME}); /** @type {Submodule} */ export const id5IdSubmodule = { @@ -253,7 +262,7 @@ function getLegacyCookieSignature() { let legacyStoredValue; LEGACY_COOKIE_NAMES.forEach(function(cookie) { if (storage.getCookie(cookie)) { - legacyStoredValue = JSON.parse(storage.getCookie(cookie)) || legacyStoredValue; + legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; } }); return (legacyStoredValue && legacyStoredValue.signature) || ''; diff --git a/modules/userId/index.js b/modules/userId/index.js index 045786f59ec..5aed9b808f6 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -423,7 +423,7 @@ function processSubmoduleCallbacks(submodules, cb) { }, submodules.length); } submodules.forEach(function (submodule) { - submodule.callback(function callbackCompleted(idObj) { + function callbackCompleted(idObj) { // if valid, id data should be saved to cookie/html storage if (idObj) { if (submodule.config.storage) { @@ -435,8 +435,13 @@ function processSubmoduleCallbacks(submodules, cb) { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } done(); - }); - + } + try { + submodule.callback(callbackCompleted); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + done(); + } // clear callback, this prop is used to test if all submodule callbacks are complete below submodule.callback = undefined; }); @@ -881,8 +886,12 @@ function initSubmodules(dest, submodules, consentData, forceRefresh = false) { setStoredConsentData(consentData); const initialized = userIdModules.reduce((carry, submodule) => { - populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); - carry.push(submodule); + try { + populateSubmoduleId(submodule, consentData, storedConsentData, forceRefresh); + carry.push(submodule); + } catch (e) { + logError(`Error in userID module '${submodule.submodule.name}':`, e); + } return carry; }, []); if (initialized.length) { diff --git a/src/utils.js b/src/utils.js index 33755a4fb82..3109b52c4df 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1364,3 +1364,14 @@ export function cyrb53Hash(str, seed = 0) { 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 + * @returns {any} + */ +export function safeJSONParse(data) { + try { + return JSON.parse(data); + } catch (e) {} +} diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 74c2b053ce1..a54542f7278 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -5,7 +5,7 @@ import { ID5_PRIVACY_STORAGE_NAME, ID5_STORAGE_NAME, id5IdSubmodule, - nbCacheName, + nbCacheName, storage, storeInLocalStorage, storeNbInCache, } from 'modules/id5IdSystem.js'; @@ -310,6 +310,21 @@ describe('ID5 ID System', function() { request.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); expect(getFromLocalStorage(ID5_PRIVACY_STORAGE_NAME)).to.be.null; }); + + describe('when legacy cookies are set', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.sandbox.create(); + sandbox.stub(storage, 'getCookie'); + }); + afterEach(() => { + sandbox.restore(); + }); + it('should not throw if malformed JSON is forced into cookies', () => { + storage.getCookie.callsFake(() => ' Not JSON '); + id5IdSubmodule.getId(getId5FetchConfig()); + }); + }) }); describe('Request Bids Hook', function() { diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 48d7ea6d53e..03924e320c0 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -507,6 +507,57 @@ describe('User ID', function () { }); }); + describe('when ID systems throw errors', () => { + function mockIdSystem(name) { + return { + name, + decode: function(value) { + return { + [name]: value + }; + }, + getId: sinon.stub().callsFake(() => ({id: name})) + }; + } + let id1, id2; + beforeEach(() => { + id1 = mockIdSystem('mock1'); + id2 = mockIdSystem('mock2'); + init(config); + setSubmoduleRegistry([id1, id2]); + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [{ + name: 'mock1', + storage: {name: 'mock1', type: 'cookie'} + }, { + name: 'mock2', + storage: {name: 'mock2', type: 'cookie'} + }] + } + }) + }); + afterEach(() => { + config.resetConfig(); + }) + Object.entries({ + 'in init': () => id1.getId.callsFake(() => { throw new Error() }), + 'in callback': () => { + const mockCallback = sinon.stub().callsFake(() => { throw new Error() }); + id1.getId.callsFake(() => ({callback: mockCallback})) + } + }).forEach(([t, setup]) => { + describe(`${t}`, () => { + beforeEach(setup); + it('should still retrieve IDs that do not throw', () => { + return getGlobal().getUserIdsAsync().then((uid) => { + expect(uid.mock2).to.not.be.undefined; + }) + }); + }) + }) + }); it('pbjs.refreshUserIds updates submodules', function(done) { let sandbox = sinon.createSandbox(); let mockIdCallback = sandbox.stub().returns({id: {'MOCKID': '1111'}});