From 719568da269587d7b9b3e1cd32b365f9779859b4 Mon Sep 17 00:00:00 2001 From: Marcin Wrobel Date: Fri, 13 Feb 2026 16:39:37 +0100 Subject: [PATCH 01/50] WIP deduplicate request --- modules/openxBidAdapter.js | 28 ++---------- test/spec/modules/openxBidAdapter_spec.js | 55 +++++++++++------------ 2 files changed, 30 insertions(+), 53 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 2da04ad3e38..3f54d889b3c 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -162,39 +162,17 @@ function isBidRequestValid(bidRequest) { } function buildRequests(bidRequests, bidderRequest) { - const videoRequests = bidRequests.filter(bidRequest => isVideoBidRequest(bidRequest)); - const bannerAndNativeRequests = bidRequests.filter(bidRequest => isBannerBidRequest(bidRequest) || isNativeBidRequest(bidRequest)) - // In case of multi-format bids remove `video` from mediaTypes as for video a separate bid request is built - .map(bid => ({...bid, mediaTypes: {...bid.mediaTypes, video: undefined}})); - - const requests = bannerAndNativeRequests.length ? [createRequest(bannerAndNativeRequests, bidderRequest, null)] : []; - videoRequests.forEach(bid => { - requests.push(createRequest([bid], bidderRequest, VIDEO)); - }); - return requests; + return [createRequest(bidRequests, bidderRequest)]; } -function createRequest(bidRequests, bidderRequest, mediaType) { +function createRequest(bidRequests, bidderRequest) { return { method: 'POST', url: config.getConfig('openxOrtbUrl') || REQUEST_URL, - data: converter.toORTB({bidRequests, bidderRequest, context: {mediaType}}) + data: converter.toORTB({bidRequests, bidderRequest}) } } -function isVideoBidRequest(bidRequest) { - return utils.deepAccess(bidRequest, 'mediaTypes.video'); -} - -function isNativeBidRequest(bidRequest) { - return utils.deepAccess(bidRequest, 'mediaTypes.native'); -} - -function isBannerBidRequest(bidRequest) { - const isNotVideoOrNativeBid = !isVideoBidRequest(bidRequest) && !isNativeBidRequest(bidRequest) - return utils.deepAccess(bidRequest, 'mediaTypes.banner') || isNotVideoOrNativeBid; -} - function interpretResponse(resp, req) { if (!resp.body) { resp.body = {nbr: 0}; diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index dfcdecdf586..eac26f89e82 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -416,16 +416,12 @@ describe('OpenxRtbAdapter', function () { playerSize: [640, 480] }; const requests = spec.buildRequests([multiformat], mockBidderRequest); - expect(requests).to.have.length(2); + expect(requests).to.have.length(1); expect(requests[0].data.imp).to.have.length(1); expect(requests[0].data.imp[0].banner).to.exist; - expect(requests[0].data.imp[0].video).to.not.exist; expect(requests[0].data.imp[0].native).to.not.exist; - expect(requests[1].data.imp).to.have.length(1); - expect(requests[1].data.imp[0].banner).to.not.exist; - expect(requests[1].data.imp[0].native).to.not.exist; if (FEATURES.VIDEO) { - expect(requests[1].data.imp[0].video).to.exist; + expect(requests[0].data.imp[0].video).to.exist; } }) @@ -454,21 +450,34 @@ describe('OpenxRtbAdapter', function () { sizes: [[300, 250]] } const requests = spec.buildRequests([multiformat], mockBidderRequest); - expect(requests).to.have.length(2); + expect(requests).to.have.length(1); expect(requests[0].data.imp).to.have.length(1); expect(requests[0].data.imp[0].banner).to.exist; - expect(requests[0].data.imp[0].video).to.not.exist; if (FEATURES.NATIVE) { expect(requests[0].data.imp[0].native).to.exist; } - expect(requests[1].data.imp).to.have.length(1); - expect(requests[1].data.imp[0].banner).to.not.exist; - expect(requests[1].data.imp[0].native).to.not.exist; if (FEATURES.VIDEO) { - expect(requests[1].data.imp[0].video).to.exist; + expect(requests[0].data.imp[0].video).to.exist; } }) + it('should send all bid requests in a single HTTP request', function () { + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request).to.have.length(1); + expect(request[0].data.imp).to.have.length(2); + expect(request[0].data.imp[0].banner).to.exist; + if (FEATURES.VIDEO) { + expect(request[0].data.imp[1].video).to.exist; + } + }); + + it('should send banner, video and native bids in a single HTTP request', function () { + const allBids = [...bidRequestsWithMediaTypes, nativeBidRequest]; + const request = spec.buildRequests(allBids, mockBidderRequest); + expect(request).to.have.length(1); + expect(request[0].data.imp).to.have.length(3); + }); + it('should send bid request to openx url via POST', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal(REQUEST_URL); @@ -488,13 +497,12 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); - expect(request[1].data.ext.platform).to.equal(bidRequestsWithMediaTypes[1].params.platform); }); it('should send openx adunit codes', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); - expect(request[1].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); + expect(request[0].data.imp[1].tagid).to.equal(bidRequestsWithMediaTypes[1].params.unit); }); it('should send out custom params on bids that have customParams specified', function () { @@ -904,7 +912,6 @@ describe('OpenxRtbAdapter', function () { it('should send a signal to specify that US Privacy applies to this request', async function () { const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.us_privacy).to.equal('1YYN'); - expect(request[1].data.regs.ext.us_privacy).to.equal('1YYN'); }); it('should not send the regs object, when consent string is undefined', async function () { @@ -946,21 +953,18 @@ describe('OpenxRtbAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(1); - expect(request[1].data.regs.ext.gdpr).to.equal(1); }); it('should send the consent string', async function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); it('should send the addtlConsent string', async function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); - expect(request[1].data.user.ext.ConsentedProvidersSettings.consented_providers).to.equal(bidderRequest.gdprConsent.addtlConsent); }); it('should send a signal to specify that GDPR does not apply to this request', async function () { @@ -968,7 +972,6 @@ describe('OpenxRtbAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs.ext.gdpr).to.equal(0); - expect(request[1].data.regs.ext.gdpr).to.equal(0); }); it('when GDPR application is undefined, should not send a signal to specify whether GDPR applies to this request, ' + @@ -978,7 +981,6 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.regs?.ext?.gdpr).to.not.be.ok; expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); - expect(request[1].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); it('when consent string is undefined, should not send the consent string, ', async function () { @@ -986,7 +988,7 @@ describe('OpenxRtbAdapter', function () { bidderRequest.bids = bidRequests; const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); expect(request[0].data.imp[0].ext.consent).to.equal(undefined); - expect(request[1].data.imp[0].ext.consent).to.equal(undefined); + expect(request[0].data.imp[1].ext.consent).to.equal(undefined); }); }); @@ -1002,8 +1004,6 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data.regs.gpp).to.equal('test-gpp-string'); expect(request[0].data.regs.gpp_sid).to.deep.equal([6]); - expect(request[1].data.regs.gpp).to.equal('test-gpp-string'); - expect(request[1].data.regs.gpp_sid).to.deep.equal([6]); }); it('should not send GPP string and GPP section IDs in bid request when not available', async function () { @@ -1014,8 +1014,6 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(bidRequests, bidderRequest); expect(request[0].data.regs.gpp).to.not.exist; expect(request[0].data.regs.gpp_sid).to.not.exist; - expect(request[1].data.regs.gpp).to.not.exist; - expect(request[1].data.regs.gpp_sid).to.not.exist; }); }); }); @@ -1251,7 +1249,7 @@ describe('OpenxRtbAdapter', function () { context('video', function () { it('should send bid request with a mediaTypes specified with video type', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request[1].data.imp[0]).to.have.any.keys(VIDEO); + expect(request[0].data.imp[1]).to.have.any.keys(VIDEO); }); it('Update imp.video with OpenRTB options from mimeTypes and params', function() { @@ -1288,8 +1286,8 @@ describe('OpenxRtbAdapter', function () { h: 250 }; const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests).to.have.lengthOf(2); - expect(requests[1].data.imp[0].video).to.deep.equal(expected); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.imp[0].video).to.deep.equal(expected); }); }); } @@ -1573,6 +1571,7 @@ describe('OpenxRtbAdapter', function () { crid: 'test-creative-id', dealid: 'test-deal-id', adm: '', + mtype: 2, }] }], cur: 'AUS' From a9c8b431133ba4ef5b11e0252117e32f47b4e3d3 Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Fri, 20 Feb 2026 08:21:47 +0100 Subject: [PATCH 02/50] Cleanup of tests and buildRequest function --- modules/openxBidAdapter.js | 8 ++------ test/spec/modules/openxBidAdapter_spec.js | 13 +------------ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 3f54d889b3c..4c249512e8c 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -162,15 +162,11 @@ function isBidRequestValid(bidRequest) { } function buildRequests(bidRequests, bidderRequest) { - return [createRequest(bidRequests, bidderRequest)]; -} - -function createRequest(bidRequests, bidderRequest) { - return { + return [{ method: 'POST', url: config.getConfig('openxOrtbUrl') || REQUEST_URL, data: converter.toORTB({bidRequests, bidderRequest}) - } + }]; } function interpretResponse(resp, req) { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index eac26f89e82..fe85a803d61 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -461,16 +461,6 @@ describe('OpenxRtbAdapter', function () { } }) - it('should send all bid requests in a single HTTP request', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); - expect(request).to.have.length(1); - expect(request[0].data.imp).to.have.length(2); - expect(request[0].data.imp[0].banner).to.exist; - if (FEATURES.VIDEO) { - expect(request[0].data.imp[1].video).to.exist; - } - }); - it('should send banner, video and native bids in a single HTTP request', function () { const allBids = [...bidRequestsWithMediaTypes, nativeBidRequest]; const request = spec.buildRequests(allBids, mockBidderRequest); @@ -493,7 +483,6 @@ describe('OpenxRtbAdapter', function () { it('should send platform id, if available', function () { bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397'; const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); @@ -571,7 +560,7 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.imp[0].bidfloorcur).to.equal('USD'); }); - it('should send not send floors', function () { + it('should not send floors', function () { adServerCurrencyStub.returns('EUR'); const bidRequest = Object.assign({}, bidRequestsWithMediaTypes[0], From 27eff870d0440c7bd9b5ba93911f2ac0f2df386d Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Fri, 20 Feb 2026 08:23:53 +0100 Subject: [PATCH 03/50] Improve tests --- test/spec/modules/openxBidAdapter_spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index fe85a803d61..2d498ab5479 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -466,6 +466,13 @@ describe('OpenxRtbAdapter', function () { const request = spec.buildRequests(allBids, mockBidderRequest); expect(request).to.have.length(1); expect(request[0].data.imp).to.have.length(3); + expect(request[0].data.imp[0].banner).to.exist; + if (FEATURES.VIDEO) { + expect(request[0].data.imp[1].video).to.exist; + } + if (FEATURES.NATIVE) { + expect(request[0].data.imp[2].native).to.exist; + } }); it('should send bid request to openx url via POST', function () { @@ -481,10 +488,12 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.ext.platformId).to.be.undefined; }); - it('should send platform id, if available', function () { + it('should send platform id from first bid, if available', function () { bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '2cabba9e-cafe-3665-beef-f00f00f00f00'; const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request.length).to.equal(1); expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); }); From a5e777853d75b2fce4a07e19a87d5cfe3236075e Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Fri, 20 Feb 2026 08:32:57 +0100 Subject: [PATCH 04/50] Revert changing platformID in test --- test/spec/modules/openxBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 2d498ab5479..94e0140fcb1 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -490,7 +490,7 @@ describe('OpenxRtbAdapter', function () { it('should send platform id from first bid, if available', function () { bidRequestsWithMediaTypes[0].params.platform = '1cabba9e-cafe-3665-beef-f00f00f00f00'; - bidRequestsWithMediaTypes[1].params.platform = '2cabba9e-cafe-3665-beef-f00f00f00f00'; + bidRequestsWithMediaTypes[1].params.platform = '51ca3159-abc2-4035-8e00-fe26eaa09397'; const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request.length).to.equal(1); From 13172aa8e6743e0bb3dd23e7033b96847f79e9f6 Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Fri, 20 Feb 2026 10:23:58 +0100 Subject: [PATCH 05/50] Add additional tests --- test/spec/modules/openxBidAdapter_spec.js | 46 ++++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 94e0140fcb1..4f3f2bfd8de 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -468,10 +468,12 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.imp).to.have.length(3); expect(request[0].data.imp[0].banner).to.exist; if (FEATURES.VIDEO) { - expect(request[0].data.imp[1].video).to.exist; + const videoImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[1].bidId); + expect(videoImp.video).to.exist; } if (FEATURES.NATIVE) { - expect(request[0].data.imp[2].native).to.exist; + const nativeImp = request[0].data.imp.find(imp => imp.id === nativeBidRequest.bidId); + expect(nativeImp.native).to.exist; } }); @@ -497,6 +499,13 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data.ext.platform).to.equal(bidRequestsWithMediaTypes[0].params.platform); }); + it('should send delDomain from first bid when bids have different delDomains', function () { + bidRequestsWithMediaTypes[0].params.delDomain = 'domain-a.test'; + bidRequestsWithMediaTypes[1].params.delDomain = 'domain-b.test'; + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); + expect(request[0].data.ext.delDomain).to.equal('domain-a.test'); + }); + it('should send openx adunit codes', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.imp[0].tagid).to.equal(bidRequestsWithMediaTypes[0].params.unit); @@ -1690,6 +1699,39 @@ describe('OpenxRtbAdapter', function () { }); } + context('when multiple bid requests (banner + video) are in one request and both bids are returned', function() { + it('should correctly return both banner and video bids', function () { + const bidRequestConfigs = [ + { + bidder: 'openx', params: { unit: '1', delDomain: 'test-del-domain' }, + adUnitCode: 'au-1', mediaTypes: { banner: { sizes: [[300, 250]] } }, + bidId: 'bid-id-banner', bidderRequestId: 'br-1', auctionId: 'a-1' + }, + { + bidder: 'openx', params: { unit: '2', delDomain: 'test-del-domain' }, + adUnitCode: 'au-2', mediaTypes: { video: { playerSize: [640, 480] } }, + bidId: 'bid-id-video', bidderRequestId: 'br-1', auctionId: 'a-1' + } + ]; + const bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + const bidResponse = { + seatbid: [{ + bid: [ + { impid: 'bid-id-banner', price: 1, adm: '
ad
', mtype: 1 }, + { impid: 'bid-id-video', price: 2, adm: '', mtype: 2 } + ] + }], + cur: 'USD' + }; + const result = spec.interpretResponse({ body: bidResponse }, bidRequest); + expect(result.bids).to.have.length(2); + expect(result.bids[0].mediaType).to.equal(BANNER); + expect(result.bids[1].mediaType).to.equal(VIDEO); + expect(result.bids[0].requestId).to.equal('bid-id-banner'); + expect(result.bids[1].requestId).to.equal('bid-id-video'); + }); + }); + if (FEATURES.NATIVE) { context('when native request and the response is a native', function() { beforeEach(function () { From 163229ccae022d6c49f4ade0c50523f22b0cbd1c Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Mon, 23 Feb 2026 11:20:49 +0100 Subject: [PATCH 06/50] Search banner imp by id in test --- test/spec/modules/openxBidAdapter_spec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 4f3f2bfd8de..349248ac2c6 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -464,13 +464,18 @@ describe('OpenxRtbAdapter', function () { it('should send banner, video and native bids in a single HTTP request', function () { const allBids = [...bidRequestsWithMediaTypes, nativeBidRequest]; const request = spec.buildRequests(allBids, mockBidderRequest); + expect(request).to.have.length(1); expect(request[0].data.imp).to.have.length(3); - expect(request[0].data.imp[0].banner).to.exist; + + const bannerImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[1].bidId); + expect(bannerImp.banner).to.exist; + if (FEATURES.VIDEO) { const videoImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[1].bidId); expect(videoImp.video).to.exist; } + if (FEATURES.NATIVE) { const nativeImp = request[0].data.imp.find(imp => imp.id === nativeBidRequest.bidId); expect(nativeImp.native).to.exist; From f403d448c3a6cecec3550ee849f0ed57775f6cba Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Mon, 23 Feb 2026 11:21:01 +0100 Subject: [PATCH 07/50] Format tests --- test/spec/modules/openxBidAdapter_spec.js | 52 ++++++++++++++++++----- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 349248ac2c6..458d97c3545 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -467,8 +467,8 @@ describe('OpenxRtbAdapter', function () { expect(request).to.have.length(1); expect(request[0].data.imp).to.have.length(3); - - const bannerImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[1].bidId); + + const bannerImp = request[0].data.imp.find(imp => imp.id === bidRequestsWithMediaTypes[0].bidId); expect(bannerImp.banner).to.exist; if (FEATURES.VIDEO) { @@ -1708,22 +1708,54 @@ describe('OpenxRtbAdapter', function () { it('should correctly return both banner and video bids', function () { const bidRequestConfigs = [ { - bidder: 'openx', params: { unit: '1', delDomain: 'test-del-domain' }, - adUnitCode: 'au-1', mediaTypes: { banner: { sizes: [[300, 250]] } }, - bidId: 'bid-id-banner', bidderRequestId: 'br-1', auctionId: 'a-1' + bidder: 'openx', + params: { + unit: '1', + delDomain: 'test-del-domain' + }, + adUnitCode: 'au-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: 'bid-id-banner', + bidderRequestId: 'br-1', + auctionId: 'a-1' }, { - bidder: 'openx', params: { unit: '2', delDomain: 'test-del-domain' }, - adUnitCode: 'au-2', mediaTypes: { video: { playerSize: [640, 480] } }, - bidId: 'bid-id-video', bidderRequestId: 'br-1', auctionId: 'a-1' + bidder: 'openx', + params: { + unit: '2', + delDomain: 'test-del-domain' + }, + adUnitCode: 'au-2', + mediaTypes: { + video: { + playerSize: [640, 480] + } + }, + bidId: 'bid-id-video', + bidderRequestId: 'br-1', + auctionId: 'a-1' } ]; const bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; const bidResponse = { seatbid: [{ bid: [ - { impid: 'bid-id-banner', price: 1, adm: '
ad
', mtype: 1 }, - { impid: 'bid-id-video', price: 2, adm: '', mtype: 2 } + { + impid: 'bid-id-banner', + price: 1, + adm: '
ad
', + mtype: 1 + }, + { + impid: 'bid-id-video', + price: 2, + adm: '', + mtype: 2 + } ] }], cur: 'USD' From 8a91fe78ab31ff4d96591431a8db043258ce0c16 Mon Sep 17 00:00:00 2001 From: Rafal Sieczka Date: Thu, 12 Mar 2026 09:39:20 +0100 Subject: [PATCH 08/50] Fix lint errors --- modules/openxBidAdapter.js | 2 +- test/spec/modules/openxBidAdapter_spec.js | 64 +++++++++++------------ 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index ddb1c30fe73..7c66915314b 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -165,7 +165,7 @@ function buildRequests(bidRequests, bidderRequest) { return [{ method: 'POST', url: config.getConfig('openxOrtbUrl') || REQUEST_URL, - data: converter.toORTB({bidRequests, bidderRequest}) + data: converter.toORTB({ bidRequests, bidderRequest }) }]; } diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 1dbc0229f37..449429286f4 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -1712,53 +1712,53 @@ describe('OpenxRtbAdapter', function () { it('should correctly return both banner and video bids', function () { const bidRequestConfigs = [ { - bidder: 'openx', - params: { - unit: '1', - delDomain: 'test-del-domain' + bidder: 'openx', + params: { + unit: '1', + delDomain: 'test-del-domain' }, adUnitCode: 'au-1', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } + mediaTypes: { + banner: { + sizes: [[300, 250]] + } }, - bidId: 'bid-id-banner', - bidderRequestId: 'br-1', + bidId: 'bid-id-banner', + bidderRequestId: 'br-1', auctionId: 'a-1' }, { - bidder: 'openx', - params: { - unit: '2', - delDomain: 'test-del-domain' + bidder: 'openx', + params: { + unit: '2', + delDomain: 'test-del-domain' }, - adUnitCode: 'au-2', - mediaTypes: { - video: { - playerSize: [640, 480] - } + adUnitCode: 'au-2', + mediaTypes: { + video: { + playerSize: [640, 480] + } }, - bidId: 'bid-id-video', - bidderRequestId: 'br-1', + bidId: 'bid-id-video', + bidderRequestId: 'br-1', auctionId: 'a-1' } ]; - const bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; + const bidRequest = spec.buildRequests(bidRequestConfigs, { refererInfo: {} })[0]; const bidResponse = { seatbid: [{ bid: [ - { - impid: 'bid-id-banner', - price: 1, - adm: '
ad
', - mtype: 1 + { + impid: 'bid-id-banner', + price: 1, + adm: '
ad
', + mtype: 1 }, - { - impid: 'bid-id-video', - price: 2, - adm: '', - mtype: 2 + { + impid: 'bid-id-video', + price: 2, + adm: '', + mtype: 2 } ] }], From dc526c7e20083205166214fb94587b838806bb33 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 12 Mar 2026 09:07:43 -0400 Subject: [PATCH 09/50] Mediafuse adapter update: resubmit refactor (#14581) * MediaFuse Adapter: Fixed Test Issues * Fixed Lint Issue * fix video vastUrl not set on openrtb2 endpoint * Fixed lint errors * Fixed PR Flagged Issues --------- Co-authored-by: unknown --- modules/mediafuseBidAdapter.js | 1766 +++++----- modules/mediafuseBidAdapter.md | 8 +- test/spec/modules/mediafuseBidAdapter_spec.js | 2942 +++++++++-------- 3 files changed, 2484 insertions(+), 2232 deletions(-) diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index da98d88fa81..135ee52a880 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -1,8 +1,13 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; import { createTrackPixelHtml, deepAccess, - deepClone, - getBidRequest, + deepSetValue, getParameterByName, isArray, isArrayOfNums, @@ -16,1087 +21,982 @@ import { logMessage, 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 { ADPOD, BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { INSTREAM, OUTSTREAM } from '../src/video.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { bidderSettings } from '../src/bidderSettings.js'; -import { hasPurpose1Consent } from '../src/utils/gdpr.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { APPNEXUS_CATEGORY_MAPPING } from '../libraries/categoryTranslationMapping/index.js'; import { getANKewyordParamFromMaps, getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; -import { convertCamelToUnderscore, fill } from '../libraries/appnexusUtils/anUtils.js'; +import { convertCamelToUnderscore } 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 - */ - const BIDDER_CODE = 'mediafuse'; -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']; -const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; -const APP_DEVICE_PARAMS = ['device_id']; // appid is collected separately +const GVLID = 32; +const ENDPOINT_URL_NORMAL = 'https://ib.adnxs.com/openrtb2/prebidjs'; +const ENDPOINT_URL_SIMPLE = 'https://ib.adnxs-simple.com/openrtb2/prebidjs'; +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; -const VIDEO_MAPPING = { - playback_method: { - 'unknown': 0, - 'auto_play_sound_on': 1, - 'auto_play_sound_off': 2, - 'click_to_play': 3, - 'mouse_over': 4, - 'auto_play_sound_unknown': 5 - }, - context: { - 'unknown': 0, - 'pre_roll': 1, - 'mid_roll': 2, - 'post_roll': 3, - 'outstream': 4, - 'in-banner': 5 - } +const DEBUG_QUERY_PARAM_MAP = { + 'apn_debug_enabled': 'enabled', + 'apn_debug_dongle': 'dongle', + 'apn_debug_member_id': 'member_id', + 'apn_debug_timeout': 'debug_timeout' }; -const NATIVE_MAPPING = { - body: 'description', - body2: 'desc2', - cta: 'ctatext', - image: { - serverName: 'main_image', - requiredParams: { required: true } - }, - icon: { - serverName: 'icon', - requiredParams: { required: true } - }, - sponsoredBy: 'sponsored_by', - privacyLink: 'privacy_link', - salePrice: 'saleprice', - displayUrl: 'displayurl' +const RESPONSE_MEDIA_TYPE_MAP = { + 0: BANNER, + 1: VIDEO, + 3: NATIVE }; -const SOURCE = 'pbjs'; -const MAX_IMPS_PER_REQUEST = 15; -const SCRIPT_TAG_START = ' USER_PARAMS.includes(param)) - .forEach((param) => { - const uparam = convertCamelToUnderscore(param); - if (param === 'segments' && isArray(userObjBid.params.user[param])) { - const segs = []; - userObjBid.params.user[param].forEach(val => { - if (isNumber(val)) { - segs.push({ 'id': val }); - } else if (isPlainObject(val)) { - segs.push(val); - } - }); - userObj[uparam] = segs; - } else if (param !== 'segments') { - userObj[uparam] = userObjBid.params.user[param]; - } - }); + if (!imp.banner && deepAccess(bidRequest, 'mediaTypes.banner')) { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes'); + if (isArray(sizes) && sizes.length > 0) { + const size = isArray(sizes[0]) ? sizes[0] : sizes; + imp.banner = { + w: size[0], + h: size[1], + format: sizes.map(s => { + const sz = isArray(s) ? s : [s[0], s[1]]; + return { w: sz[0], h: sz[1] }; + }) + }; + } + } + const bidderParams = bidRequest.params; + const extANData = { + disable_psa: true + }; + // Legacy support for placement_id vs placementId + const placementId = bidderParams.placement_id || bidderParams.placementId; + if (placementId) { + extANData.placement_id = parseInt(placementId, 10); + } else { + const invCode = bidderParams.inv_code || bidderParams.invCode; + if (invCode) { + deepSetValue(imp, 'tagid', invCode); + } } - const appDeviceObjBid = ((bidRequests) || []).find(hasAppDeviceInfo); - let appDeviceObj; - if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { - appDeviceObj = {}; - Object.keys(appDeviceObjBid.params.app) - .filter(param => APP_DEVICE_PARAMS.includes(param)) - .forEach(param => { - appDeviceObj[param] = appDeviceObjBid.params.app[param]; - }); + if (imp.banner) { + // primary_size for legacy support + const firstFormat = deepAccess(imp, 'banner.format.0'); + if (firstFormat) { + extANData.primary_size = firstFormat; + } + if (!imp.banner.api) { + const bannerFrameworks = bidderParams.banner_frameworks || bidderParams.frameworks; + if (isArrayOfNums(bannerFrameworks)) { + extANData.banner_frameworks = bannerFrameworks; + } + } + if (bidderParams.position === 'above') { + imp.banner.pos = 1; + } else if (bidderParams.position === 'below') { + imp.banner.pos = 3; + } } - const appIdObjBid = ((bidRequests) || []).find(hasAppId); - let appIdObj; - if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { - appIdObj = { - appid: appIdObjBid.params.app.id - }; + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + if (gpid) { + extANData.gpid = gpid; } - let debugObj = {}; - const debugObjParams = {}; - const debugCookieName = 'apn_prebid_debug'; - const debugCookie = storage.getCookie(debugCookieName) || null; + if (FEATURES.VIDEO && imp.video) { + if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'instream') { + extANData.require_asset_url = true; + } - if (debugCookie) { - try { - debugObj = JSON.parse(debugCookie); - } catch (e) { - logError('MediaFuse Debug Auction Cookie Error:\n\n' + e); + const videoParams = bidderParams.video; + if (videoParams) { + Object.keys(videoParams) + .filter(param => VIDEO_TARGETING.includes(param)) + .forEach(param => { + if (param === 'frameworks') { + if (isArray(videoParams.frameworks)) { + extANData.video_frameworks = videoParams.frameworks; + } + } else { + imp.video[param] = videoParams[param]; + } + }); } - } else { - const debugBidRequest = ((bidRequests) || []).find(hasDebug); - if (debugBidRequest && debugBidRequest.debug) { - debugObj = debugBidRequest.debug; + + const videoMediaType = deepAccess(bidRequest, 'mediaTypes.video'); + if (videoMediaType && imp.video) { + Object.keys(videoMediaType) + .filter(param => VIDEO_RTB_TARGETING.includes(param)) + .forEach(param => { + switch (param) { + case 'minduration': + case 'maxduration': + if (typeof imp.video[param] !== 'number') imp.video[param] = videoMediaType[param]; + break; + case 'skip': + if (typeof imp.video['skippable'] !== 'boolean') imp.video['skippable'] = (videoMediaType[param] === 1); + break; + case 'skipafter': + if (typeof imp.video['skipoffset'] !== 'number') imp.video['skipoffset'] = videoMediaType[param]; + break; + case 'playbackmethod': + if (typeof imp.video['playback_method'] !== 'number' && isArray(videoMediaType[param])) { + const type = videoMediaType[param][0]; + if (type >= 1 && type <= 4) { + imp.video['playback_method'] = type; + } + } + break; + case 'api': + if (!extANData.video_frameworks && isArray(videoMediaType[param])) { + const apiTmp = videoMediaType[param].map(val => { + const v = (val === 4) ? 5 : (val === 5) ? 4 : val; + return (v >= 1 && v <= 5) ? v : undefined; + }).filter(v => v !== undefined); + extANData.video_frameworks = apiTmp; + } + break; + } + }); } - } - if (debugObj && debugObj.enabled) { - Object.keys(debugObj) - .filter(param => DEBUG_PARAMS.includes(param)) - .forEach(param => { - debugObjParams[param] = debugObj[param]; - }); + if (deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + + const xandrVideoContext = VIDEO_CONTEXT_MAP[deepAccess(bidRequest, 'mediaTypes.video.context')]; + if (xandrVideoContext !== undefined) { + deepSetValue(imp, 'video.ext.appnexus.context', xandrVideoContext); + } + } + if (bidRequest.renderer) { + extANData.custom_renderer_present = true; } - const memberIdBid = ((bidRequests) || []).find(hasMemberId); - const member = memberIdBid ? parseInt(memberIdBid.params.member, 10) : 0; - const schain = bidRequests[0]?.ortb2?.source?.ext?.schain; - const omidSupport = ((bidRequests) || []).find(hasOmidSupport); + Object.entries(OPTIONAL_PARAMS_MAP).forEach(([paramName, ortbName]) => { + if (bidderParams[paramName] !== undefined) { + extANData[ortbName] = bidderParams[paramName]; + } + }); - const payload = { - tags: [...tags], - user: userObj, - sdk: { - source: SOURCE, - version: '$prebid.version$' - }, - schain: schain - }; + Object.keys(bidderParams) + .filter(param => !KNOWN_PARAMS.has(param)) + .forEach(param => { + extANData[convertCamelToUnderscore(param)] = bidderParams[param]; + }); - if (omidSupport) { - payload['iab_support'] = { - omidpn: 'Mediafuse', - omidpv: '$prebid.version$' - }; + // Keywords + if (!isEmpty(bidderParams.keywords)) { + const keywords = getANKewyordParamFromMaps(bidderParams.keywords); + if (keywords && keywords.length > 0) { + extANData.keywords = keywords.map(kw => kw.key + (kw.value ? '=' + kw.value.join(',') : '')).join(','); + } } - if (member > 0) { - payload.member_id = member; + // Floor + const bidFloor = getBidFloor(bidRequest); + if (bidFloor) { + imp.bidfloor = bidFloor; + imp.bidfloorcur = 'USD'; + } else { + delete imp.bidfloor; + delete imp.bidfloorcur; } - if (appDeviceObjBid) { - payload.device = appDeviceObj; + if (Object.keys(extANData).length > 0) { + deepSetValue(imp, 'ext.appnexus', extANData); } - if (appIdObjBid) { - payload.app = appIdObj; + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + // Ensure EIDs from the userId module are included when ortbConverter hasn't already + // populated user.ext.eids (e.g. when ortb2.user.ext.eids is not pre-set by the page). + // All bids in a request share the same user EIDs, so reading from bids[0] is correct. + if (!deepAccess(request, 'user.ext.eids')) { + const bidEids = bidderRequest.bids?.[0]?.userIdAsEids; + if (isArray(bidEids) && bidEids.length > 0) { + deepSetValue(request, 'user.ext.eids', bidEids); + } } - const mfKeywords = config.getConfig('mediafuseAuctionKeywords'); - payload.keywords = getANKeywordParam(bidderRequest?.ortb2, mfKeywords); + if (request.user && request.user.ext && isArray(request.user.ext.eids)) { + request.user.ext.eids.forEach(eid => { + let rtiPartner; + if (eid.source === 'adserver.org') { + rtiPartner = 'TDID'; + } else if (eid.source === 'uidapi.com') { + rtiPartner = 'UID2'; + } + + if (rtiPartner) { + // Set rtiPartner on the first uid's ext object + if (isArray(eid.uids) && eid.uids[0]) { + eid.uids[0] = Object.assign({}, eid.uids[0], { ext: Object.assign({}, eid.uids[0].ext, { rtiPartner }) }); + } + } + }); + } + + const extANData = { + prebid: true, + hb_source: 1, // 1 = client/web-originated header bidding request (Xandr source enum) + sdk: { + version: '$prebid.version$', + source: SOURCE + } + }; - if (config.getConfig('adpod.brandCategoryExclusion')) { - payload.brand_category_uniqueness = true; + if (bidderRequest?.refererInfo) { + const refererinfo = { + rd_ref: bidderRequest.refererInfo.topmostLocation ? encodeURIComponent(bidderRequest.refererInfo.topmostLocation) : '', + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack?.map((url) => encodeURIComponent(url)).join(',') + }; + if (bidderRequest.refererInfo.canonicalUrl) { + refererinfo.rd_can = bidderRequest.refererInfo.canonicalUrl; + } + extANData.referrer_detection = refererinfo; } - if (debugObjParams.enabled) { - payload.debug = debugObjParams; - logInfo('MediaFuse Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); + // App/Device parameters + const expandedBids = bidderRequest?.bids || []; + const memberBid = expandedBids.find(bid => bid.params && bid.params.member); + const commonBidderParams = memberBid ? memberBid.params : (expandedBids[0] && expandedBids[0].params); + + if (commonBidderParams) { + if (commonBidderParams.member) { + // member_id in the request body routes bids to the correct Xandr seat + extANData.member_id = parseInt(commonBidderParams.member, 10); + } + if (commonBidderParams.publisherId) { + deepSetValue(request, 'site.publisher.id', commonBidderParams.publisherId.toString()); + } } - if (bidderRequest && bidderRequest.gdprConsent) { - // note - objects for impbus use underscore instead of camelCase - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies + if (bidderRequest.bids?.some(bid => hasOmidSupport(bid))) { + extANData.iab_support = { + omidpn: 'Mediafuse', + omidpv: '$prebid.version$' }; + } + + deepSetValue(request, 'ext.appnexus', extANData); + + // GDPR / Consent + if (bidderRequest.gdprConsent) { + deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { const ac = bidderRequest.gdprConsent.addtlConsent; - // pull only the ids from the string (after the ~) and convert them to an array of ints const acStr = ac.substring(ac.indexOf('~') + 1); - payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + const addtlConsent = acStr.split('.').map(id => parseInt(id, 10)).filter(id => !isNaN(id)); + if (addtlConsent.length > 0) { + deepSetValue(request, 'user.ext.addtl_consent', addtlConsent); + } } } - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent; + if (bidderRequest.uspConsent) { + deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); } - if (bidderRequest && bidderRequest.refererInfo) { - const refererinfo = { - // TODO: this collects everything it finds, except for canonicalUrl - rd_ref: encodeURIComponent(bidderRequest.refererInfo.topmostLocation), - rd_top: bidderRequest.refererInfo.reachedTop, - rd_ifs: bidderRequest.refererInfo.numIframes, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }; - payload.referrer_detection = refererinfo; + if (bidderRequest.gppConsent) { + deepSetValue(request, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(request, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); } - const hasAdPodBid = ((bidRequests) || []).find(hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); + if (config.getConfig('coppa') === true) { + deepSetValue(request, 'regs.coppa', 1); } - if (bidRequests[0].userIdAsEids?.length > 0) { - const eids = []; - bidRequests[0].userIdAsEids.forEach(eid => { - if (!eid || !eid.uids || eid.uids.length < 1) { return; } - eid.uids.forEach(uid => { - const tmp = { 'source': eid.source, 'id': uid.id }; - if (eid.source === 'adserver.org') { - tmp.rti_partner = 'TDID'; - } else if (eid.source === 'uidapi.com') { - tmp.rti_partner = 'UID2'; + // Legacy Xandr-specific user params (externalUid, segments, age, gender, dnt, language). + // These are not part of standard OpenRTB; kept for backwards compatibility with existing + // publisher configs. Standard OpenRTB user fields flow via bidderRequest.ortb2.user. + const userObjBid = ((bidderRequest?.bids) || []).find(bid => bid.params?.user); + if (userObjBid) { + const userObj = request.user || {}; + Object.keys(userObjBid.params.user) + .filter(param => USER_PARAMS.includes(param)) + .forEach((param) => { + const uparam = convertCamelToUnderscore(param); + if (param === 'segments' && isArray(userObjBid.params.user[param])) { + const segs = userObjBid.params.user[param].map(val => { + if (isNumber(val)) return { 'id': val }; + if (isPlainObject(val)) return val; + return undefined; + }).filter(s => s); + userObj.ext = userObj.ext || {}; + userObj.ext[uparam] = segs; + } else if (param !== 'segments') { + userObj[uparam] = userObjBid.params.user[param]; } - eids.push(tmp); }); - }); + request.user = userObj; + } - if (eids.length) { - payload.eids = eids; - } + // Legacy app object from bid.params.app; backwards compatibility for publishers who pre-date + // the standard bidderRequest.ortb2.app first-party data path. + const appObjBid = ((bidderRequest?.bids) || []).find(bid => bid.params?.app); + if (appObjBid) { + request.app = Object.assign({}, request.app, appObjBid.params.app); } - if (tags[0].publisher_id) { - payload.publisher_id = tags[0].publisher_id; + // Global Keywords — set via pbjs.setConfig({ mediafuseAuctionKeywords: { key: ['val'] } }) + const mfKeywords = config.getConfig('mediafuseAuctionKeywords'); + if (mfKeywords) { + const keywords = getANKeywordParam(bidderRequest?.ortb2, mfKeywords); + if (keywords && keywords.length > 0) { + const kwString = keywords.map(kw => kw.key + (kw.value ? '=' + kw.value.join(',') : '')).join(','); + deepSetValue(request, 'ext.appnexus.keywords', kwString); + } } - const request = formatRequest(payload, bidderRequest); return 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: function (serverResponse, { bidderRequest }) { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse || serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } - logError(errorMessage); - return bids; - } - - if (serverResponse.tags) { - serverResponse.tags.forEach(serverBid => { - const rtbBid = getRtbBid(serverBid); - if (rtbBid) { - const cpmCheck = (bidderSettings.get(bidderRequest.bidderCode, 'allowZeroCpmBids') === true) ? rtbBid.cpm >= 0 : rtbBid.cpm > 0; - if (cpmCheck && this.supportedMediaTypes.includes(rtbBid.ad_type)) { - const bid = newBid(serverBid, rtbBid, bidderRequest); - bid.mediaType = parseMediaType(rtbBid); - bids.push(bid); - } - } - }); + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + const bidAdType = bid?.ext?.appnexus?.bid_ad_type; + const mediaType = RESPONSE_MEDIA_TYPE_MAP[bidAdType]; + const extANData = deepAccess(bid, 'ext.appnexus'); + // Set mediaType for all bids to help ortbConverter determine the correct parser + if (mediaType) { + context.mediaType = mediaType; + } + let bidResponse; + try { + bidResponse = buildBidResponse(bid, context); + } catch (e) { + if (bidAdType !== 3 && mediaType !== 'native') { + logError('Mediafuse: buildBidResponse hook crash', e); + } else { + logWarn('Mediafuse: buildBidResponse native parse error', e); + } } - - if (serverResponse.debug && serverResponse.debug.debug_info) { - const debugHeader = 'MediaFuse Debug Auction for Prebid\n\n' - let debugText = debugHeader + serverResponse.debug.debug_info - debugText = debugText - .replace(/(|)/gm, '\t') // Tables - .replace(/(<\/td>|<\/th>)/gm, '\n') // Tables - .replace(/^
/gm, '') // Remove leading
- .replace(/(
\n|
)/gm, '\n') //
- .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') // Header H1 - .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') // Headers - .replace(/(<([^>]+)>)/igm, ''); // Remove any other tags - // logMessage('https://console.appnexus.com/docs/understanding-the-debug-auction'); - logMessage(debugText); + if (!bidResponse) { + if (mediaType) { + bidResponse = { + requestId: bidRequest?.bidId || bid.impid, + cpm: bid.price || 0, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + dealId: bid.dealid, + currency: 'USD', + netRevenue: true, + ttl: 300, + mediaType, + ad: bid.adm + }; + } else { + logWarn('Mediafuse: Could not build bidResponse for unknown mediaType', { bidAdType, mediaType }); + return null; + } } - return bids; - }, + if (extANData) { + bidResponse.meta = Object.assign({}, bidResponse.meta, { + advertiserId: extANData.advertiser_id, + brandId: extANData.brand_id, + buyerMemberId: extANData.buyer_member_id, + dealPriority: extANData.deal_priority, + dealCode: extANData.deal_code + }); - getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (syncOptions.iframeEnabled && hasPurpose1Consent({ gdprConsent })) { - return [{ - type: 'iframe', - url: 'https://acdn.adnxs.com/dmp/async_usersync.html' - }]; + if (extANData.buyer_member_id) { + bidResponse.meta.dchain = { + ver: '1.0', + complete: 0, + nodes: [{ + bsid: extANData.buyer_member_id.toString() + }] + }; + } } - }, - /** - * Add element selector to javascript tracker to improve native viewability - * @param {Bid} bid - */ - onBidWon: function (bid) { - if (bid.native) { - reloadViewabilityScriptWithCorrectParameters(bid); + if (bid.adomain) { + const adomain = isArray(bid.adomain) ? bid.adomain : [bid.adomain]; + if (adomain.length > 0) { + bidResponse.meta = bidResponse.meta || {}; + bidResponse.meta.advertiserDomains = adomain; + } } - } -}; - -function reloadViewabilityScriptWithCorrectParameters(bid) { - const viewJsPayload = getMediafuseViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - if (viewJsPayload) { - const prebidParams = 'pbjs_adid=' + bid.adId + ';pbjs_auc=' + bid.adUnitCode; + // Video + if (FEATURES.VIDEO && mediaType === VIDEO) { + bidResponse.ttl = 3600; + if (bid.nurl) { + bidResponse.vastImpUrl = bid.nurl; + } - const jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); + if (extANData?.renderer_url && extANData?.renderer_id) { + const rendererOptions = deepAccess(bidRequest, 'mediaTypes.video.renderer.options') || deepAccess(bidRequest, 'renderer.options'); + bidResponse.adResponse = { + ad: { + notify_url: bid.nurl || '', + renderer_config: extANData.renderer_config || '', + }, + auction_id: extANData.auction_id, + content: bidResponse.vastXml, + tag_id: extANData.tag_id, + uuid: bidResponse.requestId + }; + bidResponse.renderer = newRenderer(bidRequest.adUnitCode, { + renderer_url: extANData.renderer_url, + renderer_id: extANData.renderer_id, + }, rendererOptions); + } else if (extANData?.asset_url) { + bidResponse.vastUrl = extANData.asset_url; + } + } - const newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); + // Native processing: viewability macro replacement and manual asset mapping + if (FEATURES.NATIVE && (bidAdType === 3 || mediaType === 'native')) { + bidResponse.mediaType = 'native'; + try { + const adm = bid.adm; + const nativeAdm = isStr(adm) ? JSON.parse(adm) : adm || {}; + + // 1. Viewability macro replacement + const eventtrackers = nativeAdm.native?.eventtrackers || nativeAdm.eventtrackers; + if (eventtrackers && isArray(eventtrackers)) { + eventtrackers.forEach(trackCfg => { + if (trackCfg.url && trackCfg.url.includes('dom_id=%native_dom_id%')) { + const prebidParams = 'pbjs_adid=' + (bidResponse.adId || bidResponse.requestId) + ';pbjs_auc=' + (bidRequest?.adUnitCode || ''); + trackCfg.url = trackCfg.url.replace('dom_id=%native_dom_id%', prebidParams); + } + }); + if (nativeAdm.native) { + nativeAdm.native.eventtrackers = eventtrackers; + } else { + nativeAdm.eventtrackers = eventtrackers; + } + } + // Stringify native ADM to ensure 'ad' field is available for tracking + bidResponse.ad = JSON.stringify(nativeAdm); + + // 2. Manual Mapping - OpenRTB 1.2 asset array format + const nativeAd = nativeAdm.native || nativeAdm; + const native = { + clickUrl: nativeAd.link?.url, + clickTrackers: nativeAd.link?.clicktrackers || nativeAd.link?.click_trackers || [], + impressionTrackers: nativeAd.imptrackers || nativeAd.impression_trackers || [], + privacyLink: nativeAd.privacy || nativeAd.privacy_link, + }; - // find iframe containing script tag - const frameArray = document.getElementsByTagName('iframe'); + const nativeDataTypeById = {}; + const nativeImgTypeById = {}; + try { + const ortbImp = context.imp || (context.request ?? context.ortbRequest)?.imp?.find(i => i.id === bid.impid); + if (ortbImp) { + const reqStr = ortbImp.native?.request; + const nativeReq = reqStr ? (isStr(reqStr) ? JSON.parse(reqStr) : reqStr) : null; + (nativeReq?.assets || []).forEach(a => { + if (a.data?.type) nativeDataTypeById[a.id] = a.data.type; + if (a.img?.type) nativeImgTypeById[a.id] = a.img.type; + }); + } + } catch (e) { + logError('Mediafuse Native fallback error', e); + } - // boolean var to modify only one script. That way if there are muliple scripts, - // they won't all point to the same creative. - let modifiedAScript = false; + try { + (nativeAd.assets || []).forEach(asset => { + if (asset.title) { + native.title = asset.title.text; + } else if (asset.img) { + const imgType = asset.img.type ?? nativeImgTypeById[asset.id]; + if (imgType === 1) { + native.icon = { url: asset.img.url, width: asset.img.w || asset.img.width, height: asset.img.h || asset.img.height }; + } else { + native.image = { url: asset.img.url, width: asset.img.w || asset.img.width, height: asset.img.h || asset.img.height }; + } + } else if (asset.data) { + switch (asset.data.type ?? nativeDataTypeById[asset.id]) { + case 1: native.sponsoredBy = asset.data.value; break; + case 2: native.body = asset.data.value; break; + case 3: native.rating = asset.data.value; break; + case 4: native.likes = asset.data.value; break; + case 5: native.downloads = asset.data.value; break; + case 6: native.price = asset.data.value; break; + case 7: native.salePrice = asset.data.value; break; + case 8: native.phone = asset.data.value; break; + case 9: native.address = asset.data.value; break; + case 10: native.body2 = asset.data.value; break; + case 11: native.displayUrl = asset.data.value; break; + case 12: native.cta = asset.data.value; break; + } + } + }); - // first, loop on all ifames - for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { - const currentFrame = frameArray[i]; - try { - // IE-compatible, see https://stackoverflow.com/a/3999191/2112089 - const nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + // Fallback for non-asset based native response (AppNexus legacy format) + if (!native.title && nativeAd.title) { + native.title = (isStr(nativeAd.title)) ? nativeAd.title : nativeAd.title.text; + } + if (!native.body && nativeAd.desc) { + native.body = nativeAd.desc; + } + if (!native.body2 && nativeAd.desc2) native.body2 = nativeAd.desc2; + if (!native.cta && nativeAd.ctatext) native.cta = nativeAd.ctatext; + if (!native.rating && nativeAd.rating) native.rating = nativeAd.rating; + if (!native.sponsoredBy && nativeAd.sponsored) native.sponsoredBy = nativeAd.sponsored; + if (!native.displayUrl && nativeAd.displayurl) native.displayUrl = nativeAd.displayurl; + if (!native.address && nativeAd.address) native.address = nativeAd.address; + if (!native.downloads && nativeAd.downloads) native.downloads = nativeAd.downloads; + if (!native.likes && nativeAd.likes) native.likes = nativeAd.likes; + if (!native.phone && nativeAd.phone) native.phone = nativeAd.phone; + if (!native.price && nativeAd.price) native.price = nativeAd.price; + if (!native.salePrice && nativeAd.saleprice) native.salePrice = nativeAd.saleprice; + + if (!native.image && nativeAd.main_img) { + native.image = { url: nativeAd.main_img.url, width: nativeAd.main_img.width, height: nativeAd.main_img.height }; + } + if (!native.icon && nativeAd.icon) { + native.icon = { url: nativeAd.icon.url, width: nativeAd.icon.width, height: nativeAd.icon.height }; + } - if (nestedDoc) { - // if the doc is present, we look for our jstracker - const scriptArray = nestedDoc.getElementsByTagName('script'); - for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { - const currentScript = scriptArray[j]; - if (currentScript.getAttribute('data-src') === jsTrackerSrc) { - currentScript.setAttribute('src', newJsTrackerSrc); - currentScript.setAttribute('data-src', ''); - if (currentScript.removeAttribute) { - currentScript.removeAttribute('data-src'); - } - modifiedAScript = true; + bidResponse.native = native; + + let jsTrackers = nativeAd.javascript_trackers; + const viewabilityConfig = deepAccess(bid, 'ext.appnexus.viewability.config'); + if (viewabilityConfig) { + const jsTrackerDisarmed = viewabilityConfig.replace(/src=/g, 'data-src='); + if (jsTrackers == null) { + jsTrackers = [jsTrackerDisarmed]; + } else if (isStr(jsTrackers)) { + jsTrackers = [jsTrackers, jsTrackerDisarmed]; + } else if (isArray(jsTrackers)) { + jsTrackers = [...jsTrackers, jsTrackerDisarmed]; + } + } else if (isArray(nativeAd.eventtrackers)) { + const trackers = nativeAd.eventtrackers + .filter(t => t.method === 1) + .map(t => (t.url && t.url.match(VIEWABILITY_URL_START) && t.url.indexOf(VIEWABILITY_FILE_NAME) > -1) + ? t.url.replace(/src=/g, 'data-src=') + : t.url + ).filter(url => url); + + if (jsTrackers == null) { + jsTrackers = trackers; + } else if (isStr(jsTrackers)) { + jsTrackers = [jsTrackers, ...trackers]; + } else if (isArray(jsTrackers)) { + jsTrackers = [...jsTrackers, ...trackers]; } } + if (bidResponse.native) { + bidResponse.native.javascriptTrackers = jsTrackers; + } + } catch (e) { + logError('Mediafuse Native mapping error', e); } - } catch (exception) { - // trying to access a cross-domain iframe raises a SecurityError - // this is expected and ignored - if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { - // all other cases are raised again to be treated by the calling function - throw exception; - } + } catch (e) { + logError('Mediafuse Native JSON parse error', e); } } + + // Banner Trackers + if (mediaType === BANNER && extANData?.trackers) { + extANData.trackers.forEach(tracker => { + if (tracker.impression_urls) { + tracker.impression_urls.forEach(url => { + bidResponse.ad = (bidResponse.ad || '') + createTrackPixelHtml(url); + }); + } + }); + } + + return bidResponse; + } +}); + +function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return (bid.params.reserve != null) ? bid.params.reserve : null; + } + // Mediafuse/AppNexus generally expects USD for its RTB endpoints + let floor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; } + return null; } -function strIsMediafuseViewabilityScript(str) { - const regexMatchUrlStart = str.match(VIEWABILITY_URL_START); - const viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; +function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: rtbBid.renderer_id, + url: rtbBid.renderer_url, + config: rendererOptions, + loaded: false, + adUnitCode, + }); - const regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); - const fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } - return str.startsWith(SCRIPT_TAG_START) && fileNameInStr && viewUrlStartInStr; + renderer.setEventHandlers({ + impression: () => logMessage('Mediafuse outstream video impression event'), + loaded: () => logMessage('Mediafuse outstream video loaded event'), + ended: () => { + logMessage('Mediafuse outstream renderer video event'); + const el = document.getElementById(adUnitCode); + if (el) { + el.style.display = 'none'; + } + }, + }); + return renderer; } -function getMediafuseViewabilityScriptFromJsTrackers(jsTrackerArray) { - let viewJsPayload; - if (isStr(jsTrackerArray) && strIsMediafuseViewabilityScript(jsTrackerArray)) { - viewJsPayload = jsTrackerArray; - } else if (isArray(jsTrackerArray)) { - for (let i = 0; i < jsTrackerArray.length; i++) { - const currentJsTracker = jsTrackerArray[i]; - if (strIsMediafuseViewabilityScript(currentJsTracker)) { - viewJsPayload = currentJsTracker; - } +function hidedfpContainer(elementId) { + try { + const el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + if (el[0]) { + el[0].style.setProperty('display', 'none'); } + } catch (e) { + logWarn('Mediafuse: hidedfpContainer error', e); } - return viewJsPayload; } -function getViewabilityScriptUrlFromPayload(viewJsPayload) { - // extracting the content of the src attribute - // -> substring between src=" and " - const indexOfFirstQuote = viewJsPayload.indexOf('src="') + 5; // offset of 5: the length of 'src=' + 1 - const indexOfSecondQuote = viewJsPayload.indexOf('"', indexOfFirstQuote); - const jsTrackerSrc = viewJsPayload.substring(indexOfFirstQuote, indexOfSecondQuote); - return jsTrackerSrc; -} - -function formatRequest(payload, bidderRequest) { - let request = []; - const options = { - withCredentials: true - }; - - let endpointUrl = URL; - - if (!hasPurpose1Consent(bidderRequest?.gdprConsent)) { - endpointUrl = URL_SIMPLE; - } - - if (getParameterByName('apn_test').toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { - options.customHeaders = { - 'X-Is-Test': 1 - }; - } - - if (payload.tags.length > MAX_IMPS_PER_REQUEST) { - const clonedPayload = deepClone(payload); - - chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { - clonedPayload.tags = tags; - const payloadString = JSON.stringify(clonedPayload); - request.push({ - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }); - }); - } else { - const payloadString = JSON.stringify(payload); - request = { - method: 'POST', - url: endpointUrl, - data: payloadString, - bidderRequest, - options - }; +function hideSASIframe(elementId) { + try { + const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + if (el[0]?.nextSibling?.localName === 'iframe') { + el[0].nextSibling.style.setProperty('display', 'none'); + } + } catch (e) { + logWarn('Mediafuse: hideSASIframe error', e); } - - return request; } -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { - const renderer = Renderer.install({ - id: rtbBid.renderer_id, - url: rtbBid.renderer_url, - config: rendererOptions, - loaded: false, - adUnitCode - }); - +function handleOutstreamRendererEvents(bid, id, eventName) { try { - renderer.setRender(outstreamRender); + bid.renderer.handleVideoEvent({ + id, + eventName, + }); } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); + logWarn(`Mediafuse: handleOutstreamRendererEvents error for ${eventName}`, err); } +} - renderer.setEventHandlers({ - impression: () => logMessage('MediaFuse outstream video impression event'), - loaded: () => logMessage('MediaFuse outstream video loaded event'), - ended: () => { - logMessage('MediaFuse outstream renderer video event'); - document.querySelector(`#${adUnitCode}`).style.display = 'none'; +function outstreamRender(bid, doc) { + hidedfpContainer(bid.adUnitCode); + hideSASIframe(bid.adUnitCode); + bid.renderer.push(() => { + const win = doc?.defaultView || window; + if (win.ANOutstreamVideo) { + let sizes = bid.getSize(); + if (typeof sizes === 'string' && sizes.indexOf('x') > -1) { + sizes = [sizes.split('x').map(Number)]; + } else if (!isArray(sizes) || !isArray(sizes[0])) { + sizes = [sizes]; + } + + win.ANOutstreamVideo.renderAd({ + tagId: bid.adResponse.tag_id, + sizes: sizes, + targetId: bid.adUnitCode, + uuid: bid.requestId, + adResponse: bid.adResponse, + rendererOptions: bid.renderer.getConfig(), + }, + handleOutstreamRendererEvents.bind(null, bid) + ); } }); - return renderer; } -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - mediafuse: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code - } - }; - - // WE DON'T FULLY SUPPORT THIS ATM - future spot for adomain code; creating a stub for 5.0 compliance - if (rtbBid.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [] }); - } - - if (rtbBid.advertiser_id) { - bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); +function hasOmidSupport(bid) { + let hasOmid = false; + const bidderParams = bid?.params; + const videoParams = bid?.mediaTypes?.video?.api; + if (bidderParams?.frameworks && isArray(bidderParams.frameworks)) { + hasOmid = bidderParams.frameworks.includes(OMID_FRAMEWORK); } - - // temporary function; may remove at later date if/when adserver fully supports dchain - function setupDChain(rtbBid) { - const dchain = { - ver: '1.0', - complete: 0, - nodes: [{ - bsid: rtbBid.buyer_member_id.toString() - }] - }; - - return dchain; + if (!hasOmid && isArray(bidderParams?.video?.frameworks)) { + hasOmid = bidderParams.video.frameworks.includes(OMID_FRAMEWORK); } - if (rtbBid.buyer_member_id) { - bid.meta = Object.assign({}, bid.meta, { dchain: setupDChain(rtbBid) }); + if (!hasOmid && isArray(videoParams)) { + hasOmid = videoParams.includes(OMID_API); } + return hasOmid; +} - if (rtbBid.brand_id) { - bid.meta = Object.assign({}, bid.meta, { brandId: rtbBid.brand_id }); - } +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + maintainer: { email: 'indrajit@oncoredigital.com' }, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid: function (bid) { + const params = bid?.params; + if (!params) return false; + return !!(params.placementId || params.placement_id || (params.member && (params.invCode || params.inv_code))); + }, - if (rtbBid.rtb.video) { - // shared video properties used for all 3 contexts - Object.assign(bid, { - width: rtbBid.rtb.video.player_width, - height: rtbBid.rtb.video.player_height, - vastImpUrl: rtbBid.notify_url, - ttl: 3600 - }); + buildRequests: function (bidRequests, bidderRequest) { + const options = { + withCredentials: true + }; - const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); - switch (videoContext) { - case ADPOD: - const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; - bid.meta = Object.assign({}, bid.meta, { primaryCatId }); - const dealTier = rtbBid.deal_priority; - bid.video = { - context: ADPOD, - durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), - dealTier - }; - bid.vastUrl = rtbBid.rtb.video.asset_url; - break; - case OUTSTREAM: - bid.adResponse = serverBid; - bid.adResponse.ad = bid.adResponse.ads[0]; - bid.adResponse.ad.video = bid.adResponse.ad.rtb.video; - bid.vastXml = rtbBid.rtb.video.content; - - if (rtbBid.renderer_url) { - const videoBid = ((bidderRequest.bids) || []).find(bid => bid.bidId === serverBid.uuid); - const rendererOptions = deepAccess(videoBid, 'renderer.options'); - bid.renderer = newRenderer(bid.adUnitCode, rtbBid, rendererOptions); - } - break; - case INSTREAM: - bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); - break; + if (getParameterByName('apn_test')?.toUpperCase() === 'TRUE' || config.getConfig('apn_test') === true) { + options.customHeaders = { 'X-Is-Test': 1 }; } - } else if (rtbBid.rtb[NATIVE]) { - const nativeAd = rtbBid.rtb[NATIVE]; - // setting up the jsTracker: - // we put it as a data-src attribute so that the tracker isn't called - // until we have the adId (see onBidWon) - const jsTrackerDisarmed = rtbBid.viewability.config.replace('src=', 'data-src='); + const requests = []; + const chunkedRequests = chunk(bidRequests, MAX_IMPS_PER_REQUEST); - let jsTrackers = nativeAd.javascript_trackers; + chunkedRequests.forEach(batch => { + const data = converter.toORTB({ bidRequests: batch, bidderRequest }); - if (jsTrackers === undefined || jsTrackers === null) { - jsTrackers = jsTrackerDisarmed; - } else if (isStr(jsTrackers)) { - jsTrackers = [jsTrackers, jsTrackerDisarmed]; - } else { - jsTrackers.push(jsTrackerDisarmed); - } + let endpointUrl = ENDPOINT_URL_NORMAL; + if (!hasPurpose1Consent(bidderRequest.gdprConsent)) { + endpointUrl = ENDPOINT_URL_SIMPLE; + } - bid[NATIVE] = { - title: nativeAd.title, - body: nativeAd.desc, - body2: nativeAd.desc2, - cta: nativeAd.ctatext, - rating: nativeAd.rating, - sponsoredBy: nativeAd.sponsored, - privacyLink: nativeAd.privacy_link, - address: nativeAd.address, - downloads: nativeAd.downloads, - likes: nativeAd.likes, - phone: nativeAd.phone, - price: nativeAd.price, - salePrice: nativeAd.saleprice, - clickUrl: nativeAd.link.url, - displayUrl: nativeAd.displayurl, - clickTrackers: nativeAd.link.click_trackers, - impressionTrackers: nativeAd.impression_trackers, - javascriptTrackers: jsTrackers - }; - if (nativeAd.main_img) { - bid['native'].image = { - url: nativeAd.main_img.url, - height: nativeAd.main_img.height, - width: nativeAd.main_img.width, - }; - } - if (nativeAd.icon) { - bid['native'].icon = { - url: nativeAd.icon.url, - height: nativeAd.icon.height, - width: nativeAd.icon.width, - }; - } - } else { - Object.assign(bid, { - width: rtbBid.rtb.banner.width, - height: rtbBid.rtb.banner.height, - ad: rtbBid.rtb.banner.content - }); - try { - if (rtbBid.rtb.trackers) { - for (let i = 0; i < rtbBid.rtb.trackers[0].impression_urls.length; i++) { - const url = rtbBid.rtb.trackers[0].impression_urls[i]; - const tracker = createTrackPixelHtml(url); - bid.ad += tracker; + // Debug logic + let debugObj = {}; + const debugCookie = storage.getCookie('apn_prebid_debug'); + if (debugCookie) { + try { + debugObj = JSON.parse(debugCookie); + } catch (e) { + logWarn('Mediafuse: failed to parse debug cookie', e); } + } else { + Object.keys(DEBUG_QUERY_PARAM_MAP).forEach(qparam => { + const qval = getParameterByName(qparam); + if (qval) debugObj[DEBUG_QUERY_PARAM_MAP[qparam]] = qval; + }); + if (Object.keys(debugObj).length > 0 && !('enabled' in debugObj)) debugObj.enabled = true; } - } catch (error) { - logError('Error appending tracking pixel', error); - } - } - return bid; -} + if (debugObj.enabled) { + logInfo('MediaFuse Debug Auction Settings:\n\n' + JSON.stringify(debugObj, null, 4)); + endpointUrl += (endpointUrl.indexOf('?') === -1 ? '?' : '&') + + Object.keys(debugObj).filter(p => DEBUG_PARAMS.includes(p)) + .map(p => (p === 'enabled') ? `debug=1` : `${p}=${encodeURIComponent(debugObj[p])}`).join('&'); + } -function bidToTag(bid) { - const tag = {}; - tag.sizes = transformSizes(bid.sizes); - tag.primary_size = tag.sizes[0]; - tag.ad_types = []; - tag.uuid = bid.bidId; - if (bid.params.placementId) { - tag.id = parseInt(bid.params.placementId, 10); - } else { - tag.code = bid.params.invCode; - } - tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; - tag.use_pmt_rule = bid.params.usePaymentRule || false; - tag.prebid = true; - tag.disable_psa = true; - const bidFloor = getBidFloor(bid); - if (bidFloor) { - tag.reserve = bidFloor; - } - if (bid.params.position) { - tag.position = { 'above': 1, 'below': 2 }[bid.params.position] || 0; - } - if (bid.params.trafficSourceCode) { - tag.traffic_source_code = bid.params.trafficSourceCode; - } - if (bid.params.privateSizes) { - tag.private_sizes = transformSizes(bid.params.privateSizes); - } - if (bid.params.supplyType) { - tag.supply_type = bid.params.supplyType; - } - if (bid.params.pubClick) { - tag.pubclick = bid.params.pubClick; - } - if (bid.params.extInvCode) { - tag.ext_inv_code = bid.params.extInvCode; - } - if (bid.params.publisherId) { - tag.publisher_id = parseInt(bid.params.publisherId, 10); - } - if (bid.params.externalImpId) { - tag.external_imp_id = bid.params.externalImpId; - } - if (!isEmpty(bid.params.keywords)) { - tag.keywords = getANKewyordParamFromMaps(bid.params.keywords); - } - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); - if (gpid) { - tag.gpid = gpid; - } + // member_id on the URL enables Xandr server-side routing to the correct seat; + // it is also present in ext.appnexus.member_id in the request body for exchange logic. + const memberBid = batch.find(bid => bid.params && bid.params.member); + const member = memberBid && memberBid.params.member; + if (member) { + endpointUrl += (endpointUrl.indexOf('?') === -1 ? '?' : '&') + 'member_id=' + member; + } - if (bid.mediaType === NATIVE || deepAccess(bid, `mediaTypes.${NATIVE}`)) { - tag.ad_types.push(NATIVE); - if (tag.sizes.length === 0) { - tag.sizes = transformSizes([1, 1]); - } + requests.push({ + method: 'POST', + url: endpointUrl, + data, + bidderRequest, + options + }); + }); - if (bid.nativeParams) { - const nativeRequest = buildNativeRequest(bid.nativeParams); - tag[NATIVE] = { layouts: [nativeRequest] }; - } - } + return requests; + }, - const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); - const context = deepAccess(bid, 'mediaTypes.video.context'); + interpretResponse: function (serverResponse, request) { + const bids = converter.fromORTB({ + response: serverResponse.body, + request: request.data, + context: { + ortbRequest: request.data + } + }).bids; - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } - if (bid.mediaType === VIDEO || videoMediaType) { - tag.ad_types.push(VIDEO); - } + // Debug logging + if (serverResponse.body?.debug?.debug_info) { + const debugHeader = 'MediaFuse Debug Auction for Prebid\n\n'; + let debugText = debugHeader + serverResponse.body.debug.debug_info; + debugText = debugText + .replace(/(|)/gm, '\t') + .replace(/(<\/td>|<\/th>)/gm, '\n') + .replace(/^
/gm, '') + .replace(/(
\n|
)/gm, '\n') + .replace(/

(.*)<\/h1>/gm, '\n\n===== $1 =====\n\n') + .replace(/(.*)<\/h[2-6]>/gm, '\n\n*** $1 ***\n\n') + .replace(/(<([^>]+)>)/igm, ''); + logMessage(debugText); + } - // instream gets vastUrl, outstream gets vastXml - if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { - tag.require_asset_url = true; - } + return bids; + }, - if (bid.params.video) { - tag.video = {}; - // place any valid video params on the tag - Object.keys(bid.params.video) - .filter(param => VIDEO_TARGETING.includes(param)) - .forEach(param => { - switch (param) { - case 'context': - case 'playback_method': - let type = bid.params.video[param]; - type = (isArray(type)) ? type[0] : type; - tag.video[param] = VIDEO_MAPPING[param][type]; - break; - // Deprecating tags[].video.frameworks in favor of tags[].video_frameworks - case 'frameworks': - break; - default: - tag.video[param] = bid.params.video[param]; - } - }); + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + let gdprParams = ''; - if (bid.params.video.frameworks && isArray(bid.params.video.frameworks)) { - tag['video_frameworks'] = bid.params.video.frameworks; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `?gdpr_consent=${gdprConsent.consentString}`; + } } - } - // use IAB ORTB values if the corresponding values weren't already set by bid.params.video - if (videoMediaType) { - tag.video = tag.video || {}; - Object.keys(videoMediaType) - .filter(param => VIDEO_RTB_TARGETING.includes(param)) - .forEach(param => { - switch (param) { - case 'minduration': - case 'maxduration': - if (typeof tag.video[param] !== 'number') tag.video[param] = videoMediaType[param]; - break; - case 'skip': - if (typeof tag.video['skippable'] !== 'boolean') tag.video['skippable'] = (videoMediaType[param] === 1); - break; - case 'skipafter': - if (typeof tag.video['skipoffset'] !== 'number') tag.video['skippoffset'] = videoMediaType[param]; - break; - case 'playbackmethod': - if (typeof tag.video['playback_method'] !== 'number') { - let type = videoMediaType[param]; - type = (isArray(type)) ? type[0] : type; - - // we only support iab's options 1-4 at this time. - if (type >= 1 && type <= 4) { - tag.video['playback_method'] = type; - } - } - break; - case 'api': - if (!tag['video_frameworks'] && isArray(videoMediaType[param])) { - // need to read thru array; remove 6 (we don't support it), swap 4 <> 5 if found (to match our adserver mapping for these specific values) - const apiTmp = videoMediaType[param].map(val => { - const v = (val === 4) ? 5 : (val === 5) ? 4 : val; - - if (v >= 1 && v <= 5) { - return v; - } - return undefined; - }).filter(v => v); - tag['video_frameworks'] = apiTmp; - } - break; - } + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { + syncs.push({ + type: 'iframe', + url: 'https://acdn.adnxs.com/dmp/async_usersync.html' + gdprParams }); - } - - if (bid.renderer) { - tag.video = Object.assign({}, tag.video, { custom_renderer_present: true }); - } - - if (bid.params.frameworks && isArray(bid.params.frameworks)) { - tag['banner_frameworks'] = bid.params.frameworks; - } - - if (bid.mediaTypes?.banner) { - tag.ad_types.push(BANNER); - } - - if (tag.ad_types.length === 0) { - delete tag.ad_types; - } - - return tag; -} - -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - const sizes = []; - let sizeObj = {}; - - if (isArray(requestSizes) && requestSizes.length === 2 && - !isArray(requestSizes[0])) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - const size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); } - } - - return sizes; -} - -function hasUserInfo(bid) { - return !!bid.params.user; -} - -function hasMemberId(bid) { - return !!parseInt(bid.params.member, 10); -} - -function hasAppDeviceInfo(bid) { - if (bid.params) { - return !!bid.params.app - } -} - -function hasAppId(bid) { - if (bid.params && bid.params.app) { - return !!bid.params.app.id - } - return !!bid.params.app -} - -function hasDebug(bid) { - return !!bid.debug -} - -function hasAdPod(bid) { - return ( - bid.mediaTypes && - bid.mediaTypes.video && - bid.mediaTypes.video.context === ADPOD - ); -} - -function hasOmidSupport(bid) { - let hasOmid = false; - const bidderParams = bid.params; - const videoParams = bid.params.video; - if (bidderParams.frameworks && isArray(bidderParams.frameworks)) { - hasOmid = bid.params.frameworks.includes(6); - } - if (!hasOmid && videoParams && videoParams.frameworks && isArray(videoParams.frameworks)) { - hasOmid = bid.params.video.frameworks.includes(6); - } - return hasOmid; -} - -/** - * Expand an adpod placement into a set of request objects according to the - * total adpod duration and the range of duration seconds. Sets minduration/ - * maxduration video property according to requireExactDuration configuration - */ -function createAdPodRequest(tags, adPodBid) { - const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; - - const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = Math.max(...durationRangeSec); - const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); - const request = fill(...tagToDuplicate, numberOfPlacements); + if (syncOptions.pixelEnabled && serverResponses.length > 0) { + const userSync = deepAccess(serverResponses[0], 'body.ext.appnexus.userSync'); + if (userSync && userSync.url) { + let url = userSync.url; + if (gdprParams) { + url += (url.indexOf('?') === -1 ? '?' : '&') + gdprParams.substring(1); + } + syncs.push({ + type: 'image', + url: url + }); + } + } + return syncs; + }, - if (requireExactDuration) { - const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); - const chunked = chunk(request, divider); + onBidWon: function (bid) { + if (bid.native) { + reloadViewabilityScriptWithCorrectParameters(bid); + } + }, - // each configured duration is set as min/maxduration for a subset of requests - durationRangeSec.forEach((duration, index) => { - chunked[index].forEach(tag => { - setVideoProperty(tag, 'minduration', duration); - setVideoProperty(tag, 'maxduration', duration); - }); - }); - } else { - // all maxdurations should be the same - request.forEach(tag => setVideoProperty(tag, 'maxduration', maxDuration)); + onBidderError: function ({ error, bidderRequest }) { + logError(`Mediafuse Bidder Error: ${error.message || error}`, bidderRequest); } +}; - return request; -} - -function getAdPodPlacementNumber(videoParams) { - const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = Math.min(...durationRangeSec); - const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); +function reloadViewabilityScriptWithCorrectParameters(bid) { + const viewJsPayload = getMediafuseViewabilityScriptFromJsTrackers(bid.native.javascriptTrackers); - return requireExactDuration - ? Math.max(numberOfPlacements, durationRangeSec.length) - : numberOfPlacements; -} + if (viewJsPayload) { + const prebidParams = 'pbjs_adid=' + (bid.adId || bid.requestId) + ';pbjs_auc=' + bid.adUnitCode; + const jsTrackerSrc = getViewabilityScriptUrlFromPayload(viewJsPayload); + const newJsTrackerSrc = jsTrackerSrc.replace('dom_id=%native_dom_id%', prebidParams); -function setVideoProperty(tag, key, value) { - if (isEmpty(tag.video)) { tag.video = {}; } - tag.video[key] = value; -} + // find iframe containing script tag + const frameArray = document.getElementsByTagName('iframe'); -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && ((tag.ads) || []).find(ad => ad.rtb); -} + // flag to modify only one script — prevents multiple scripts from pointing to the same creative + let modifiedAScript = false; -function buildNativeRequest(params) { - const request = {}; - - // map standard prebid native asset identifier to /ut parameters - // e.g., tag specifies `body` but /ut only knows `description`. - // mapping may be in form {tag: ''} or - // {tag: {serverName: '', requiredParams: {...}}} - Object.keys(params).forEach(key => { - // check if one of the forms is used, otherwise - // a mapping wasn't specified so pass the key straight through - const requestKey = - (NATIVE_MAPPING[key] && NATIVE_MAPPING[key].serverName) || - NATIVE_MAPPING[key] || - key; - - // required params are always passed on request - const requiredParams = NATIVE_MAPPING[key] && NATIVE_MAPPING[key].requiredParams; - request[requestKey] = Object.assign({}, requiredParams, params[key]); - - // convert the sizes of image/icon assets to proper format (if needed) - const isImageAsset = !!(requestKey === NATIVE_MAPPING.image.serverName || requestKey === NATIVE_MAPPING.icon.serverName); - if (isImageAsset && request[requestKey].sizes) { - const sizes = request[requestKey].sizes; - if (isArrayOfNums(sizes) || (isArray(sizes) && sizes.length > 0 && sizes.every(sz => isArrayOfNums(sz)))) { - request[requestKey].sizes = transformSizes(request[requestKey].sizes); + // loop on all iframes + for (let i = 0; i < frameArray.length && !modifiedAScript; i++) { + const currentFrame = frameArray[i]; + try { + const nestedDoc = currentFrame.contentDocument || currentFrame.contentWindow.document; + if (nestedDoc) { + const scriptArray = nestedDoc.getElementsByTagName('script'); + for (let j = 0; j < scriptArray.length && !modifiedAScript; j++) { + const currentScript = scriptArray[j]; + if (currentScript.getAttribute('data-src') === jsTrackerSrc) { + currentScript.setAttribute('src', newJsTrackerSrc); + currentScript.removeAttribute('data-src'); + modifiedAScript = true; + } + } + } + } catch (exception) { + if (!(exception instanceof DOMException && exception.name === 'SecurityError')) { + throw exception; + } } } - - if (requestKey === NATIVE_MAPPING.privacyLink) { - request.privacy_supported = true; - } - }); - - return request; -} - -/** - * This function hides google div container for outstream bids to remove unwanted space on page. Mediafuse renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id - */ -function hidedfpContainer(elementId) { - var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); - if (el[0]) { - el[0].style.setProperty('display', 'none'); - } -} - -function hideSASIframe(elementId) { - try { - // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. - const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); - if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { - el[0].nextSibling.style.setProperty('display', 'none'); - } - } catch (e) { - // element not found! } } -function outstreamRender(bid) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); - // push to render queue because ANOutstreamVideo may not be loaded - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - tagId: bid.adResponse.tag_id, - sizes: [bid.getSize().split('x')], - targetId: bid.adUnitCode, // target div id to render video - uuid: bid.adResponse.uuid, - adResponse: bid.adResponse, - rendererOptions: bid.renderer.getConfig() - }, handleOutstreamRendererEvents.bind(null, bid)); - }); -} - -function handleOutstreamRendererEvents(bid, id, eventName) { - bid.renderer.handleVideoEvent({ id, eventName }); -} +function strIsMediafuseViewabilityScript(str) { + const regexMatchUrlStart = str.match(VIEWABILITY_URL_START); + const viewUrlStartInStr = regexMatchUrlStart != null && regexMatchUrlStart.length >= 1; + const regexMatchFileName = str.match(VIEWABILITY_FILE_NAME); + const fileNameInStr = regexMatchFileName != null && regexMatchFileName.length >= 1; -function parseMediaType(rtbBid) { - const adType = rtbBid.ad_type; - if (adType === VIDEO) { - return VIDEO; - } else if (adType === NATIVE) { - return NATIVE; - } else { - return BANNER; - } + return str.startsWith(' { + if (key === 'mediafuseAuctionKeywords') return { section: ['news', 'sports'] }; + return undefined; + }); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.ext.appnexus.keywords).to.include('section=news,sports'); }); }); - describe('buildRequests', function () { - let getAdUnitsStub; - const bidRequests = [ - { - 'bidder': 'mediafuse', - 'params': { - 'placementId': '10433394' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '04f2659e-c005-4eb1-a57c-fa93145e3843' - } - ]; + // ------------------------------------------------------------------------- + // buildRequests — user params + // ------------------------------------------------------------------------- + describe('buildRequests - user params', function () { + it('should map age, gender, and numeric segments', function () { + const bid = deepClone(BASE_BID); + bid.params.user = { age: 35, gender: 'F', segments: [10, 20] }; + // bidderRequest.bids must contain the bid for the request() hook to find params.user + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.user.age).to.equal(35); + expect(req.data.user.gender).to.equal('F'); + expect(req.data.user.ext.segments).to.deep.equal([{ id: 10 }, { id: 20 }]); + }); - beforeEach(function() { - getAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits').callsFake(function() { - return []; - }); + it('should map object-style segments and ignore invalid ones', function () { + const bid = deepClone(BASE_BID); + bid.params.user = { segments: [{ id: 99 }, 'bad', null] }; + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.user.ext.segments).to.deep.equal([{ id: 99 }]); }); + }); - afterEach(function() { - getAdUnitsStub.restore(); + // ------------------------------------------------------------------------- + // buildRequests — app params + // ------------------------------------------------------------------------- + describe('buildRequests - app params', function () { + it('should merge app params into request.app', function () { + const bid = deepClone(BASE_BID); + bid.params.app = { name: 'MyApp', bundle: 'com.myapp', ver: '1.0' }; + // bidderRequest.bids must contain the bid for the request() hook to find params.app + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.app.name).to.equal('MyApp'); + expect(req.data.app.bundle).to.equal('com.myapp'); }); + }); - it('should parse out private sizes', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - privateSizes: [300, 250] - } - } - ); + // ------------------------------------------------------------------------- + // buildRequests — privacy: USP, addtlConsent, COPPA + // ------------------------------------------------------------------------- + describe('buildRequests - privacy', function () { + it('should set us_privacy from uspConsent', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.uspConsent = '1YNN'; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.regs.ext.us_privacy).to.equal('1YNN'); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should parse addtlConsent into array of integers', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'cs', + addtlConsent: '1~7.12.99' + }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.user.ext.addtl_consent).to.deep.equal([7, 12, 99]); + }); - expect(payload.tags[0].private_sizes).to.exist; - expect(payload.tags[0].private_sizes).to.deep.equal([{ width: 300, height: 250 }]); + it('should not set addtl_consent when addtlConsent has no ~ separator', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { gdprApplies: true, consentString: 'cs', addtlConsent: 'no-tilde' }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.user?.ext?.addtl_consent).to.be.undefined; }); - it('should add publisher_id in request', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - publisherId: '1231234' - } - }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].publisher_id).to.exist; - expect(payload.tags[0].publisher_id).to.deep.equal(1231234); - expect(payload.publisher_id).to.exist; - expect(payload.publisher_id).to.deep.equal(1231234); - }) - - it('should add source and verison to the tag', function () { - const request = spec.buildRequests(bidRequests); - const payload = JSON.parse(request.data); - expect(payload.sdk).to.exist; - expect(payload.sdk).to.deep.equal({ - source: 'pbjs', - version: '$prebid.version$' + it('should set regs.coppa=1 when coppa config is true', function () { + sandbox.stub(config, 'getConfig').callsFake((key) => { + if (key === 'coppa') return true; + return undefined; }); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.regs.coppa).to.equal(1); }); + }); - it('should populate the ad_types array on all requests', function () { - const adUnits = [{ - code: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - } - }, - bids: [{ - bidder: 'mediafuse', - params: { - placementId: '10433394' + // ------------------------------------------------------------------------- + // buildRequests — video RTB targeting + // ------------------------------------------------------------------------- + describe('buildRequests - video RTB targeting', function () { + if (FEATURES.VIDEO) { + it('should map skip, skipafter, playbackmethod, and api to AN fields', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + skip: 1, + skipafter: 5, + playbackmethod: [2], + api: [4] } - }], - transactionId: '04f2659e-c005-4eb1-a57c-fa93145e3843' - }]; + }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const video = req.data.imp[0].video; + const extAN = req.data.imp[0].ext.appnexus; + expect(video.skippable).to.be.true; + expect(video.skipoffset).to.equal(5); + expect(video.playback_method).to.equal(2); + // api [4] maps to video_frameworks [5] (4↔5 swap) + expect(extAN.video_frameworks).to.include(5); + }); + + it('should set outstream placement=4 for outstream context', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'outstream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.placement).to.equal(4); + }); + + it('should set video.ext.appnexus.context=1 for instream', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.ext.appnexus.context).to.equal(1); + }); - ['banner', 'video', 'native'].forEach(type => { - getAdUnitsStub.callsFake(function(...args) { - return adUnits; - }); + it('should set video.ext.appnexus.context=4 for outstream', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'outstream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.ext.appnexus.context).to.equal(4); + }); - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes[type] = {}; + it('should set video.ext.appnexus.context=5 for in-banner', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'in-banner', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.ext.appnexus.context).to.equal(5); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should not set video.ext.appnexus.context for unknown context', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'unknown-type', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.ext?.appnexus?.context).to.be.undefined; + }); - expect(payload.tags[0].ad_types).to.deep.equal([type]); + it('should set require_asset_url for instream context', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.require_asset_url).to.be.true; + }); - if (type === 'banner') { - delete adUnits[0].mediaTypes; - } + it('should map video params from bid.params.video (VIDEO_TARGETING fields)', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + bid.params.video = { minduration: 5, maxduration: 30, frameworks: [1, 2] }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.minduration).to.equal(5); + expect(req.data.imp[0].ext.appnexus.video_frameworks).to.deep.equal([1, 2]); }); - }); + } + }); - it('should not populate the ad_types array when adUnit.mediaTypes is undefined', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // buildRequests — OMID support + // ------------------------------------------------------------------------- + describe('buildRequests - OMID support', function () { + it('should set iab_support when bid.params.frameworks includes 6', function () { + const bid = deepClone(BASE_BID); + bid.params.frameworks = [6]; + // hasOmidSupport iterates all bids via .some(), so bid must be in bidderRequest.bids + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.ext.appnexus.iab_support).to.deep.equal({ + omidpn: 'Mediafuse', + omidpv: '$prebid.version$' + }); + }); - expect(payload.tags[0].ad_types).to.not.exist; + it('should set iab_support when mediaTypes.video.api includes 7', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], api: [7] } }; + // hasOmidSupport iterates all bids via .some(), so bid must be in bidderRequest.bids + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.ext.appnexus.iab_support).to.exist; }); + }); - it('should populate the ad_types array on outstream requests', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - bidRequest.mediaTypes = {}; - bidRequest.mediaTypes.video = { context: 'outstream' }; + // ------------------------------------------------------------------------- + // interpretResponse — outstream renderer + // ------------------------------------------------------------------------- + if (FEATURES.VIDEO) { + describe('interpretResponse - outstream renderer', function () { + it('should create renderer when renderer_url and renderer_id are present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'outstream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 3.0, + ext: { + appnexus: { + bid_ad_type: 1, + renderer_url: 'https://cdn.adnxs.com/renderer.js', + renderer_id: 42, + renderer_config: '{"key":"val"}' + } + } + }] + }] + } + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].renderer).to.exist; + expect(bids[0].adResponse.ad.renderer_config).to.equal('{"key":"val"}'); + }); - expect(payload.tags[0].ad_types).to.deep.equal(['video']); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); + it('should set vastUrl directly from asset_url when no renderer', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + ext: { + appnexus: { + bid_ad_type: 1, + asset_url: 'https://vast.example.com/vast.xml' + } + } + }] + }] + } + }; - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests(bidRequests); - expect(request.url).to.equal(ENDPOINT); - expect(request.method).to.equal('POST'); - }); + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].vastUrl).to.equal('https://vast.example.com/vast.xml'); + }); - it('should attach valid video params to the tag', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - video: { - id: 123, - minduration: 100, - foobar: 'invalid' - } + it('should set vastImpUrl from nurl and vastUrl from asset_url when both present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + nurl: 'https://notify.example.com/win', + ext: { + appnexus: { + bid_ad_type: 1, + asset_url: 'https://vast.example.com/vast.xml' + } + } + }] + }] } - } - ); + }; - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - id: 123, - minduration: 100 + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].vastUrl).to.equal('https://vast.example.com/vast.xml'); + expect(bids[0].vastImpUrl).to.equal('https://notify.example.com/win'); }); - expect(payload.tags[0].hb_source).to.deep.equal(1); }); - - it('should include ORTB video values when video params were not set', function() { - const bidRequest = deepClone(bidRequests[0]); - bidRequest.params = { - placementId: '1234235', - video: { - skippable: true, - playback_method: ['auto_play_sound_off', 'auto_play_sound_unknown'], - context: 'outstream' + } // FEATURES.VIDEO + + // ------------------------------------------------------------------------- + // interpretResponse — debug info logging + // ------------------------------------------------------------------------- + describe('interpretResponse - debug info logging', function () { + it('should clean HTML and call logMessage when debug_info is present', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const logStub = sandbox.stub(utils, 'logMessage'); + + spec.interpretResponse({ + body: { + seatbid: [], + debug: { debug_info: '

Auction Debug


Row' } } - }; - bidRequest.mediaTypes = { - video: { - playerSize: [640, 480], - context: 'outstream', - mimes: ['video/mp4'], - skip: 0, - minduration: 5, - api: [1, 5, 6], - playbackmethod: [2, 4] - } - }; + }, req); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + expect(logStub.calledOnce).to.be.true; + expect(logStub.firstCall.args[0]).to.include('===== Auction Debug ====='); + expect(logStub.firstCall.args[0]).to.not.include('

'); + }); + }); - expect(payload.tags[0].video).to.deep.equal({ - minduration: 5, - playback_method: 2, - skippable: true, - context: 4 + // ------------------------------------------------------------------------- + // interpretResponse — native exhaustive assets + // ------------------------------------------------------------------------- + if (FEATURES.NATIVE) { + describe('interpretResponse - native exhaustive assets', function () { + it('should map all optional native fields', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + // OpenRTB 1.2 assets array format (as returned by the /openrtb2/prebidjs endpoint) + const nativeAdm = { + native: { + assets: [ + { id: 1, title: { text: 'Title' } }, + { id: 2, data: { type: 2, value: 'Body' } }, + { id: 3, data: { type: 10, value: 'Body2' } }, + { id: 4, data: { type: 12, value: 'Click' } }, + { id: 5, data: { type: 3, value: '4.5' } }, + { id: 6, data: { type: 1, value: 'Sponsor' } }, + { id: 7, data: { type: 9, value: '123 Main St' } }, + { id: 8, data: { type: 5, value: '1000' } }, + { id: 9, data: { type: 4, value: '500' } }, + { id: 10, data: { type: 8, value: '555-1234' } }, + { id: 11, data: { type: 6, value: '$9.99' } }, + { id: 12, data: { type: 7, value: '$4.99' } }, + { id: 13, data: { type: 11, value: 'example.com' } }, + { id: 14, img: { type: 3, url: 'https://img.example.com/img.jpg', w: 300, h: 250 } }, + { id: 15, img: { type: 1, url: 'https://img.example.com/icon.png', w: 50, h: 50 } } + ], + link: { url: 'https://click.example.com', clicktrackers: ['https://ct.example.com'] }, + privacy: 'https://priv.example.com' + } + }; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.5, + adm: JSON.stringify(nativeAdm), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; + + const bids = spec.interpretResponse(serverResponse, req); + const native = bids[0].native; + expect(native.title).to.equal('Title'); + expect(native.body).to.equal('Body'); + expect(native.body2).to.equal('Body2'); + expect(native.cta).to.equal('Click'); + expect(native.rating).to.equal('4.5'); + expect(native.sponsoredBy).to.equal('Sponsor'); + expect(native.privacyLink).to.equal('https://priv.example.com'); + expect(native.address).to.equal('123 Main St'); + expect(native.downloads).to.equal('1000'); + expect(native.likes).to.equal('500'); + expect(native.phone).to.equal('555-1234'); + expect(native.price).to.equal('$9.99'); + expect(native.salePrice).to.equal('$4.99'); + expect(native.displayUrl).to.equal('example.com'); + expect(native.clickUrl).to.equal('https://click.example.com'); + expect(native.clickTrackers).to.deep.equal(['https://ct.example.com']); + expect(native.image.url).to.equal('https://img.example.com/img.jpg'); + expect(native.image.width).to.equal(300); + expect(native.icon.url).to.equal('https://img.example.com/icon.png'); }); - 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: { - video: { - context: 'outstream', - mimes: ['video/mp4'] + it('should map native fields using request asset IDs as type fallback when response omits type', function () { + // Build a real request via spec.buildRequests so ortbConverter registers it in its + // internal WeakMap (required by fromORTB). Then inject native.request directly on + // the imp — this simulates what FEATURES.NATIVE would have built without requiring it. + const bid = deepClone(BASE_BID); + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + req.data.imp[0].native = { + request: JSON.stringify({ + assets: [ + { id: 1, title: { len: 100 } }, + { id: 2, data: { type: 1 } }, // sponsoredBy + { id: 3, data: { type: 2 } }, // body + { id: 4, img: { type: 3, wmin: 1, hmin: 1 } }, // main image + { id: 5, img: { type: 1, wmin: 50, hmin: 50 } } // icon + ] + }) + }; + + // Response assets intentionally omit type — Xandr does this in practice + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.5, + adm: JSON.stringify({ + native: { + assets: [ + { id: 1, title: { text: 'Fallback Title' } }, + { id: 2, data: { value: 'Fallback Sponsor' } }, + { id: 3, data: { value: 'Fallback Body' } }, + { id: 4, img: { url: 'https://img.test/img.jpg', w: 300, h: 250 } }, + { id: 5, img: { url: 'https://img.test/icon.png', w: 50, h: 50 } } + ], + link: { url: 'https://click.test' } + } + }), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] } - }, - params: { - placementId: '10433394', - video: { - skippable: true, - playback_method: ['auto_play_sound_off'] + }; + + const bids = spec.interpretResponse(serverResponse, req); + const native = bids[0].native; + expect(native.title).to.equal('Fallback Title'); + expect(native.sponsoredBy).to.equal('Fallback Sponsor'); + expect(native.body).to.equal('Fallback Body'); + expect(native.image.url).to.equal('https://img.test/img.jpg'); + expect(native.icon.url).to.equal('https://img.test/icon.png'); + }); + + it('should handle real-world native response: top-level format (no native wrapper), non-sequential IDs, type fallback', function () { + // Validates the format actually returned by the Mediafuse/Xandr endpoint: + // ADM is top-level {ver, assets, link, eventtrackers} — no 'native' wrapper key. + // Asset IDs are non-sequential (id:0 for title). Data/img assets omit 'type'; + // type is resolved from the native request's asset definitions. + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + // Inject native.request asset definitions so the type-fallback resolves correctly + req.data.imp[0].native = { + request: JSON.stringify({ + assets: [ + { id: 0, title: { len: 100 } }, + { id: 1, img: { type: 3, wmin: 1, hmin: 1 } }, // main image + { id: 2, data: { type: 1 } } // sponsoredBy + ] + }) + }; + + // Real-world ADM: top-level, assets lack 'type', id:0 title, two eventtrackers + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 0.88, + adm: JSON.stringify({ + ver: '1.2', + assets: [ + { id: 1, img: { url: 'https://img.example.com/img.jpg', w: 150, h: 150 } }, + { id: 0, title: { text: 'Discover Insights That Matter' } }, + { id: 2, data: { value: 'probescout' } } + ], + link: { url: 'https://click.example.com' }, + eventtrackers: [ + { event: 1, method: 1, url: 'https://tracker1.example.com/it' }, + { event: 1, method: 1, url: 'https://tracker2.example.com/t' } + ] + }), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] } - } - }; + }; + + const bids = spec.interpretResponse(serverResponse, req); + const native = bids[0].native; + expect(native.title).to.equal('Discover Insights That Matter'); + expect(native.sponsoredBy).to.equal('probescout'); + expect(native.image.url).to.equal('https://img.example.com/img.jpg'); + expect(native.image.width).to.equal(150); + expect(native.image.height).to.equal(150); + expect(native.clickUrl).to.equal('https://click.example.com'); + expect(native.javascriptTrackers).to.be.an('array').with.lengthOf(2); + }); - let bidRequest1 = deepClone(bidRequests[0]); - bidRequest1 = Object.assign({}, bidRequest1, videoData, { - renderer: { - url: 'https://test.renderer.url', - render: function () {} - } + it('should disarm eventtrackers (trk.js) by replacing src= with data-src=', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const nativeAdm = { + native: { + title: 'T', + eventtrackers: [ + { method: 1, url: '//cdn.adnxs.com/v/trk.js?src=1&dom_id=%native_dom_id%' }, + { method: 1, url: 'https://other-tracker.com/pixel' } + ] + } + }; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify(nativeAdm), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; + + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array'); + // The trk.js tracker should be disarmed: 'src=' replaced with 'data-src=' + const trkTracker = trackers.find(t => t.includes('trk.js')); + expect(trkTracker).to.include('data-src='); + // Verify the original 'src=1' param is now 'data-src=1' (not a bare 'src=') + expect(trkTracker).to.not.match(/[^-]src=1/); }); - let bidRequest2 = deepClone(bidRequests[0]); - bidRequest2.adUnitCode = 'adUnit_code_2'; - bidRequest2 = Object.assign({}, bidRequest2, videoData); + it('should handle viewability.config disarming', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify({ native: { title: 'T' } }), + ext: { + appnexus: { + bid_ad_type: 3, + viewability: { config: '' } + } + } + }] + }] + } + }; - const request = spec.buildRequests([bidRequest1, bidRequest2]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].video).to.deep.equal({ - skippable: true, - playback_method: 2, - custom_renderer_present: true + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array'); + expect(trackers[0]).to.include('data-src='); }); - expect(payload.tags[1].video).to.deep.equal({ - skippable: true, - playback_method: 2 + + it('should handle malformed native adm gracefully', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const logErrorStub = sandbox.stub(utils, 'logError'); + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: 'NOT_VALID_JSON', + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; + + // Should not throw + expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); + expect(logErrorStub.calledOnce).to.be.true; }); }); + } // FEATURES.NATIVE + + // ------------------------------------------------------------------------- + // getUserSyncs — gdprApplies not a boolean + // ------------------------------------------------------------------------- + describe('getUserSyncs - gdprApplies undefined', function () { + it('should use only gdpr_consent param when gdprApplies is not a boolean', function () { + const syncOptions = { pixelEnabled: true }; + const serverResponses = [{ + body: { ext: { appnexus: { userSync: { url: 'https://sync.example.com/px' } } } } + }]; + const gdprConsent = { consentString: 'abc123' }; // gdprApplies is undefined - it('should attach valid user params to the tag', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - user: { - externalUid: '123', - segments: [123, { id: 987, value: 876 }], - foobar: 'invalid' - } - } - } - ); + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.include('gdpr_consent=abc123'); + expect(syncs[0].url).to.not.include('gdpr='); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // lifecycle — onBidWon + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + // interpretResponse — dchain from buyer_member_id + // ------------------------------------------------------------------------- + describe('interpretResponse - dchain', function () { + it('should set meta.dchain when buyer_member_id is present', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + ext: { appnexus: { bid_ad_type: 0, buyer_member_id: 77, advertiser_id: 99 } } + }] + }] + } + }; - expect(payload.user).to.exist; - expect(payload.user).to.deep.equal({ - external_uid: '123', - segments: [{ id: 123 }, { id: 987, value: 876 }] + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].meta.dchain).to.deep.equal({ + ver: '1.0', + complete: 0, + nodes: [{ bsid: '77' }] }); + expect(bids[0].meta.advertiserId).to.equal(99); }); + }); - it('should attach reserve param when either bid param or getFloor function exists', function () { - const getFloorResponse = { currency: 'USD', floor: 3 }; - let request; let payload = null; - const bidRequest = deepClone(bidRequests[0]); + // ------------------------------------------------------------------------- + // buildRequests — optional params map (allowSmallerSizes, usePaymentRule, etc.) + // ------------------------------------------------------------------------- + describe('buildRequests - optional params', function () { + it('should map allowSmallerSizes, usePaymentRule, trafficSourceCode', function () { + const bid = deepClone(BASE_BID); + bid.params.allowSmallerSizes = true; + bid.params.usePaymentRule = true; + bid.params.trafficSourceCode = 'my-source'; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const extAN = req.data.imp[0].ext.appnexus; + expect(extAN.allow_smaller_sizes).to.be.true; + expect(extAN.use_pmt_rule).to.be.true; + expect(extAN.traffic_source_code).to.equal('my-source'); + }); - // 1 -> reserve not defined, getFloor not defined > empty - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); + it('should map externalImpId to ext.appnexus.ext_imp_id', function () { + const bid = deepClone(BASE_BID); + bid.params.externalImpId = 'ext-imp-123'; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.ext_imp_id).to.equal('ext-imp-123'); + }); + }); - expect(payload.tags[0].reserve).to.not.exist; + // ------------------------------------------------------------------------- + // isBidRequestValid + // ------------------------------------------------------------------------- + describe('isBidRequestValid', function () { + it('should return true for placement_id (snake_case)', function () { + expect(spec.isBidRequestValid({ params: { placement_id: 12345 } })).to.be.true; + }); - // 2 -> reserve is defined, getFloor not defined > reserve is used - bidRequest.params = { - 'placementId': '10433394', - 'reserve': 0.5 - }; - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.exist.and.to.equal(0.5); - - // 3 -> reserve is defined, getFloor is defined > getFloor is used - bidRequest.getFloor = () => getFloorResponse; - - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); - - expect(payload.tags[0].reserve).to.exist.and.to.equal(3); - }); - - it('should duplicate adpod placements into batches and set correct maxduration', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); - }); - - it('should round down adpod placements when numbers are uneven', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); - - it('should duplicate adpod placements when requireExactDuration is set', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest]); - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); - }); - - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - it('should contain hb_source value for adpod', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - const request = spec.buildRequests([bidRequest])[0]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(7); - }); - - it('should contain hb_source value for other media', function() { - const bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'banner', - params: { - sizes: [[300, 250], [300, 600]], - placementId: 13144370 - } - } - ); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(1); - }); - - it('adds brand_category_exclusion to request when set', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('adpod.brandCategoryExclusion') - .returns(true); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.brand_category_uniqueness).to.equal(true); - - config.getConfig.restore(); - }); - - it('adds auction level keywords to request when set', function() { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('mediafuseAuctionKeywords') - .returns({ - gender: 'm', - music: ['rock', 'pop'], - test: '' - }); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.keywords).to.deep.equal([{ - 'key': 'gender', - 'value': ['m'] - }, { - 'key': 'music', - 'value': ['rock', 'pop'] - }, { - 'key': 'test' - }]); - - config.getConfig.restore(); - }); - - it('should attach native params to the request', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - title: { required: true }, - body: { required: true }, - body2: { required: true }, - image: { required: true, sizes: [100, 100] }, - icon: { required: true }, - cta: { required: false }, - rating: { required: true }, - sponsoredBy: { required: true }, - privacyLink: { required: true }, - displayUrl: { required: true }, - address: { required: true }, - downloads: { required: true }, - likes: { required: true }, - phone: { required: true }, - price: { required: true }, - salePrice: { required: true } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].native.layouts[0]).to.deep.equal({ - title: { required: true }, - description: { required: true }, - desc2: { required: true }, - main_image: { required: true, sizes: [{ width: 100, height: 100 }] }, - icon: { required: true }, - ctatext: { required: false }, - rating: { required: true }, - sponsored_by: { required: true }, - privacy_link: { required: true }, - displayurl: { required: true }, - address: { required: true }, - downloads: { required: true }, - likes: { required: true }, - phone: { required: true }, - price: { required: true }, - saleprice: { required: true }, - privacy_supported: true - }); - expect(payload.tags[0].hb_source).to.equal(1); + it('should return true for member + invCode', function () { + expect(spec.isBidRequestValid({ params: { member: '123', invCode: 'inv' } })).to.be.true; }); - it('should always populated tags[].sizes with 1,1 for native if otherwise not defined', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - mediaType: 'native', - nativeParams: { - image: { required: true } - } - } - ); - bidRequest.sizes = [[150, 100], [300, 250]]; - - let request = spec.buildRequests([bidRequest]); - let payload = JSON.parse(request.data); - expect(payload.tags[0].sizes).to.deep.equal([{ width: 150, height: 100 }, { width: 300, height: 250 }]); - - delete bidRequest.sizes; - - request = spec.buildRequests([bidRequest]); - payload = JSON.parse(request.data); - - expect(payload.tags[0].sizes).to.deep.equal([{ width: 1, height: 1 }]); - }); - - it('should convert keyword params to proper form and attaches to request', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - keywords: { - single: 'val', - singleArr: ['val'], - singleArrNum: [5], - multiValMixed: ['value1', 2, 'value3'], - singleValNum: 123, - emptyStr: '', - emptyArr: [''], - badValue: { 'foo': 'bar' } // should be dropped - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.tags[0].keywords).to.deep.equal([{ - 'key': 'single', - 'value': ['val'] - }, { - 'key': 'singleArr', - 'value': ['val'] - }, { - 'key': 'singleArrNum', - 'value': ['5'] - }, { - 'key': 'multiValMixed', - 'value': ['value1', '2', 'value3'] - }, { - 'key': 'singleValNum', - 'value': ['123'] - }, { - 'key': 'emptyStr' - }, { - 'key': 'emptyArr' - }]); - }); - - it('should add payment rules to the request', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - usePaymentRule: true - } - } - ); + it('should return true for member + inv_code', function () { + expect(spec.isBidRequestValid({ params: { member: '123', inv_code: 'inv' } })).to.be.true; + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should return false when no params', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); - expect(payload.tags[0].use_pmt_rule).to.equal(true); + it('should return false for member without invCode or inv_code', function () { + expect(spec.isBidRequestValid({ params: { member: '123' } })).to.be.false; }); + }); - it('should add gpid to the request', function () { - const testGpid = '/12345/my-gpt-tag-0'; - const bidRequest = deepClone(bidRequests[0]); - bidRequest.ortb2Imp = { ext: { data: {}, gpid: testGpid } }; + // ------------------------------------------------------------------------- + // getBidFloor + // ------------------------------------------------------------------------- + describe('buildRequests - getBidFloor', function () { + it('should use getFloor function result when available and currency matches', function () { + const bid = deepClone(BASE_BID); + bid.getFloor = () => ({ currency: 'USD', floor: 1.5 }); + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].bidfloor).to.equal(1.5); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + it('should return null when getFloor returns wrong currency', function () { + const bid = deepClone(BASE_BID); + bid.getFloor = () => ({ currency: 'EUR', floor: 1.5 }); + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].bidfloor).to.be.undefined; + }); - expect(payload.tags[0].gpid).to.exist.and.equal(testGpid) + it('should use params.reserve when no getFloor function', function () { + const bid = deepClone(BASE_BID); + bid.params.reserve = 2.0; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].bidfloor).to.equal(2.0); }); + }); - it('should add gdpr consent information to the request', function () { - const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - 'bidderCode': 'mediafuse', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - addtlConsent: '1~7.12.35.62.66.70.89.93.108' - } - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.options).to.deep.equal({ withCredentials: true }); - const payload = JSON.parse(request.data); - - expect(payload.gdpr_consent).to.exist; - expect(payload.gdpr_consent.consent_string).to.exist.and.to.equal(consentString); - expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; - expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); - }); - - it('should add us privacy string to payload', function() { - const consentString = '1YA-'; - const bidderRequest = { - 'bidderCode': 'mediafuse', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'uspConsent': consentString - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.us_privacy).to.exist; - expect(payload.us_privacy).to.exist.and.to.equal(consentString); - }); - - it('supports sending hybrid mobile app parameters', function () { - const appRequest = Object.assign({}, - bidRequests[0], - { - params: { - placementId: '10433394', - app: { - id: 'B1O2W3M4AN.com.prebid.webview', - geo: { - lat: 40.0964439, - lng: -75.3009142 - }, - device_id: { - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', // Apple advertising identifier - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', // Android advertising identifier - md5udid: '5756ae9022b2ea1e47d84fead75220c8', // MD5 hash of the ANDROID_ID - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', // SHA1 hash of the ANDROID_ID - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' // Windows advertising identifier - } - } - } - } - ); - const request = spec.buildRequests([appRequest]); - const payload = JSON.parse(request.data); - expect(payload.app).to.exist; - expect(payload.app).to.deep.equal({ - appid: 'B1O2W3M4AN.com.prebid.webview' - }); - expect(payload.device.device_id).to.exist; - expect(payload.device.device_id).to.deep.equal({ - aaid: '38400000-8cf0-11bd-b23e-10b96e40000d', - idfa: '4D12078D-3246-4DA4-AD5E-7610481E7AE', - md5udid: '5756ae9022b2ea1e47d84fead75220c8', - sha1udid: '4DFAA92388699AC6539885AEF1719293879985BF', - windowsadid: '750c6be243f1c4b5c9912b95a5742fc5' - }); - expect(payload.device.geo).to.not.exist; - expect(payload.device.geo).to.not.deep.equal({ - lat: 40.0964439, - lng: -75.3009142 - }); + // ------------------------------------------------------------------------- + // buildRequests — inv_code + // ------------------------------------------------------------------------- + describe('buildRequests - inv_code', function () { + it('should set tagid from invCode when no placementId', function () { + const bid = { bidder: 'mediafuse', adUnitCode: 'au', bidId: 'b1', params: { invCode: 'my-inv-code', member: '123' } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].tagid).to.equal('my-inv-code'); }); - it('should add referer info to payload', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequest = { - refererInfo: { - topmostLocation: 'https://example.com/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://example.com/page.html', - 'https://example.com/iframe1.html', - 'https://example.com/iframe2.html' - ] - } - } - const request = spec.buildRequests([bidRequest], bidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.referrer_detection).to.exist; - expect(payload.referrer_detection).to.deep.equal({ - rd_ref: 'https%3A%2F%2Fexample.com%2Fpage.html', - rd_top: true, - rd_ifs: 2, - rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') - }); + it('should set tagid from inv_code when no placementId', function () { + const bid = { bidder: 'mediafuse', adUnitCode: 'au', bidId: 'b1', params: { inv_code: 'my-inv-code-snake', member: '123' } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].tagid).to.equal('my-inv-code-snake'); }); + }); - it('should populate schain if available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - ortb2: { - source: { - ext: { - schain: { - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] - } - } - } - } - }); + // ------------------------------------------------------------------------- + // buildRequests — banner_frameworks + // ------------------------------------------------------------------------- + describe('buildRequests - banner_frameworks', function () { + it('should set banner_frameworks from bid.params.banner_frameworks when no banner.api', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.params.banner_frameworks = [1, 2, 3]; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.banner_frameworks).to.deep.equal([1, 2, 3]); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.schain).to.deep.equal({ - ver: '1.0', - complete: 1, - nodes: [ - { - 'asi': 'blob.com', - 'sid': '001', - 'hp': 1 - } - ] + // ------------------------------------------------------------------------- + // buildRequests — custom_renderer_present via bid.renderer + // ------------------------------------------------------------------------- + describe('buildRequests - custom renderer present', function () { + if (FEATURES.VIDEO) { + it('should set custom_renderer_present when bid.renderer is set for video imp', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'outstream', playerSize: [640, 480] } }; + bid.renderer = { id: 'custom', url: 'https://renderer.example.com/r.js' }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.custom_renderer_present).to.be.true; }); + } + }); + + // ------------------------------------------------------------------------- + // buildRequests — catch-all unknown camelCase params + // ------------------------------------------------------------------------- + describe('buildRequests - catch-all unknown params', function () { + it('should convert unknown camelCase params to snake_case in extAN', function () { + const bid = deepClone(BASE_BID); + bid.params.unknownCamelCaseParam = 'value123'; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.unknown_camel_case_param).to.equal('value123'); + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — bid-level keywords + // ------------------------------------------------------------------------- + describe('buildRequests - bid keywords', function () { + it('should map bid.params.keywords to extAN.keywords string', function () { + const bid = deepClone(BASE_BID); + bid.params.keywords = { genre: ['rock', 'pop'] }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.keywords).to.be.a('string'); + expect(req.data.imp[0].ext.appnexus.keywords).to.include('genre=rock,pop'); }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — canonicalUrl in referer detection + // ------------------------------------------------------------------------- + describe('buildRequests - canonicalUrl', function () { + it('should set rd_can in referrer_detection when canonicalUrl is present', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.refererInfo.canonicalUrl = 'https://canonical.example.com/page'; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.ext.appnexus.referrer_detection.rd_can).to.equal('https://canonical.example.com/page'); + }); + }); - it('should populate coppa if set in config', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + // ------------------------------------------------------------------------- + // buildRequests — publisherId → site.publisher.id + // ------------------------------------------------------------------------- + describe('buildRequests - publisherId', function () { + it('should set site.publisher.id from bid.params.publisherId', function () { + const bid = deepClone(BASE_BID); + bid.params.publisherId = 67890; + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.site.publisher.id).to.equal('67890'); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); + // ------------------------------------------------------------------------- + // buildRequests — member appended to endpoint URL + // ------------------------------------------------------------------------- + describe('buildRequests - member URL param', function () { + it('should append member_id to endpoint URL when bid.params.member is set', function () { + const bid = deepClone(BASE_BID); + bid.params.member = '456'; + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.url).to.include('member_id=456'); + }); + }); - expect(payload.user.coppa).to.equal(true); + // ------------------------------------------------------------------------- + // buildRequests — gppConsent + // ------------------------------------------------------------------------- + describe('buildRequests - gppConsent', function () { + it('should set regs.gpp and regs.gpp_sid from gppConsent', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gppConsent = { gppString: 'DBACMYA', applicableSections: [7] }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.regs.gpp).to.equal('DBACMYA'); + expect(req.data.regs.gpp_sid).to.deep.equal([7]); + }); + }); - config.getConfig.restore(); + // ------------------------------------------------------------------------- + // buildRequests — gdprApplies=false + // ------------------------------------------------------------------------- + describe('buildRequests - gdprApplies false', function () { + it('should set regs.ext.gdpr=0 when gdprApplies is false', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { gdprApplies: false, consentString: 'cs' }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.regs.ext.gdpr).to.equal(0); }); + }); - it('should set the X-Is-Test customHeader if test flag is enabled', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon.stub(config, 'getConfig') - .withArgs('apn_test') - .returns(true); + // ------------------------------------------------------------------------- + // buildRequests — user.externalUid + // ------------------------------------------------------------------------- + describe('buildRequests - user externalUid', function () { + it('should map externalUid to user.external_uid', function () { + const bid = deepClone(BASE_BID); + bid.params.user = { externalUid: 'uid-abc-123' }; + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.bids = [bid]; + const [req] = spec.buildRequests([bid], bidderRequest); + expect(req.data.user.external_uid).to.equal('uid-abc-123'); + }); + }); - const request = spec.buildRequests([bidRequest]); - expect(request.options.customHeaders).to.deep.equal({ 'X-Is-Test': 1 }); + // ------------------------------------------------------------------------- + // buildRequests — EID rtiPartner mapping (TDID / UID2) + // ------------------------------------------------------------------------- + describe('buildRequests - EID rtiPartner mapping', function () { + it('should set rtiPartner=TDID inside uids[0].ext for adserver.org EID', function () { + const bid = deepClone(BASE_BID); + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.ortb2.user = { ext: { eids: [{ source: 'adserver.org', uids: [{ id: 'tdid-value', atype: 1 }] }] } }; + const [req] = spec.buildRequests([bid], bidderRequest); + const eid = req.data.user?.ext?.eids?.find(e => e.source === 'adserver.org'); + expect(eid).to.exist; + expect(eid.uids[0].ext.rtiPartner).to.equal('TDID'); + expect(eid.rti_partner).to.be.undefined; + }); - config.getConfig.restore(); + it('should set rtiPartner=UID2 inside uids[0].ext for uidapi.com EID', function () { + const bid = deepClone(BASE_BID); + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.ortb2.user = { ext: { eids: [{ source: 'uidapi.com', uids: [{ id: 'uid2-value', atype: 3 }] }] } }; + const [req] = spec.buildRequests([bid], bidderRequest); + const eid = req.data.user?.ext?.eids?.find(e => e.source === 'uidapi.com'); + expect(eid).to.exist; + expect(eid.uids[0].ext.rtiPartner).to.equal('UID2'); + expect(eid.rti_partner).to.be.undefined; }); - it('should always set withCredentials: true on the request.options', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - const request = spec.buildRequests([bidRequest]); - expect(request.options.withCredentials).to.equal(true); + it('should preserve existing uid.ext fields when adding rtiPartner', function () { + const bid = deepClone(BASE_BID); + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.ortb2.user = { ext: { eids: [{ source: 'adserver.org', uids: [{ id: 'tdid-value', atype: 1, ext: { existing: true } }] }] } }; + const [req] = spec.buildRequests([bid], bidderRequest); + const eid = req.data.user?.ext?.eids?.find(e => e.source === 'adserver.org'); + expect(eid).to.exist; + expect(eid.uids[0].ext.rtiPartner).to.equal('TDID'); + expect(eid.uids[0].ext.existing).to.be.true; }); + }); - it('should set simple domain variant if purpose 1 consent is not given', function () { - const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - 'bidderCode': 'mediafuse', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'gdprConsent': { - consentString: consentString, - gdprApplies: true, - apiVersion: 2, - vendorData: { - purpose: { - consents: { - 1: false - } - } - } - } - }; - bidderRequest.bids = bidRequests; - - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.equal('https://ib.adnxs-simple.com/ut/v3/prebid'); - }); - - it('should populate eids when supported userIds are available', function () { - const bidRequest = Object.assign({}, bidRequests[0], { - 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' }] - }] + // ------------------------------------------------------------------------- + // buildRequests — apn_test config → X-Is-Test header + // ------------------------------------------------------------------------- + describe('buildRequests - apn_test config header', function () { + it('should set X-Is-Test:1 custom header when config apn_test=true', function () { + sandbox.stub(config, 'getConfig').callsFake((key) => { + if (key === 'apn_test') return true; + return undefined; }); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.options.customHeaders).to.deep.equal({ 'X-Is-Test': 1 }); + }); + }); - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.eids).to.deep.include({ - source: 'adserver.org', - id: 'sample-userid', - rti_partner: 'TDID' + // ------------------------------------------------------------------------- + // buildRequests — video minduration already set (skip overwrite) + // ------------------------------------------------------------------------- + describe('buildRequests - video minduration skip overwrite', function () { + if (FEATURES.VIDEO) { + it('should not overwrite minduration set by params.video when mediaTypes.video.minduration also present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], minduration: 10 } }; + bid.params.video = { minduration: 5 }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + // params.video sets minduration=5 first; mediaTypes check sees it's already a number → skips + expect(req.data.imp[0].video.minduration).to.equal(5); }); + } + }); - expect(payload.eids).to.deep.include({ - source: 'criteo.com', - id: 'sample-criteo-userid', + // ------------------------------------------------------------------------- + // buildRequests — playbackmethod out of range (>4) + // ------------------------------------------------------------------------- + describe('buildRequests - video playbackmethod out of range', function () { + if (FEATURES.VIDEO) { + it('should not set playback_method when playbackmethod[0] > 4', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], playbackmethod: [5] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.playback_method).to.be.undefined; }); + } + }); - expect(payload.eids).to.deep.include({ - source: 'netid.de', - id: 'sample-netId-userid', + // ------------------------------------------------------------------------- + // buildRequests — video api val=6 filtered out + // ------------------------------------------------------------------------- + describe('buildRequests - video api val=6 filtered', function () { + if (FEATURES.VIDEO) { + it('should produce empty video_frameworks when api=[6] since 6 is out of 1-5 range', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], api: [6] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.video_frameworks).to.deep.equal([]); }); + } + }); - expect(payload.eids).to.deep.include({ - source: 'liveramp.com', - id: 'sample-idl-userid' + // ------------------------------------------------------------------------- + // buildRequests — video_frameworks already set; api should not override + // ------------------------------------------------------------------------- + describe('buildRequests - video_frameworks not overridden by api', function () { + if (FEATURES.VIDEO) { + it('should keep frameworks from params.video when mediaTypes.video.api is also present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], api: [4] } }; + bid.params.video = { frameworks: [1, 2] }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.video_frameworks).to.deep.equal([1, 2]); }); + } + }); - expect(payload.eids).to.deep.include({ - source: 'uidapi.com', - id: 'sample-uid2-value', - rti_partner: 'UID2' - }); + // ------------------------------------------------------------------------- + // interpretResponse — adomain string vs empty array + // ------------------------------------------------------------------------- + describe('interpretResponse - adomain handling', function () { + it('should wrap string adomain in an array for advertiserDomains', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adomain: 'example.com', + ext: { appnexus: { bid_ad_type: 0 } } + }] + }] + } + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].meta.advertiserDomains).to.deep.equal(['example.com']); }); - it('should populate iab_support object at the root level if omid support is detected', function () { - // with bid.params.frameworks - const bidRequest_A = Object.assign({}, bidRequests[0], { - params: { - frameworks: [1, 2, 5, 6], - video: { - frameworks: [1, 2, 5, 6] - } + it('should not set non-empty advertiserDomains when adomain is an empty array', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adomain: [], + ext: { appnexus: { bid_ad_type: 0 } } + }] + }] + } + }; + const bids = spec.interpretResponse(serverResponse, req); + // adapter's guard skips setting advertiserDomains for empty arrays; + // ortbConverter may set it to [] — either way it must not be a non-empty array + const domains = bids[0].meta && bids[0].meta.advertiserDomains; + expect(!domains || domains.length === 0).to.be.true; + }); + }); + + // ------------------------------------------------------------------------- + // interpretResponse — banner impression_urls trackers + // ------------------------------------------------------------------------- + describe('interpretResponse - banner trackers', function () { + it('should append tracker pixel HTML to bid.ad when trackers.impression_urls is present', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: '
ad
', + ext: { + appnexus: { + bid_ad_type: 0, + trackers: [{ impression_urls: ['https://tracker.example.com/impression'] }] + } + } + }] + }] } + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].ad).to.include('tracker.example.com/impression'); + }); + }); + + // ------------------------------------------------------------------------- + // interpretResponse — native jsTrackers combinations + // ------------------------------------------------------------------------- + if (FEATURES.NATIVE) { + describe('interpretResponse - native jsTrackers combinations', function () { + function buildNativeReq() { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + return req; + } + + it('should combine string jsTracker with viewability.config into array [str, disarmed]', function () { + const req = buildNativeReq(); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify({ native: { title: 'T', javascript_trackers: 'https://existing-tracker.com/t.js' } }), + ext: { + appnexus: { + bid_ad_type: 3, + viewability: { config: '' } + } + } + }] + }] + } + }; + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array').with.lengthOf(2); + expect(trackers[0]).to.equal('https://existing-tracker.com/t.js'); + expect(trackers[1]).to.include('data-src='); }); - let request = spec.buildRequests([bidRequest_A]); - let payload = JSON.parse(request.data); - expect(payload.iab_support).to.be.an('object'); - expect(payload.iab_support).to.deep.equal({ - omidpn: 'Mediafuse', - omidpv: '$prebid.version$' + + it('should push viewability.config into existing array jsTrackers', function () { + const req = buildNativeReq(); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify({ native: { title: 'T', javascript_trackers: ['https://tracker1.com/t.js'] } }), + ext: { + appnexus: { + bid_ad_type: 3, + viewability: { config: '' } + } + } + }] + }] + } + }; + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array').with.lengthOf(2); + expect(trackers[0]).to.equal('https://tracker1.com/t.js'); + expect(trackers[1]).to.include('data-src='); }); - expect(payload.tags[0].banner_frameworks).to.be.an('array'); - expect(payload.tags[0].banner_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video_frameworks).to.be.an('array'); - expect(payload.tags[0].video_frameworks).to.deep.equal([1, 2, 5, 6]); - expect(payload.tags[0].video.frameworks).to.not.exist; - - // without bid.params.frameworks - const bidRequest_B = Object.assign({}, bidRequests[0]); - request = spec.buildRequests([bidRequest_B]); - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; - - // with video.frameworks but it is not an array - const bidRequest_C = Object.assign({}, bidRequests[0], { - params: { - video: { - frameworks: "'1', '2', '3', '6'" + + it('should combine string jsTracker with eventtrackers into array', function () { + const req = buildNativeReq(); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify({ + native: { + title: 'T', + javascript_trackers: 'https://existing-tracker.com/t.js', + eventtrackers: [{ method: 1, url: 'https://event-tracker.com/track' }] + } + }), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] } - } + }; + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array').with.lengthOf(2); + expect(trackers[0]).to.equal('https://existing-tracker.com/t.js'); + expect(trackers[1]).to.equal('https://event-tracker.com/track'); }); - request = spec.buildRequests([bidRequest_C]); - payload = JSON.parse(request.data); - expect(payload.iab_support).to.not.exist; - expect(payload.tags[0].banner_frameworks).to.not.exist; - expect(payload.tags[0].video_frameworks).to.not.exist; - }); - }) - - describe('interpretResponse', function () { - let bidderSettingsStorage; - - before(function() { - bidderSettingsStorage = getGlobal().bidderSettings; - }); - - after(function() { - getGlobal().bidderSettings = bidderSettingsStorage; - }); - - const response = { - 'version': '3.0.0', - 'tags': [ - { - 'uuid': '3db3773286ee59', - 'tag_id': 10433394, - 'auction_id': '4534722592064951574', - 'nobid': false, - 'no_ad_url': 'https://lax1-ib.adnxs.com/no-ad', - 'timeout_ms': 10000, - 'ad_profile_id': 27079, - 'ads': [ - { - 'content_source': 'rtb', - 'ad_type': 'banner', - 'buyer_member_id': 958, - 'creative_id': 29681110, - 'media_type_id': 1, - 'media_subtype_id': 1, - 'cpm': 0.5, - 'cpm_publisher_currency': 0.5, - 'publisher_currency_code': '$', - 'client_initiated_ad_counting': true, - 'viewability': { - 'config': '' - }, - 'rtb': { - 'banner': { - 'content': '', - 'width': 300, - 'height': 250 - }, - 'trackers': [ - { - 'impression_urls': [ - 'https://lax1-ib.adnxs.com/impression', - 'https://www.test.com/tracker' - ], - 'video_events': {} + + it('should push eventtrackers into existing array jsTrackers', function () { + const req = buildNativeReq(); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify({ + native: { + title: 'T', + javascript_trackers: ['https://existing-tracker.com/t.js'], + eventtrackers: [{ method: 1, url: 'https://event-tracker.com/track' }] } - ] - } - } - ] - } - ] - }; - - it('should get correct bid response', function () { - const expectedResponse = [ - { - 'requestId': '3db3773286ee59', - 'cpm': 0.5, - 'creativeId': 29681110, - 'dealId': undefined, - 'width': 300, - 'height': 250, - 'ad': '', - 'mediaType': 'banner', - 'currency': 'USD', - 'ttl': 300, - 'netRevenue': true, - 'adUnitCode': 'code', - 'mediafuse': { - 'buyerMemberId': 958 - }, - 'meta': { - 'dchain': { - 'ver': '1.0', - 'complete': 0, - 'nodes': [{ - 'bsid': '958' + }), + ext: { appnexus: { bid_ad_type: 3 } } }] - } + }] } - } - ]; - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] + }; + const bids = spec.interpretResponse(serverResponse, req); + const trackers = bids[0].native.javascriptTrackers; + expect(trackers).to.be.an('array').with.lengthOf(2); + expect(trackers[0]).to.equal('https://existing-tracker.com/t.js'); + expect(trackers[1]).to.equal('https://event-tracker.com/track'); + }); + + it('should replace %native_dom_id% macro in eventtrackers during interpretResponse', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { native: { title: { required: true } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const adWithMacro = { + native: { + title: 'T', + eventtrackers: [{ + method: 1, + url: 'https://cdn.adnxs.com/v/trk.js?dom_id=%native_dom_id%&id=123' + }] + } + }; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + adm: JSON.stringify(adWithMacro), + ext: { appnexus: { bid_ad_type: 3 } } + }] + }] + } + }; + + const bids = spec.interpretResponse(serverResponse, req); + const parsedAdm = JSON.parse(bids[0].ad); + const trackers = parsedAdm.native?.eventtrackers || parsedAdm.eventtrackers; + expect(trackers[0].url).to.include('pbjs_adid='); + expect(trackers[0].url).to.include('pbjs_auc='); + expect(trackers[0].url).to.not.include('%native_dom_id%'); + }); + }); + } // FEATURES.NATIVE + + // ------------------------------------------------------------------------- + // getUserSyncs — iframe and pixel syncing + // ------------------------------------------------------------------------- + describe('getUserSyncs - iframe and pixel syncing', function () { + it('should add iframe sync when iframeEnabled and purpose-1 consent is present', function () { + const syncOptions = { iframeEnabled: true }; + const gdprConsent = { + gdprApplies: true, + consentString: 'cs', + vendorData: { purpose: { consents: { 1: true } } } }; - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.include('gdpr=1'); }); - it('should reject 0 cpm bids', function () { - const zeroCpmResponse = deepClone(response); - zeroCpmResponse.tags[0].ads[0].cpm = 0; + it('should have no gdpr params in pixel url when gdprConsent is null', function () { + const syncOptions = { pixelEnabled: true }; + const serverResponses = [{ + body: { ext: { appnexus: { userSync: { url: 'https://sync.example.com/px' } } } } + }]; + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].url).to.not.include('gdpr'); + }); + + it('should append gdpr params with & when pixel url already contains ?', function () { + const syncOptions = { pixelEnabled: true }; + const serverResponses = [{ + body: { ext: { appnexus: { userSync: { url: 'https://sync.example.com/px?existing=1' } } } } + }]; + const gdprConsent = { gdprApplies: true, consentString: 'cs' }; + const syncs = spec.getUserSyncs(syncOptions, serverResponses, gdprConsent); + expect(syncs[0].url).to.include('existing=1'); + expect(syncs[0].url).to.include('gdpr=1'); + expect(syncs[0].url).to.match(/\?existing=1&/); + }); + }); - const bidderRequest = { - bidderCode: 'mediafuse' + // ------------------------------------------------------------------------- + // getUserSyncs — iframeEnabled but consent denied (no iframe added) + // ------------------------------------------------------------------------- + describe('getUserSyncs - iframeEnabled denied by consent', function () { + it('should not add iframe sync when iframeEnabled but purpose-1 consent is denied', function () { + const syncOptions = { iframeEnabled: true }; + const gdprConsent = { + gdprApplies: true, + consentString: 'cs', + vendorData: { purpose: { consents: { 1: false } } } }; + const syncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + expect(syncs).to.have.lengthOf(0); + }); - const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); - expect(result.length).to.equal(0); + it('should not add pixel sync when serverResponses is empty', function () { + const syncOptions = { pixelEnabled: true }; + const syncs = spec.getUserSyncs(syncOptions, [], null); + expect(syncs).to.have.lengthOf(0); }); - it('should allow 0 cpm bids if allowZeroCpmBids setConfig is true', function () { - getGlobal().bidderSettings = { - mediafuse: { - allowZeroCpmBids: true + it('should not add pixel sync when response has no userSync url', function () { + const syncOptions = { pixelEnabled: true }; + const serverResponses = [{ body: { ext: { appnexus: {} } } }]; + const syncs = spec.getUserSyncs(syncOptions, serverResponses, null); + expect(syncs).to.have.lengthOf(0); + }); + }); + + // ------------------------------------------------------------------------- + // interpretResponse — bid_ad_type not in RESPONSE_MEDIA_TYPE_MAP + // ------------------------------------------------------------------------- + describe('interpretResponse - unknown bid_ad_type', function () { + it('should not throw when bid_ad_type=2 is not in the media type map', function () { + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.5, + adm: '
creative
', + ext: { appnexus: { bid_ad_type: 2 } } + }] + }] } }; + expect(() => spec.interpretResponse(serverResponse, req)).to.not.throw(); + }); + }); - const zeroCpmResponse = deepClone(response); - zeroCpmResponse.tags[0].ads[0].cpm = 0; - - const bidderRequest = { - bidderCode: 'mediafuse', - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] + // ------------------------------------------------------------------------- + // buildRequests — topmostLocation falsy → rd_ref='' + // ------------------------------------------------------------------------- + describe('buildRequests - topmostLocation falsy', function () { + it('should set rd_ref to empty string when topmostLocation is not present', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.refererInfo = { + topmostLocation: null, + reachedTop: false, + numIframes: 0, + stack: [] }; - - const result = spec.interpretResponse({ body: zeroCpmResponse }, { bidderRequest }); - expect(result.length).to.equal(1); - expect(result[0].cpm).to.equal(0); + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.ext.appnexus.referrer_detection.rd_ref).to.equal(''); }); + }); - it('handles nobid responses', function () { - const response = { - 'version': '0.0.1', - 'tags': [{ - 'uuid': '84ab500420319d', - 'tag_id': 5976557, - 'auction_id': '297492697822162468', - 'nobid': true - }] - }; - let bidderRequest; - - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result.length).to.equal(0); - }); - - it('handles outstream video responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'content': '' - } - }, - 'javascriptTrackers': '' - }] - }] + // ------------------------------------------------------------------------- + // buildRequests — addtlConsent all-NaN → addtl_consent not set + // ------------------------------------------------------------------------- + describe('buildRequests - addtlConsent all-NaN values', function () { + it('should not set addtl_consent when all values after ~ are NaN', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'cs', + addtlConsent: '1~abc.def.ghi' }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'outstream' - } - } - }] + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.user && req.data.user.ext && req.data.user.ext.addtl_consent).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — EID with unrecognized source passes through unchanged + // ------------------------------------------------------------------------- + describe('buildRequests - EID unrecognized source', function () { + it('should pass through EID unchanged when source is neither adserver.org nor uidapi.com', function () { + const bid = deepClone(BASE_BID); + bid.userIdAsEids = [{ source: 'unknown-id-provider.com', uids: [{ id: 'some-id', atype: 1 }] }]; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const eid = req.data.user && req.data.user.ext && req.data.user.ext.eids && + req.data.user.ext.eids.find(e => e.source === 'unknown-id-provider.com'); + if (eid) { + expect(eid.rti_partner).to.be.undefined; } + }); + }); - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastXml'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - - it('handles instream video responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/vid' - } - }, - 'javascriptTrackers': '' - }] - }] + // ------------------------------------------------------------------------- + // getBidFloor — edge cases + // ------------------------------------------------------------------------- + describe('buildRequests - getBidFloor edge cases', function () { + it('should return null when getFloor returns a non-plain-object (null)', function () { + const bid = deepClone(BASE_BID); + bid.getFloor = () => null; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].bidfloor).to.be.undefined; + }); + + it('should return null when getFloor returns a NaN floor value', function () { + const bid = deepClone(BASE_BID); + bid.getFloor = () => ({ currency: 'USD', floor: NaN }); + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].bidfloor).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — banner_frameworks type guard + // ------------------------------------------------------------------------- + describe('buildRequests - banner_frameworks invalid type', function () { + it('should not set banner_frameworks when value is a string (not array of nums)', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.params.banner_frameworks = 'not-an-array'; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.banner_frameworks).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — video params frameworks type guard + // ------------------------------------------------------------------------- + describe('buildRequests - video params frameworks', function () { + it('should not set video_frameworks when params.video.frameworks is not an array', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + bid.params.video = { frameworks: 'not-an-array' }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.video_frameworks).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — banner_frameworks param fallback + // ------------------------------------------------------------------------- + describe('buildRequests - banner frameworks param fallback', function () { + it('should use bid.params.frameworks as fallback when banner_frameworks is absent', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { banner: { sizes: [[300, 250]] } }; + bid.params.frameworks = [1, 2]; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.banner_frameworks).to.deep.equal([1, 2]); + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — refererInfo.stack absent + // ------------------------------------------------------------------------- + describe('buildRequests - refererInfo stack absent', function () { + it('should set rd_stk to undefined when stack is not present in refererInfo', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.refererInfo = { + topmostLocation: 'http://example.com', + reachedTop: true, + numIframes: 0 }; - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'instream' - } + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.ext.appnexus.referrer_detection.rd_stk).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // interpretResponse — renderer options from mediaTypes.video.renderer.options + // ------------------------------------------------------------------------- + if (FEATURES.VIDEO) { + describe('interpretResponse - renderer options from mediaTypes.video.renderer', function () { + it('should use mediaTypes.video.renderer.options when available', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'outstream', playerSize: [640, 480], renderer: { options: { key: 'val' } } } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 3.0, + ext: { + appnexus: { + bid_ad_type: 1, + renderer_url: 'https://cdn.adnxs.com/renderer.js', + renderer_id: 42 + } + } + }] + }] } - }] - } + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].renderer).to.exist; + }); + }); + } // FEATURES.VIDEO + + // ------------------------------------------------------------------------- + // buildRequests — video params.video takes priority over mediaTypes.video for maxduration + // ------------------------------------------------------------------------- + describe('buildRequests - video maxduration skip overwrite', function () { + if (FEATURES.VIDEO) { + it('should not overwrite maxduration set by params.video when mediaTypes.video.maxduration also present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], maxduration: 15 } }; + bid.params.video = { maxduration: 30 }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.maxduration).to.equal(30); + }); + } + }); - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0]).to.have.property('vastImpUrl'); - expect(result[0]).to.have.property('mediaType', 'video'); - }); - - it('handles adpod responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, - } - }, - 'viewability': { - 'config': '' - } - }] - }] - }; + // ------------------------------------------------------------------------- + // buildRequests — video params.video takes priority over mediaTypes.video for skippable + // ------------------------------------------------------------------------- + describe('buildRequests - video skippable skip overwrite', function () { + if (FEATURES.VIDEO) { + it('should not overwrite skippable set by params.video when mediaTypes.video.skip is also present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], skip: 1 } }; + bid.params.video = { skippable: false }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.skippable).to.be.false; + }); + } + }); - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } + // ------------------------------------------------------------------------- + // buildRequests — video params.video takes priority over mediaTypes.video for skipoffset + // ------------------------------------------------------------------------- + describe('buildRequests - video skipoffset skip overwrite', function () { + if (FEATURES.VIDEO) { + it('should not overwrite skipoffset set by params.video when mediaTypes.video.skipafter is also present', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], skipafter: 10 } }; + bid.params.video = { skipoffset: 5 }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.skipoffset).to.equal(5); + }); + } + }); + + // ------------------------------------------------------------------------- + // buildRequests — video playbackmethod type guard + // ------------------------------------------------------------------------- + describe('buildRequests - video playbackmethod', function () { + if (FEATURES.VIDEO) { + it('should not set playback_method when mediaTypes.video.playbackmethod is not an array', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480], playbackmethod: 2 } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].video.playback_method).to.be.undefined; + }); + } + }); + + // ------------------------------------------------------------------------- + // interpretResponse — video nurl without asset_url + // ------------------------------------------------------------------------- + describe('interpretResponse - video nurl without asset_url', function () { + if (FEATURES.VIDEO) { + it('should set vastImpUrl from nurl and not override vastUrl with redir pattern when asset_url is absent', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + const impId = req.data.imp[0].id; + + const serverResponse = { + body: { + seatbid: [{ + bid: [{ + impid: impId, + price: 1.0, + nurl: 'https://notify.example.com/win', + ext: { + appnexus: { + bid_ad_type: 1 + // no asset_url, no renderer_url/renderer_id + } + } + }] + }] } - }] + }; + const bids = spec.interpretResponse(serverResponse, req); + expect(bids[0].vastImpUrl).to.equal('https://notify.example.com/win'); + // ortbConverter sets vastUrl from nurl; our adapter does not override it with a redir pattern + expect(bids[0].vastUrl).to.equal('https://notify.example.com/win'); + }); + } + }); + + // ------------------------------------------------------------------------- + // onBidWon — viewability script reload + // ------------------------------------------------------------------------- + describe('onBidWon - viewability', function () { + it('should not throw when bid has no native property', function () { + expect(() => spec.onBidWon({ cpm: 1.0, adUnitCode: 'test' })).to.not.throw(); + }); + + it('should traverse viewability helpers for a string tracker matching cdn.adnxs.com pattern', function () { + const jsScript = ''; + const bid = { + adId: 'ad-id-1', + adUnitCode: 'adunit-code', + native: { javascriptTrackers: jsScript } }; + // Exercises reloadViewabilityScriptWithCorrectParameters, strIsMediafuseViewabilityScript, + // getMediafuseViewabilityScriptFromJsTrackers, and getViewabilityScriptUrlFromPayload. + expect(() => spec.onBidWon(bid)).to.not.throw(); + }); - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); - }); - - it('handles native responses', function () { - const response1 = deepClone(response); - response1.tags[0].ads[0].ad_type = 'native'; - response1.tags[0].ads[0].rtb.native = { - 'title': 'Native Creative', - 'desc': 'Cool description great stuff', - 'desc2': 'Additional body text', - 'ctatext': 'Do it', - 'sponsored': 'MediaFuse', - 'icon': { - 'width': 0, - 'height': 0, - 'url': 'https://cdn.adnxs.com/icon.png' - }, - 'main_img': { - 'width': 2352, - 'height': 1516, - 'url': 'https://cdn.adnxs.com/img.png' - }, - 'link': { - 'url': 'https://www.mediafuse.com', - 'fallback_url': '', - 'click_trackers': ['https://nym1-ib.adnxs.com/click'] - }, - 'impression_trackers': ['https://example.com'], - 'rating': '5', - 'displayurl': 'https://mediafuse.com/?url=display_url', - 'likes': '38908320', - 'downloads': '874983', - 'price': '9.99', - 'saleprice': 'FREE', - 'phone': '1234567890', - 'address': '28 W 23rd St, New York, NY 10010', - 'privacy_link': 'https://www.mediafuse.com/privacy-policy-agreement/', - 'javascriptTrackers': '' + it('should handle array of trackers and pick the viewability one', function () { + const jsScript = ''; + const bid = { + adId: 'ad-id-2', + adUnitCode: 'adunit-code', + native: { javascriptTrackers: ['', jsScript] } }; - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } + // Exercises the array branch in getMediafuseViewabilityScriptFromJsTrackers. + expect(() => spec.onBidWon(bid)).to.not.throw(); + }); - const result = spec.interpretResponse({ body: response1 }, { bidderRequest }); - expect(result[0].native.title).to.equal('Native Creative'); - expect(result[0].native.body).to.equal('Cool description great stuff'); - expect(result[0].native.cta).to.equal('Do it'); - expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); - }); - - it('supports configuring outstream renderers', function () { - const outstreamResponse = deepClone(response); - outstreamResponse.tags[0].ads[0].rtb.video = {}; - outstreamResponse.tags[0].ads[0].renderer_url = 'renderer.js'; - - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - renderer: { - options: { - adText: 'configured' - } - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }] + it('should not throw when tracker string does not match viewability pattern', function () { + const bid = { + adId: 'ad-id-3', + adUnitCode: 'adunit-code', + native: { javascriptTrackers: '' } }; + expect(() => spec.onBidWon(bid)).to.not.throw(); + }); - const result = spec.interpretResponse({ body: outstreamResponse }, { bidderRequest }); - expect(result[0].renderer.config).to.deep.equal( - bidderRequest.bids[0].renderer.options - ); - }); - - it('should add deal_priority and deal_code', function() { - const responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, + it('should handle cdn.adnxs-simple.com pattern tracker', function () { + const jsScript = ''; + const bid = { + adId: 'ad-id-4', + adUnitCode: 'adunit-code', + native: { javascriptTrackers: jsScript } }; + expect(() => spec.onBidWon(bid)).to.not.throw(); + }); + }); - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - } - const result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); - expect(Object.keys(result[0].mediafuse)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); + // ------------------------------------------------------------------------- + // onBidderError + // ------------------------------------------------------------------------- + describe('onBidderError', function () { + it('should log an error message via utils.logError', function () { + const logSpy = sandbox.spy(utils, 'logError'); + spec.onBidderError({ error: new Error('network timeout'), bidderRequest: deepClone(BASE_BIDDER_REQUEST) }); + expect(logSpy.called).to.be.true; + expect(logSpy.firstCall.args[0]).to.include('Mediafuse Bidder Error'); }); - it('should add advertiser id', function() { - const responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + it('should include the error message in the logged string', function () { + const logSpy = sandbox.spy(utils, 'logError'); + spec.onBidderError({ error: new Error('timeout'), bidderRequest: deepClone(BASE_BIDDER_REQUEST) }); + expect(logSpy.firstCall.args[0]).to.include('timeout'); + }); + }); - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - const result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + // ------------------------------------------------------------------------- + // buildRequests — debug cookie + // ------------------------------------------------------------------------- + describe('buildRequests - debug cookie', function () { + it('should append debug params to URL when valid debug cookie is set', function () { + sandbox.stub(storage, 'getCookie').returns(JSON.stringify({ enabled: true, dongle: 'mfd' })); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.url).to.include('debug=1'); + expect(req.url).to.include('dongle=mfd'); }); - it('should add brand id', function() { - const responseBrandId = deepClone(response); - responseBrandId.tags[0].ads[0].brand_id = 123; + it('should not crash and should skip debug URL when cookie JSON is invalid', function () { + sandbox.stub(storage, 'getCookie').returns('{invalid-json'); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.url).to.not.include('debug=1'); + }); - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - const result = spec.interpretResponse({ body: responseBrandId }, { bidderRequest }); - expect(Object.keys(result[0].meta)).to.include.members(['brandId']); + it('should not append debug params when cookie is absent and no debug URL params', function () { + sandbox.stub(storage, 'getCookie').returns(null); + const [req] = spec.buildRequests([deepClone(BASE_BID)], deepClone(BASE_BIDDER_REQUEST)); + expect(req.url).to.not.include('debug=1'); }); + }); - it('should add advertiserDomains', function() { - const responseAdvertiserId = deepClone(response); - responseAdvertiserId.tags[0].ads[0].adomain = ['123']; + // ------------------------------------------------------------------------- + // buildRequests — addtlConsent (GDPR additional consent string) + // ------------------------------------------------------------------------- + describe('buildRequests - addtlConsent', function () { + it('should parse addtlConsent with ~ separator and set user.ext.addtl_consent', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent-string', + addtlConsent: 'abc~1.2.3' + }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.user.ext.addtl_consent).to.deep.equal([1, 2, 3]); + }); - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code' - }] - } - const result = spec.interpretResponse({ body: responseAdvertiserId }, { bidderRequest }); - expect(Object.keys(result[0].meta)).to.include.members(['advertiserDomains']); - expect(Object.keys(result[0].meta.advertiserDomains)).to.deep.equal([]); + it('should not set addtl_consent when addtlConsent has no ~ separator', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent-string', + addtlConsent: 'abc123' + }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + const addtlConsent = utils.deepAccess(req.data, 'user.ext.addtl_consent'); + expect(addtlConsent).to.be.undefined; + }); + + it('should skip addtl_consent when addtlConsent segment list is empty after parsing', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.gdprConsent = { + gdprApplies: true, + consentString: 'consent-string', + addtlConsent: 'abc~' + }; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + const addtlConsent = utils.deepAccess(req.data, 'user.ext.addtl_consent'); + expect(addtlConsent).to.be.undefined; + }); + }); + + // ------------------------------------------------------------------------- + // buildRequests — refererInfo canonicalUrl branch + // ------------------------------------------------------------------------- + describe('buildRequests - refererInfo canonicalUrl', function () { + it('should include rd_can when canonicalUrl is present in refererInfo', function () { + const bidderRequest = deepClone(BASE_BIDDER_REQUEST); + bidderRequest.refererInfo.canonicalUrl = 'https://canonical.example.com/page'; + const [req] = spec.buildRequests([deepClone(BASE_BID)], bidderRequest); + expect(req.data.ext.appnexus.referrer_detection.rd_can).to.equal('https://canonical.example.com/page'); }); }); + + // ------------------------------------------------------------------------- + // buildRequests — video params.video.frameworks branch + // ------------------------------------------------------------------------- + describe('buildRequests - video params.video.frameworks', function () { + if (FEATURES.VIDEO) { + it('should set video_frameworks from bid.params.video.frameworks', function () { + const bid = deepClone(BASE_BID); + bid.mediaTypes = { video: { context: 'instream', playerSize: [640, 480] } }; + bid.params.video = { frameworks: [1, 2, 6], minduration: 5 }; + const [req] = spec.buildRequests([bid], deepClone(BASE_BIDDER_REQUEST)); + expect(req.data.imp[0].ext.appnexus.video_frameworks).to.deep.equal([1, 2, 6]); + expect(req.data.imp[0].video.minduration).to.equal(5); + }); + } + }); }); From 217004bcd5cb39e321e0af91ec5ecf9d20be77b8 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 12 Mar 2026 07:53:22 -0700 Subject: [PATCH 10/50] Build system: separate `build-release` from `prepare-release` (#14578) * Core: fix error handling when loading debugging-standalone * lint * run error callback once * Separate build-release from prepare-release --- gulpfile.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index cc543ad73ec..fd1f7f476d6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -540,7 +540,10 @@ gulp.task('update-codeql', function (done) { // npm will by default use .gitignore, so create an .npmignore that is a copy of it except it includes "dist" gulp.task('setup-npmignore', execaTask("sed 's/^\\/\\?dist\\/\\?$//g;w .npmignore' .gitignore", {quiet: true})); gulp.task('build', gulp.series(clean, 'build-bundle-prod', setupDist)); -gulp.task('build-release', gulp.series('update-codeql', 'build', updateCreativeExample, 'update-browserslist', 'setup-npmignore')); +// build for release - in addition to 'build', run tasks that update the codebase to be included in a release commit +gulp.task('build-release', gulp.series('update-codeql', 'build', updateCreativeExample, 'update-browserslist')); +// prepare NPM release - 'build' to generate files in dist/; 'setup-npmignore' to make sure 'dist' is published in NPM +gulp.task('prepare-release', gulp.series('build', 'setup-npmignore')) gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, precompile(), gulp.parallel('build-bundle-dev-no-precomp', watch, test))); From 2984a0e5912b7dce6fd2c83ee871827af032794b Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Thu, 12 Mar 2026 07:54:43 -0700 Subject: [PATCH 11/50] adloox analytics: do not disable if not enabled (#14585) --- modules/adlooxAnalyticsAdapter.js | 5 +++-- test/spec/modules/adlooxAnalyticsAdapter_spec.js | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index 444e9ecf3c4..4b1461c79a5 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -172,8 +172,9 @@ analyticsAdapter.enableAnalytics = function(config) { analyticsAdapter.originDisableAnalytics = analyticsAdapter.disableAnalytics; analyticsAdapter.disableAnalytics = function() { analyticsAdapter.context = null; - - analyticsAdapter.originDisableAnalytics(); + if (this.enabled) { + analyticsAdapter.originDisableAnalytics(); + } } analyticsAdapter.url = function(url, args, bid) { diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 30404fc9dc6..692349a31d6 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -40,10 +40,6 @@ describe('Adloox Analytics Adapter', function () { } }; - adapterManager.registerAnalyticsAdapter({ - code: analyticsAdapterName, - adapter: analyticsAdapter - }); describe('enableAnalytics', function () { afterEach(function () { analyticsAdapter.disableAnalytics(); From 1ef2154fbb22d242506e22a302b824e613e5462f Mon Sep 17 00:00:00 2001 From: Robert Ray Martinez III Date: Thu, 12 Mar 2026 08:34:14 -0700 Subject: [PATCH 12/50] Magnite Bid Adapter: New ORTB Adapter for magnite (rubicon) (#14476) * New Magnite Bid Adapter ORTB Converter * Update modules/magniteBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * codex suggestions * move outstream to shared util * fix tests * Update libraries/magniteUtils/outstream.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * revoke blob after ttl * translate adm_native if needed * remove transform bid params * set alwaysHasCapacity * fix lint --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Patrick McCann --- libraries/magniteUtils/outstream.js | 79 ++ modules/magniteBidAdapter.js | 363 +++++++ modules/magniteBidAdapter.md | 85 ++ modules/rubiconBidAdapter.js | 71 +- .../libraries/magniteUtils/outstream_spec.js | 157 +++ test/spec/modules/magniteBidAdapter_spec.js | 924 ++++++++++++++++++ test/spec/modules/rubiconBidAdapter_spec.js | 12 +- 7 files changed, 1616 insertions(+), 75 deletions(-) create mode 100644 libraries/magniteUtils/outstream.js create mode 100644 modules/magniteBidAdapter.js create mode 100644 modules/magniteBidAdapter.md create mode 100644 test/spec/libraries/magniteUtils/outstream_spec.js create mode 100644 test/spec/modules/magniteBidAdapter_spec.js diff --git a/libraries/magniteUtils/outstream.js b/libraries/magniteUtils/outstream.js new file mode 100644 index 00000000000..ab6f16cf89d --- /dev/null +++ b/libraries/magniteUtils/outstream.js @@ -0,0 +1,79 @@ +import { Renderer } from '../../src/Renderer.js'; +import { logWarn } from '../../src/utils.js'; + +export const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.3.7.js'; + +export function bidShouldUsePlayerWidthAndHeight(bidResponse) { + const doesNotHaveDimensions = typeof bidResponse.width !== 'number' || typeof bidResponse.height !== 'number'; + const hasPlayerSize = typeof bidResponse.playerWidth === 'number' && typeof bidResponse.playerHeight === 'number'; + return doesNotHaveDimensions && hasPlayerSize; +} + +export function hideGoogleAdsDiv(adUnit) { + const el = adUnit.querySelector("div[id^='google_ads']"); + if (el) { + el.style.setProperty('display', 'none'); + } +} + +export function hideSmartAdServerIframe(adUnit) { + const el = adUnit.querySelector("script[id^='sas_script']"); + const nextSibling = el && el.nextSibling; + if (nextSibling && nextSibling.localName === 'iframe') { + nextSibling.style.setProperty('display', 'none'); + } +} + +export function renderBid(bid) { + // hide existing ad units + let adUnitElement = document.getElementById(bid.adUnitCode); + if (!adUnitElement) { + logWarn(`Magnite: unable to find ad unit element with id "${bid.adUnitCode}" for rendering.`); + return; + } + + // try to get child element of adunit + const firstChild = adUnitElement.firstElementChild; + if (firstChild?.tagName === 'DIV') { + adUnitElement = firstChild; + } + + hideGoogleAdsDiv(adUnitElement); + hideSmartAdServerIframe(adUnitElement); + + // configure renderer + const config = bid.renderer.getConfig(); + bid.renderer.push(() => { + globalThis.MagniteApex.renderAd({ + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + placement: { + attachTo: adUnitElement, + align: config.align || 'center', + position: config.position || 'prepend' + }, + closeButton: config.closeButton || false, + label: config.label, + replay: config.replay ?? true + }); + }); +} + +export function outstreamRenderer(rtbBid, rendererUrl, rendererConfig) { + const renderer = Renderer.install({ + id: rtbBid.adId, + url: rendererUrl || DEFAULT_RENDERER_URL, + config: rendererConfig || {}, + loaded: false, + adUnitCode: rtbBid.adUnitCode + }); + + try { + renderer.setRender(renderBid); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} diff --git a/modules/magniteBidAdapter.js b/modules/magniteBidAdapter.js new file mode 100644 index 00000000000..7aaa4b76981 --- /dev/null +++ b/modules/magniteBidAdapter.js @@ -0,0 +1,363 @@ +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; +import { + deepSetValue, + formatQS, + mergeDeep, + isPlainObject +} from '../src/utils.js'; +import { + outstreamRenderer, + bidShouldUsePlayerWidthAndHeight +} from '../libraries/magniteUtils/outstream.js'; + +const GVL_ID = 52; +export const REQUEST_URL = 'https://fastlane.rubiconproject.com/a/api/prebid-exchange.json'; +export const SYNC_URL = 'https://eus.rubiconproject.com/usync.html'; +const DEFAULT_INTEGRATION = 'pbjs'; + +let mgniConf = {}; + +// For transition period we need to listen to both rubicon and magnite configs +['magnite', 'rubicon'].forEach(confName => { + // get anything set as of now + mergeDeep(mgniConf, config.getConfig(confName) || {}); + + // listen for future things + config.getConfig(confName, config => { + mergeDeep(mgniConf, config[confName]); + }); +}); +export function resetMgniConf() { + mgniConf = {}; +} + +export const spec = { + code: 'magnite', + gvlid: GVL_ID, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + alwaysHasCapacity: true +}; + +registerBidder(spec); + +/** + * Lets Prebid-Core know if the bid is valid before sending it to the adapter + * @param {object} bid + */ +function isBidRequestValid(bid) { + return ['accountId', 'siteId', 'zoneId'].every(param => !Number.isNaN(Number.parseInt(bid?.params?.[param]))); +} + +const posMap = { + atf: 1, + btf: 3 +}; + +export function masSizeOrdering(sizes) { + const MAS_SIZE_PRIORITY = [ + { w: 300, h: 250 }, + { w: 728, h: 90 }, + { w: 160, h: 600 } + ]; + + const compareSizes = (left, right) => left.w === right.w && left.h === right.h; + + return sizes.sort((first, second) => { + // sort by MAS_SIZE_PRIORITY priority order + const firstPriority = MAS_SIZE_PRIORITY.findIndex(masSize => compareSizes(masSize, first)); + const secondPriority = MAS_SIZE_PRIORITY.findIndex(masSize => compareSizes(masSize, second)); + + // Handle cases where only one size is in the priority list + if (firstPriority !== -1 || secondPriority !== -1) { + if (firstPriority === -1) return 1; + if (secondPriority === -1) return -1; + return firstPriority - secondPriority; + } + + // If neither size is in priority list, maintain original order + return 0; + }); +} + +function getPpuidFromEids(eids) { + for (const eid of eids) { + const ppId = eid.uids.find(uid => uid?.ext?.stype === 'ppuid' && uid?.id); + if (ppId) { + return ppId.id; + } + } +} + +function getPpuid(req) { + const user = req.user; + if (user?.id) { + return user.id; + } + + const userConfigId = config.getConfig('user.id'); + if (userConfigId) { + return userConfigId; + } + + const eids = user?.ext?.eids || []; + return getPpuidFromEids(eids); +} + +function cleanFpd(fpdObj) { + // DV+ wants first party data as object of keys / val where val is array + Object.entries(fpdObj || {}).forEach(([key, val]) => { + // if not array, wrap in array + if (!Array.isArray(val)) { + fpdObj[key] = [val]; + } + }); +} + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + currency: 'USD' + }, + processors: pbsExtensions, + imp(buildImp, bidRequest, context) { + // Building imps of request + const imp = buildImp(bidRequest, context); + + // remove any mediaTypes on imp that are not in our context + [BANNER, NATIVE, VIDEO].forEach(mediaType => { + if (mediaType !== context.mediaType) { + delete imp[mediaType]; + } + }); + + // move params to new location and delete old + imp.ext.prebid.bidder.dvplus = imp.ext.prebid.bidder[bidRequest.bidder]; + delete imp.ext?.prebid?.bidder?.[bidRequest.bidder]; + + // Need to convert bad input for visitor and inventory objects (convert all to array of string / numbers) + ['visitor', 'inventory'].forEach(fpdParam => { + if (isPlainObject(imp.ext.prebid.bidder.dvplus[fpdParam])) { + cleanFpd(imp.ext.prebid.bidder.dvplus[fpdParam]); + } + }); + + // Signal which formats are in the bid request (if more than one) + if (Object.keys(bidRequest.mediaTypes).length > 1) { + deepSetValue(imp, 'ext.rp.rtb.formats', Object.keys(bidRequest.mediaTypes)); + } + + // If params has pos and not already set by ORTB Converter => set it + const mappedPos = posMap[bidRequest.params.position]; + if (mappedPos && typeof imp[context.mediaType].pos !== 'number') { + imp[context.mediaType].pos = mappedPos; + } + + // Sort banner sizes + if (context.mediaType === BANNER && imp[context.mediaType]?.format?.length > 1) { + imp[context.mediaType].format = masSizeOrdering(imp[context.mediaType].format); + } + + // Set secure + imp.secure = 1; + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + + // Do not send in tmax + delete req.tmax; + + // we always deal in USD so just remove + delete req.cur; + + // Set to channel to int_type if avail + deepSetValue(req, 'ext.prebid.channel.name', mgniConf.int_type || DEFAULT_INTEGRATION); + + // we do not need to send addtlConsent to DV+ + delete req?.ext?.ConsentedProvidersSettings?.consented_providers; + + // If user.id is not set try pub conf user.id else undefined + const ppuid = getPpuid(req); + if (ppuid) { + deepSetValue(req, 'user.id', ppuid); + } else { + delete req.user?.id; + } + + // delete buyeruid always + delete req.user?.buyeruid; + + // delete device ifa if falsy + if (!req.device?.ifa) { + delete req.device?.ifa; + } + + // let AE determine dnt from headers not payload + delete req.device?.dnt; + + return req; + }, + bidResponse(buildBidResponse, bid, context) { + // Move adm_native to adm for native responses so the ortbConverter can process it + if (context.mediaType === NATIVE && bid.adm_native) { + bid.adm = bid.adm_native; + delete bid.adm_native; + } + + const bidResponse = buildBidResponse(bid, context); + + bidResponse.bidderCode = context.bidRequest.bidder; + + // If it is a video response up the ttl + if (bidResponse.mediaType === VIDEO) { + bidResponse.ttl = 900; + } + + // Attach renderer and w and h if outstream + if (bidResponse.mediaType === VIDEO && context.bidRequest?.mediaTypes?.video?.context === 'outstream') { + bidResponse.renderer = outstreamRenderer(bidResponse, mgniConf.rendererUrl, mgniConf.rendererConfig); + // generate local vastUrl using createObjectURL + bidResponse.vastUrl = URL.createObjectURL(new Blob([bidResponse.vastXml], { type: 'text/xml' })); + setTimeout(() => { + URL.revokeObjectURL(bidResponse.vastUrl); + }, (bidResponse.ttl + 60) * 1000); + } + + // If its video and the width and height are not set + if (bidResponse.mediaType === VIDEO && bidShouldUsePlayerWidthAndHeight(bidResponse)) { + bidResponse.width = bidResponse.playerWidth; + bidResponse.height = bidResponse.playerHeight; + } + + return bidResponse; + }, + response(buildResponse, bidResponses, ortbResponse, context) { + const response = buildResponse(bidResponses, ortbResponse, context); + return response; + }, + overrides: { + imp: { + bidfloor(setBidFloor, imp, bidRequest, context) { + // Floors should always be in USD + const floor = {}; + setBidFloor(floor, bidRequest, { ...context, currency: 'USD' }); + if (floor.bidfloorcur === 'USD') { + Object.assign(imp, floor); + } + }, + } + } +}); + +function shouldAddBid(bid, mediaType) { + const enabledTypes = bid.params?.enabledMediaTypes; + return !Array.isArray(enabledTypes) || enabledTypes.includes(mediaType); +} + +/** + * We must split our bids into different requests with the following criteria + * - Each accountId - siteId - mediaType combo gets own request + * - Max of 10 imps per request + * @param bids + * @param bidderRequest + * @returns Array of HTTP Request Objects + */ +function buildRequests(bids, bidderRequest) { + const bidsMap = {}; + + // Loop through all bids and group them by accountId, siteId, and mediaType + for (const bid of bids) { + const { accountId, siteId } = bid.params; + for (const mediaType of Object.keys(bid.mediaTypes)) { + if (shouldAddBid(bid, mediaType)) { + const key = `${accountId}|${siteId}|${mediaType}`; + if (!bidsMap[key]) { + bidsMap[key] = []; + } + bidsMap[key].push(bid); + } + } + } + + const impLimit = mgniConf.impLimit ?? 10; + const requests = []; + + // Loop through the grouped bids and create requests + // We need to split the bids into chunks of impLimit + // and create a request for each chunk + for (const [key, groupBids] of Object.entries(bidsMap)) { + const [accountId, siteId, mediaType] = key.split('|'); + + for (let i = 0; i < groupBids.length; i += impLimit) { + const chunk = groupBids.slice(i, i + impLimit); + requests.push(createRequest(chunk, bidderRequest, `${accountId}-${siteId}`, mediaType)); + } + } + + return requests; +} + +function createRequest(bidRequests, bidderRequest, acctSite, mediaType) { + return { + method: 'POST', + url: `${(mgniConf.bidEndpoint || REQUEST_URL)}?as=${acctSite}&m=${mediaType}&s=${bidRequests.length}`, + data: converter.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + } +} + +function interpretResponse(resp, req) { + if (!resp.body) { + resp.body = { nbr: 0 }; + } + return converter.fromORTB({ request: req.data, response: resp.body })?.bids; +} + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @param gppConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (!syncOptions.iframeEnabled) { + return; + } + + const params = {}; + + if (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean') { + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + if (gdprConsent && 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(); + } + + const queryString = Object.keys(params).length ? `?${formatQS(params)}` : ''; + const syncEndpoint = mgniConf.syncEndpoint || SYNC_URL; + + return { + type: 'iframe', + url: syncEndpoint + queryString + }; +} diff --git a/modules/magniteBidAdapter.md b/modules/magniteBidAdapter.md new file mode 100644 index 00000000000..88b5cf39551 --- /dev/null +++ b/modules/magniteBidAdapter.md @@ -0,0 +1,85 @@ +# Overview + +``` +Module Name: Magnite Bid Adapter +Module Type: Bidder Adapter +Maintainer: header-bidding@magnite.com +``` + +# Description + +Connect to Magnite's exchange for bids via a full OpenRTB integration. + +The Magnite adapter requires setup and approval from the +Magnite team. Please reach out to your account team or +header-bidding@magnite.com for more information. + +# Bid Parameters + +| Name | Scope | Type | Description | Example | +| ---- | ----- | ---- | ----------- | ------- | +| `accountId` | required | Number | Magnite account ID. | `14062` | +| `siteId` | required | Number | Magnite site ID. | `70608` | +| `zoneId` | required | Number | Magnite zone ID. | `498816` | + +# Test Parameters + +``` +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: "magnite", + params: { + accountId: 14062, + siteId: 70608, + zoneId: 498816 + } + } + ] + } +]; + +var videoAdUnit = { + code: 'myVideoAdUnit', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-ms-wmv'], + protocols: [2, 5], + maxduration: 30, + linearity: 1, + api: [2] + } + }, + bids: [ + { + bidder: 'magnite', + params: { + accountId: 14062, + siteId: 70608, + zoneId: 498816 + } + } + ] +}; +``` + +# Configuration + +Add the following code to enable user syncing. By default, Prebid.js turns off user syncing through iframes. Magnite recommends enabling iframe-based user syncing to improve match rates and bid performance. + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true + } +}); +``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 737295172a9..699f4654c1f 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -4,7 +4,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import { Renderer } from '../src/Renderer.js'; import { deepAccess, deepSetValue, @@ -23,6 +22,7 @@ import { } from '../src/utils.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js'; +import { outstreamRenderer } from '../libraries/magniteUtils/outstream.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -30,8 +30,6 @@ import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js'; const DEFAULT_INTEGRATION = 'pbjs_lite'; const DEFAULT_PBS_INTEGRATION = 'pbjs'; -const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.3.7.js'; -// renderer code at https://github.com/rubicon-project/apex2 let rubiConf = config.getConfig('rubicon') || {}; // we are saving these as global to this module so that if a pub accidentally overwrites the entire @@ -241,7 +239,7 @@ export const converter = ortbConverter({ bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight || 0; if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { - bidResponse.renderer = outstreamRenderer(bidResponse); + bidResponse.renderer = outstreamRenderer(bidResponse, rubiConf.rendererUrl, rubiConf.rendererConfig); } if (deepAccess(bid, 'ext.bidder.rp.advid')) { @@ -810,71 +808,6 @@ function _renderCreative(script, impId) { `; } -function hideGoogleAdsDiv(adUnit) { - const el = adUnit.querySelector("div[id^='google_ads']"); - if (el) { - el.style.setProperty('display', 'none'); - } -} - -function hideSmartAdServerIframe(adUnit) { - const el = adUnit.querySelector("script[id^='sas_script']"); - const nextSibling = el && el.nextSibling; - if (nextSibling && nextSibling.localName === 'iframe') { - nextSibling.style.setProperty('display', 'none'); - } -} - -function renderBid(bid) { - // hide existing ad units - const adUnitElement = document.getElementById(bid.adUnitCode); - hideGoogleAdsDiv(adUnitElement); - hideSmartAdServerIframe(adUnitElement); - - // configure renderer - 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, - height: bid.height, - vastUrl: bid.vastUrl, - placement: { - attachTo: `#${bid.adUnitCode}`, - align: config.align, - position: config.position - }, - closeButton: config.closeButton, - label: config.label, - collapse: config.collapse - }); - }); -} - -function outstreamRenderer(rtbBid) { - const renderer = Renderer.install({ - id: rtbBid.adId, - url: rubiConf.rendererUrl || DEFAULT_RENDERER_URL, - config: rubiConf.rendererConfig || {}, - loaded: false, - adUnitCode: rtbBid.adUnitCode - }); - - try { - renderer.setRender(renderBid); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - - return renderer; -} - function parseSizes(bid, mediaType) { const params = bid.params; if (mediaType === VIDEO) { diff --git a/test/spec/libraries/magniteUtils/outstream_spec.js b/test/spec/libraries/magniteUtils/outstream_spec.js new file mode 100644 index 00000000000..83752e27a75 --- /dev/null +++ b/test/spec/libraries/magniteUtils/outstream_spec.js @@ -0,0 +1,157 @@ +import { + outstreamRenderer, + renderBid, + bidShouldUsePlayerWidthAndHeight, + DEFAULT_RENDERER_URL +} from 'libraries/magniteUtils/outstream.js'; +import { Renderer } from 'src/Renderer.js'; +import * as utils from 'src/utils.js'; + +describe('Magnite Utils Outstream', function () { + let logWarnStub; + + beforeEach(function () { + logWarnStub = sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + logWarnStub.restore(); + }); + + describe('bidShouldUsePlayerWidthAndHeight', function () { + it('should return true if bid has no dimensions but has player size', function () { + const bid = { + playerWidth: 640, + playerHeight: 480 + }; + expect(bidShouldUsePlayerWidthAndHeight(bid)).to.be.true; + }); + + it('should return false if bid has dimensions', function () { + const bid = { + width: 300, + height: 250, + playerWidth: 640, + playerHeight: 480 + }; + expect(bidShouldUsePlayerWidthAndHeight(bid)).to.be.false; + }); + + it('should return false if bid has no player size', function () { + const bid = { + width: undefined, + height: undefined + }; + expect(bidShouldUsePlayerWidthAndHeight(bid)).to.be.false; + }); + }); + + describe('outstreamRenderer', function () { + let rendererInstallStub; + let rendererSetRenderSpy; + + beforeEach(function () { + rendererSetRenderSpy = sinon.spy(); + rendererInstallStub = sinon.stub(Renderer, 'install').returns({ + setRender: rendererSetRenderSpy + }); + }); + + afterEach(function () { + rendererInstallStub.restore(); + }); + + it('should install renderer with default URL if not provided', function () { + const bid = { + adId: 'test-ad-id', + adUnitCode: 'test-ad-unit' + }; + outstreamRenderer(bid); + expect(rendererInstallStub.calledOnce).to.be.true; + expect(rendererInstallStub.firstCall.args[0].url).to.equal(DEFAULT_RENDERER_URL); + }); + + it('should install renderer with provided URL', function () { + const bid = { + adId: 'test-ad-id', + adUnitCode: 'test-ad-unit' + }; + const customUrl = 'https://custom.url/renderer.js'; + outstreamRenderer(bid, customUrl); + expect(rendererInstallStub.firstCall.args[0].url).to.equal(customUrl); + }); + + it('should set render function', function () { + const bid = { + adId: 'test-ad-id', + adUnitCode: 'test-ad-unit' + }; + outstreamRenderer(bid); + expect(rendererSetRenderSpy.calledOnce).to.be.true; + expect(rendererSetRenderSpy.firstCall.args[0]).to.equal(renderBid); + }); + }); + + describe('renderBid', function () { + let adUnitElement; + let bid; + let rendererPushSpy; + + beforeEach(function () { + adUnitElement = document.createElement('div'); + adUnitElement.id = 'test-ad-unit'; + document.body.appendChild(adUnitElement); + + rendererPushSpy = sinon.spy(); + bid = { + adUnitCode: 'test-ad-unit', + width: 640, + height: 480, + vastUrl: 'https://vast.url', + renderer: { + getConfig: () => ({}), + push: rendererPushSpy + } + }; + + globalThis.MagniteApex = { + renderAd: sinon.spy() + }; + }); + + afterEach(function () { + document.body.removeChild(adUnitElement); + delete globalThis.MagniteApex; + }); + + it('should log warning if ad unit element not found', function () { + bid.adUnitCode = 'missing-ad-unit'; + renderBid(bid); + expect(logWarnStub.calledWithMatch('Magnite: unable to find ad unit element')).to.be.true; + }); + + it('should push render function to renderer', function () { + renderBid(bid); + expect(rendererPushSpy.calledOnce).to.be.true; + const callback = rendererPushSpy.firstCall.args[0]; + callback(); + expect(globalThis.MagniteApex.renderAd.calledOnce).to.be.true; + const args = globalThis.MagniteApex.renderAd.firstCall.args[0]; + expect(args.width).to.equal(640); + expect(args.height).to.equal(480); + expect(args.vastUrl).to.equal('https://vast.url'); + expect(args.placement.attachTo).to.equal(adUnitElement); + }); + + it('should hide google ads div', function () { + const wrapper = document.createElement('div'); + adUnitElement.appendChild(wrapper); + const googleAdsDiv = document.createElement('div'); + googleAdsDiv.id = 'google_ads_iframe'; + wrapper.appendChild(googleAdsDiv); + + renderBid(bid); + expect(googleAdsDiv.style.display).to.equal('none'); + }); + }); +}); diff --git a/test/spec/modules/magniteBidAdapter_spec.js b/test/spec/modules/magniteBidAdapter_spec.js new file mode 100644 index 00000000000..e727690e891 --- /dev/null +++ b/test/spec/modules/magniteBidAdapter_spec.js @@ -0,0 +1,924 @@ +import { expect } from 'chai'; +import { + spec, + REQUEST_URL, + SYNC_URL, + masSizeOrdering, + resetMgniConf +} from 'modules/magniteBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; +import { config } from 'src/config.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/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; + +import { hook } from '../../../src/hook.js'; + +function getBannerBidRequest(overrides = {}) { + return { + bidder: 'magnite', + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + ...(overrides.params) + }, + adUnitCode: 'adunit-banner', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, + sizes: [[300, 250], [728, 90]], + bidId: 'bid-banner-1', + bidderRequestId: 'bidder-req-1', + auctionId: 'auction-1', + transactionId: 'trans-1', + ortb2Imp: { + ext: { + tid: 'trans-1' + } + }, + ...overrides + }; +} + +function getVideoBidRequest(overrides = {}) { + return { + bidder: 'magnite', + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + ...(overrides.params) + }, + adUnitCode: 'adunit-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2], + linearity: 1, + ...(overrides.videoMediaType) + } + }, + sizes: [[640, 480]], + bidId: 'bid-video-1', + bidderRequestId: 'bidder-req-1', + auctionId: 'auction-1', + transactionId: 'trans-2', + ortb2Imp: { + ext: { + tid: 'trans-2' + } + }, + ...overrides + }; +} + +function getBidderRequest(overrides = {}) { + return { + bidderCode: 'magnite', + auctionId: 'auction-1', + bidderRequestId: 'bidder-req-1', + timeout: 3000, + refererInfo: { + page: 'https://example.com/test', + reachedTop: true + }, + ortb2: { + source: { + tid: 'auction-1' + } + }, + ...overrides + }; +} + +function getSampleOrtbBidResponse(impid, overrides = {}) { + return { + id: 'response-1', + seatbid: [{ + bid: [{ + impid: impid || 'bid-banner-1', + price: 3.5, + w: 300, + h: 250, + crid: 'creative-123', + dealid: 'deal-456', + adm: '
test ad
', + mtype: 1, + adomain: ['advertiser.com'], + ...overrides + }], + seat: 'magnite' + }], + cur: 'USD' + }; +} + +describe('the magnite adapter', function () { + before(() => { + hook.ready(); + }); + + const adapter = newBidder(spec); + + afterEach(function () { + config.resetConfig(); + resetMgniConf(); + }); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('spec properties', function () { + it('should have the correct bidder code', function () { + expect(spec.code).to.equal('magnite'); + }); + + it('should have the correct GVL ID', function () { + expect(spec.gvlid).to.equal(52); + }); + + it('should support banner, native, and video media types', function () { + expect(spec.supportedMediaTypes).to.deep.equal([BANNER, NATIVE, VIDEO]); + }); + + it('should have alwaysHasCapacity set to true', function () { + expect(spec.alwaysHasCapacity).to.be.true; + }); + }); + + describe('isBidRequestValid()', function () { + it('should return true when all required params are present', function () { + const bid = getBannerBidRequest(); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return true when params are passed as strings that parse to numbers', function () { + const bid = getBannerBidRequest({ + params: { accountId: '1001', siteId: '2001', zoneId: '3001' } + }); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + + it('should return false when accountId is missing', function () { + const bid = getBannerBidRequest(); + delete bid.params.accountId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when siteId is missing', function () { + const bid = getBannerBidRequest(); + delete bid.params.siteId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when zoneId is missing', function () { + const bid = getBannerBidRequest(); + delete bid.params.zoneId; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when accountId is not a number', function () { + const bid = getBannerBidRequest({ + params: { accountId: 'abc', siteId: 2001, zoneId: 3001 } + }); + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is undefined', function () { + const bid = getBannerBidRequest(); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return false when params is empty', function () { + const bid = getBannerBidRequest(); + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + it('should return true for video bid requests with valid params', function () { + const bid = getVideoBidRequest(); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); + + describe('buildRequests()', function () { + let bidderRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + }); + + it('should return an array of requests', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests).to.be.an('array'); + expect(requests.length).to.be.greaterThan(0); + }); + + it('should use POST method', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + requests.forEach(req => { + expect(req.method).to.equal('POST'); + }); + }); + + it('should use the default REQUEST_URL', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + requests.forEach(req => { + expect(req.url).to.include(REQUEST_URL); + }); + }); + + it('should include accountId-siteId in the query string as "as" param', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests[0].url).to.include('as=1001-2001'); + }); + + it('should include mediaType in the query string as "m" param', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests[0].url).to.include('m=banner'); + }); + + it('should include the number of imps in the query string as "s" param', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests[0].url).to.include('s=1'); + }); + + it('should use a custom bid endpoint from config', function () { + config.setConfig({ magnite: { bidEndpoint: 'https://custom.endpoint.com/bid' } }); + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].url).to.include('https://custom.endpoint.com/bid'); + }); + + describe('request grouping', function () { + it('should group bids by accountId-siteId-mediaType', function () { + const bid1 = getBannerBidRequest({ bidId: 'bid-1' }); + const bid2 = getBannerBidRequest({ + bidId: 'bid-2', + adUnitCode: 'adunit-banner-2', + params: { accountId: 1001, siteId: 2002, zoneId: 3002 } + }); + const requests = spec.buildRequests([bid1, bid2], bidderRequest); + // Different siteIds => separate requests + expect(requests.length).to.equal(2); + }); + + it('should combine bids with same accountId-siteId-mediaType into one request', function () { + const bid1 = getBannerBidRequest({ bidId: 'bid-1', adUnitCode: 'adunit-1' }); + const bid2 = getBannerBidRequest({ bidId: 'bid-2', adUnitCode: 'adunit-2' }); + const requests = spec.buildRequests([bid1, bid2], bidderRequest); + // Same accountId, siteId, mediaType => one request + expect(requests.length).to.equal(1); + expect(requests[0].url).to.include('s=2'); + }); + + it('should split multi-format bids into separate requests by mediaType', function () { + const multiformatBid = getBannerBidRequest({ + bidId: 'bid-multi', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2], + linearity: 1 + } + } + }); + const requests = spec.buildRequests([multiformatBid], bidderRequest); + expect(requests.length).to.equal(2); + + const mediaTypes = requests.map(r => { + const url = new URL(r.url); + return url.searchParams.get('m'); + }); + expect(mediaTypes).to.include('banner'); + expect(mediaTypes).to.include('video'); + }); + + it('should correctly handle mediaType with hyphen', function () { + const bid = getBannerBidRequest({ + bidId: 'bid-hyphen', + mediaTypes: { + 'video-outstream': {} + }, + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + enabledMediaTypes: ['video-outstream'] + } + }); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests.length).to.equal(1); + const url = new URL(requests[0].url); + expect(url.searchParams.get('m')).to.equal('video-outstream'); + expect(url.searchParams.get('as')).to.equal('1001-2001'); + }); + }); + + describe('chunking', function () { + it('should chunk requests at the default impLimit of 10', function () { + const bids = []; + for (let i = 0; i < 12; i++) { + bids.push(getBannerBidRequest({ + bidId: `bid-${i}`, + adUnitCode: `adunit-${i}` + })); + } + const requests = spec.buildRequests(bids, bidderRequest); + expect(requests.length).to.equal(2); + expect(requests[0].url).to.include('s=10'); + expect(requests[1].url).to.include('s=2'); + }); + + it('should respect custom impLimit from config', function () { + config.setConfig({ magnite: { impLimit: 3 } }); + const bids = []; + for (let i = 0; i < 7; i++) { + bids.push(getBannerBidRequest({ + bidId: `bid-${i}`, + adUnitCode: `adunit-${i}` + })); + } + const requests = spec.buildRequests(bids, bidderRequest); + expect(requests.length).to.equal(3); + expect(requests[0].url).to.include('s=3'); + expect(requests[1].url).to.include('s=3'); + expect(requests[2].url).to.include('s=1'); + }); + }); + + describe('enabledMediaTypes filtering', function () { + it('should filter out mediaTypes not in enabledMediaTypes', function () { + const bid = getBannerBidRequest({ + bidId: 'bid-filter', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2], + linearity: 1 + } + }, + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + enabledMediaTypes: ['banner'] + } + }); + const requests = spec.buildRequests([bid], bidderRequest); + // Only banner should be sent, video filtered out + expect(requests.length).to.equal(1); + const url = new URL(requests[0].url); + expect(url.searchParams.get('m')).to.equal('banner'); + }); + }); + + describe('ORTB request data', function () { + it('should produce valid ORTB data with imp array', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + const data = requests[0].data; + expect(data).to.be.an('object'); + expect(data.imp).to.be.an('array'); + expect(data.imp.length).to.equal(1); + }); + + it('should set imp.secure to 1', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.imp[0].secure).to.equal(1); + }); + + it('should not include tmax in the request', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.tmax).to.be.undefined; + }); + + it('should not include cur in the request', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.cur).to.be.undefined; + }); + + it('should set default channel name to pbjs', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.ext.prebid.channel.name).to.equal('pbjs'); + }); + + it('should use int_type from config for channel name', function () { + config.setConfig({ magnite: { int_type: 'custom_integration' } }); + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.ext.prebid.channel.name).to.equal('custom_integration'); + }); + + it('should move bidder params to dvplus namespace', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + const imp = requests[0].data.imp[0]; + expect(imp.ext.prebid.bidder.dvplus).to.be.an('object'); + expect(imp.ext.prebid.bidder.magnite).to.be.undefined; + }); + + it('should include accountId, siteId, zoneId in dvplus params', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + const dvplus = requests[0].data.imp[0].ext.prebid.bidder.dvplus; + expect(dvplus.accountId).to.equal(1001); + expect(dvplus.siteId).to.equal(2001); + expect(dvplus.zoneId).to.equal(3001); + }); + + it('should remove non-context mediaTypes from imp', function () { + const bid = getBannerBidRequest(); + const requests = spec.buildRequests([bid], bidderRequest); + const imp = requests[0].data.imp[0]; + // This is a banner context request + expect(imp.banner).to.exist; + expect(imp.video).to.be.undefined; + expect(imp.native).to.be.undefined; + }); + + it('should set pos from params.position when not already set', function () { + const bid = getBannerBidRequest({ + params: { accountId: 1001, siteId: 2001, zoneId: 3001, position: 'atf' } + }); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests[0].data.imp[0].banner.pos).to.equal(1); + }); + + it('should set pos to 3 for btf position', function () { + const bid = getBannerBidRequest({ + params: { accountId: 1001, siteId: 2001, zoneId: 3001, position: 'btf' } + }); + const requests = spec.buildRequests([bid], bidderRequest); + expect(requests[0].data.imp[0].banner.pos).to.equal(3); + }); + + it('should signal multi-format in ext.rp.rtb.formats when bid has multiple mediaTypes', function () { + const bid = getBannerBidRequest({ + bidId: 'bid-multi', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2], + linearity: 1 + } + } + }); + const requests = spec.buildRequests([bid], bidderRequest); + // The banner request should have the formats signal + const bannerReq = requests.find(r => r.url.includes('m=banner')); + expect(bannerReq.data.imp[0].ext.rp.rtb.formats).to.include('banner'); + expect(bannerReq.data.imp[0].ext.rp.rtb.formats).to.include('video'); + }); + + it('should not delete device.dnt from request', function () { + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + // dnt should be deleted by the adapter + expect(requests[0].data.device?.dnt).to.be.undefined; + }); + }); + + describe('visitor and inventory FPD cleaning', function () { + it('should wrap non-array visitor values in arrays', function () { + const bid = getBannerBidRequest({ + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + visitor: { age: '25', interests: ['sports', 'tech'] } + } + }); + const requests = spec.buildRequests([bid], bidderRequest); + const dvplus = requests[0].data.imp[0].ext.prebid.bidder.dvplus; + // Non-array value 'age' should be wrapped + expect(dvplus.visitor.age).to.deep.equal(['25']); + // Already-array value should remain unchanged + expect(dvplus.visitor.interests).to.deep.equal(['sports', 'tech']); + }); + + it('should wrap non-array inventory values in arrays', function () { + const bid = getBannerBidRequest({ + params: { + accountId: 1001, + siteId: 2001, + zoneId: 3001, + inventory: { rating: '5-star', prodtype: ['tech', 'mobile'] } + } + }); + const requests = spec.buildRequests([bid], bidderRequest); + const dvplus = requests[0].data.imp[0].ext.prebid.bidder.dvplus; + expect(dvplus.inventory.rating).to.deep.equal(['5-star']); + expect(dvplus.inventory.prodtype).to.deep.equal(['tech', 'mobile']); + }); + }); + + describe('ppuid handling', function () { + it('should set user.id from config user.id when ortb user.id is not set', function () { + config.setConfig({ 'user': { id: 'config-ppuid-123' } }); + const requests = spec.buildRequests([getBannerBidRequest()], bidderRequest); + expect(requests[0].data.user.id).to.equal('config-ppuid-123'); + }); + }); + }); + + describe('interpretResponse()', function () { + let bidderRequest; + + beforeEach(function () { + bidderRequest = getBidderRequest(); + }); + + it('should return an empty array when response body is missing', function () { + const bidRequest = getBannerBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const bids = spec.interpretResponse({}, requests[0]); + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(0); + }); + + it('should return an empty array when response body has no seatbid', function () { + const bidRequest = getBannerBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const bids = spec.interpretResponse({ body: { nbr: 0 } }, requests[0]); + expect(bids).to.be.an('array'); + expect(bids.length).to.equal(0); + }); + + context('when there is a valid banner response', function () { + let bids; + + beforeEach(function () { + const bidRequest = getBannerBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const response = getSampleOrtbBidResponse(bidRequest.bidId); + bids = spec.interpretResponse({ body: response }, requests[0]); + }); + + it('should return at least one bid', function () { + expect(bids).to.be.an('array'); + expect(bids.length).to.be.greaterThan(0); + }); + + it('should return a valid cpm', function () { + expect(bids[0].cpm).to.equal(3.5); + }); + + it('should return the correct creativeId', function () { + expect(bids[0].creativeId).to.equal('creative-123'); + }); + + it('should return the correct width and height', function () { + expect(bids[0].width).to.equal(300); + expect(bids[0].height).to.equal(250); + }); + + it('should return net revenue as true', function () { + expect(bids[0].netRevenue).to.be.true; + }); + + it('should return USD currency', function () { + expect(bids[0].currency).to.equal('USD'); + }); + + it('should return a TTL of 300 for banner', function () { + expect(bids[0].ttl).to.equal(300); + }); + }); + + context('when there is a valid video response', function () { + let bids; + + beforeEach(function () { + const bidRequest = getVideoBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const response = getSampleOrtbBidResponse(bidRequest.bidId, { + mtype: 2, + adm: '', + w: 640, + h: 480 + }); + bids = spec.interpretResponse({ body: response }, requests[0]); + }); + + it('should return at least one bid', function () { + expect(bids).to.be.an('array'); + expect(bids.length).to.be.greaterThan(0); + }); + + it('should return a TTL of 900 for video', function () { + expect(bids[0].ttl).to.equal(900); + }); + }); + + describe('outstream video', function () { + let sandbox; + let clock; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(globalThis.URL, 'createObjectURL').returns('blob:test-url'); + sandbox.stub(globalThis.URL, 'revokeObjectURL'); + clock = sandbox.useFakeTimers(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should create a blob URL and revoke it after TTL + 60s', function () { + const bidRequest = getVideoBidRequest({ + mediaTypes: { + video: { + context: 'outstream', + playerSize: [640, 480] + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const response = getSampleOrtbBidResponse(bidRequest.bidId, { + mtype: 2, + adm: '', + w: 640, + h: 480 + }); + + const bids = spec.interpretResponse({ body: response }, requests[0]); + const bid = bids[0]; + + expect(bid.vastUrl).to.equal('blob:test-url'); + expect(globalThis.URL.createObjectURL.calledOnce).to.be.true; + + // TTL is 900 for video + const ttl = 900; + const delay = (ttl + 60) * 1000; + + clock.tick(delay); + expect(globalThis.URL.revokeObjectURL.calledWith('blob:test-url')).to.be.true; + }); + }); + + describe('adm_native handling', function () { + it('should move adm_native to adm when mediaType is native and adm_native is present', function () { + const bidRequest = getBannerBidRequest({ + bidId: 'bid-native-1', + adUnitCode: 'adunit-native', + mediaTypes: { + native: { + ortb: { + assets: [{ id: 0, required: 1, title: { len: 90 } }] + } + } + } + }); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const nativeMarkup = '{"ver":"1.1","assets":[{"id":0,"title":{"text":"test"}}]}'; + const response = getSampleOrtbBidResponse('bid-native-1', { + adm_native: nativeMarkup, + adm: undefined, + mtype: 4 + }); + const bids = spec.interpretResponse({ body: response }, requests[0]); + expect(bids.length).to.be.greaterThan(0); + expect(bids[0].mediaType).to.equal('native'); + }); + + it('should not move adm_native to adm when mediaType is not native', function () { + const bidRequest = getBannerBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const response = getSampleOrtbBidResponse(bidRequest.bidId, { + adm_native: '{"native":{"ver":"1.1","assets":[]}}', + adm: '
test ad
' + }); + const bids = spec.interpretResponse({ body: response }, requests[0]); + expect(bids.length).to.be.greaterThan(0); + // adm should remain unchanged since mediaType is banner, not native + expect(bids[0].ad).to.equal('
test ad
'); + }); + + it('should not modify adm when adm_native is not present', function () { + const bidRequest = getBannerBidRequest(); + const requests = spec.buildRequests([bidRequest], bidderRequest); + const response = getSampleOrtbBidResponse(bidRequest.bidId); + const bids = spec.interpretResponse({ body: response }, requests[0]); + expect(bids.length).to.be.greaterThan(0); + expect(bids[0].ad).to.equal('
test ad
'); + }); + }); + }); + + describe('getUserSyncs()', function () { + it('should return undefined when iframe is not enabled', function () { + const result = spec.getUserSyncs({ iframeEnabled: false }, []); + expect(result).to.be.undefined; + }); + + it('should return a sync object when iframe is enabled', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(result).to.be.an('object'); + expect(result.type).to.equal('iframe'); + expect(result.url).to.include(SYNC_URL); + }); + + it('should use the default SYNC_URL', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(result.url).to.equal(SYNC_URL); + }); + + it('should use a custom syncEndpoint from config', function () { + config.setConfig({ magnite: { syncEndpoint: 'https://custom.sync.com/usync.html' } }); + const result = spec.getUserSyncs({ iframeEnabled: true }, []); + expect(result.url).to.include('https://custom.sync.com/usync.html'); + }); + + describe('GDPR consent', function () { + it('should add gdpr and gdpr_consent params when gdprConsent is present', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 'GDPR_CONSENT_STRING' + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + expect(result.url).to.include('gdpr=1'); + expect(result.url).to.include('gdpr_consent=GDPR_CONSENT_STRING'); + }); + + it('should set gdpr=0 when gdprApplies is false', function () { + const gdprConsent = { + gdprApplies: false, + consentString: 'GDPR_CONSENT_STRING' + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + expect(result.url).to.include('gdpr=0'); + }); + + it('should not add gdpr param when gdprApplies is not a boolean', function () { + const gdprConsent = { + gdprApplies: 'maybe', + consentString: 'GDPR_CONSENT_STRING' + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + expect(result.url).to.not.include('gdpr='); + }); + + it('should not add gdpr_consent when consentString is not a string', function () { + const gdprConsent = { + gdprApplies: true, + consentString: 123 + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent); + expect(result.url).to.include('gdpr=1'); + expect(result.url).to.not.include('gdpr_consent='); + }); + }); + + describe('USP consent', function () { + it('should add us_privacy param when uspConsent is present', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, '1YNN'); + expect(result.url).to.include('us_privacy=1YNN'); + }); + + it('should not add us_privacy param when uspConsent is undefined', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined); + expect(result.url).to.not.include('us_privacy'); + }); + }); + + describe('GPP consent', function () { + it('should add gpp and gpp_sid params when gppConsent is present', function () { + const gppConsent = { + gppString: 'GPP_STRING', + applicableSections: [6, 7] + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined, gppConsent); + expect(result.url).to.include('gpp=GPP_STRING'); + expect(result.url).to.include('gpp_sid=6,7'); + }); + + it('should not add gpp params when gppConsent is undefined', function () { + const result = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined, undefined); + expect(result.url).to.not.include('gpp='); + }); + + it('should not add gpp params when gppString is missing', function () { + const gppConsent = { + applicableSections: [6, 7] + }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], undefined, undefined, gppConsent); + expect(result.url).to.not.include('gpp='); + }); + }); + + describe('combined consent params', function () { + it('should include all consent params when all are present', function () { + const gdprConsent = { gdprApplies: true, consentString: 'GDPR_STRING' }; + const gppConsent = { gppString: 'GPP_STRING', applicableSections: [6] }; + const result = spec.getUserSyncs({ iframeEnabled: true }, [], gdprConsent, '1YNN', gppConsent); + expect(result.url).to.include('gdpr=1'); + expect(result.url).to.include('gdpr_consent=GDPR_STRING'); + expect(result.url).to.include('us_privacy=1YNN'); + expect(result.url).to.include('gpp=GPP_STRING'); + expect(result.url).to.include('gpp_sid=6'); + }); + }); + }); + + describe('masSizeOrdering()', function () { + it('should sort 300x250 to the first position', function () { + const sizes = [ + { w: 728, h: 90 }, + { w: 300, h: 250 }, + { w: 160, h: 600 } + ]; + const sorted = masSizeOrdering(sizes); + expect(sorted[0]).to.deep.equal({ w: 300, h: 250 }); + }); + + it('should order priority sizes as 300x250, 728x90, 160x600', function () { + const sizes = [ + { w: 160, h: 600 }, + { w: 728, h: 90 }, + { w: 300, h: 250 } + ]; + const sorted = masSizeOrdering(sizes); + expect(sorted[0]).to.deep.equal({ w: 300, h: 250 }); + expect(sorted[1]).to.deep.equal({ w: 728, h: 90 }); + expect(sorted[2]).to.deep.equal({ w: 160, h: 600 }); + }); + + it('should place priority sizes before non-priority sizes', function () { + const sizes = [ + { w: 320, h: 50 }, + { w: 300, h: 250 }, + { w: 970, h: 250 } + ]; + const sorted = masSizeOrdering(sizes); + expect(sorted[0]).to.deep.equal({ w: 300, h: 250 }); + }); + + it('should maintain relative order for non-priority sizes', function () { + const sizes = [ + { w: 320, h: 50 }, + { w: 970, h: 250 } + ]; + const sorted = masSizeOrdering(sizes); + expect(sorted[0]).to.deep.equal({ w: 320, h: 50 }); + expect(sorted[1]).to.deep.equal({ w: 970, h: 250 }); + }); + + it('should handle a single size', function () { + const sizes = [{ w: 300, h: 250 }]; + const sorted = masSizeOrdering(sizes); + expect(sorted).to.deep.equal([{ w: 300, h: 250 }]); + }); + + it('should handle an empty array', function () { + const sorted = masSizeOrdering([]); + expect(sorted).to.deep.equal([]); + }); + }); + + describe('config merging', function () { + it('should read magnite config', function () { + config.setConfig({ magnite: { int_type: 'magnite_test' } }); + const requests = spec.buildRequests([getBannerBidRequest()], getBidderRequest()); + expect(requests[0].data.ext.prebid.channel.name).to.equal('magnite_test'); + }); + + it('should read rubicon config for backward compatibility', function () { + config.setConfig({ rubicon: { int_type: 'rubicon_test' } }); + const requests = spec.buildRequests([getBannerBidRequest()], getBidderRequest()); + expect(requests[0].data.ext.prebid.channel.name).to.equal('rubicon_test'); + }); + + it('should reset config with resetMgniConf', function () { + config.setConfig({ magnite: { int_type: 'custom' } }); + resetMgniConf(); + const requests = spec.buildRequests([getBannerBidRequest()], getBidderRequest()); + // After reset, should use default integration + expect(requests[0].data.ext.prebid.channel.name).to.equal('pbjs'); + }); + }); +}); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 3b58568dd87..d1d26c7d010 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -4332,14 +4332,14 @@ describe('the rubicon adapter', function () { const renderCall = window.MagniteApex.renderAd.getCall(0); expect(renderCall.args[0]).to.deep.equal({ closeButton: true, - collapse: false, height: 320, label: undefined, placement: { align: 'left', - attachTo: adUnitSelector, - position: 'append', + attachTo: adUnit, + position: 'prepend', }, + replay: true, vastUrl: 'https://test.com/vast.xml', width: 640 }); @@ -4402,14 +4402,14 @@ describe('the rubicon adapter', function () { const renderCall = window.MagniteApex.renderAd.getCall(0); expect(renderCall.args[0]).to.deep.equal({ closeButton: true, - collapse: false, height: 480, label: undefined, placement: { align: 'left', - attachTo: adUnitSelector, - position: 'append', + attachTo: adUnit, + position: 'prepend', }, + replay: true, vastUrl: 'https://test.com/vast.xml', width: 640 }); From 13cdc5a624901aabc28cc063c04f7477922c672a Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Mar 2026 20:12:57 +0000 Subject: [PATCH 13/50] Prebid 10.28.0 release --- metadata/modules.json | 14 +++++++++ metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/aceexBidAdapter.json | 2 +- metadata/modules/acuityadsBidAdapter.json | 2 +- metadata/modules/adagioBidAdapter.json | 2 +- metadata/modules/adagioRtdProvider.json | 2 +- metadata/modules/adbroBidAdapter.json | 2 +- metadata/modules/addefendBidAdapter.json | 2 +- metadata/modules/adfBidAdapter.json | 2 +- metadata/modules/adfusionBidAdapter.json | 2 +- metadata/modules/adheseBidAdapter.json | 2 +- metadata/modules/adipoloBidAdapter.json | 2 +- metadata/modules/adkernelAdnBidAdapter.json | 2 +- metadata/modules/adkernelBidAdapter.json | 10 +++---- metadata/modules/admaticBidAdapter.json | 4 +-- metadata/modules/admixerBidAdapter.json | 2 +- metadata/modules/admixerIdSystem.json | 2 +- metadata/modules/adnowBidAdapter.json | 2 +- metadata/modules/adnuntiusBidAdapter.json | 2 +- metadata/modules/adnuntiusRtdProvider.json | 2 +- metadata/modules/adoceanBidAdapter.json | 2 +- metadata/modules/adotBidAdapter.json | 2 +- metadata/modules/adponeBidAdapter.json | 2 +- metadata/modules/adqueryBidAdapter.json | 2 +- metadata/modules/adqueryIdSystem.json | 2 +- metadata/modules/adrinoBidAdapter.json | 2 +- .../modules/ads_interactiveBidAdapter.json | 2 +- metadata/modules/adtargetBidAdapter.json | 2 +- metadata/modules/adtelligentBidAdapter.json | 4 +-- metadata/modules/adtelligentIdSystem.json | 2 +- metadata/modules/aduptechBidAdapter.json | 2 +- metadata/modules/adyoulikeBidAdapter.json | 2 +- metadata/modules/airgridRtdProvider.json | 2 +- metadata/modules/alkimiBidAdapter.json | 2 +- metadata/modules/allegroBidAdapter.json | 2 +- metadata/modules/amxBidAdapter.json | 29 +++++++++++++++++-- metadata/modules/amxIdSystem.json | 29 +++++++++++++++++-- metadata/modules/aniviewBidAdapter.json | 2 +- metadata/modules/anonymisedRtdProvider.json | 2 +- metadata/modules/apesterBidAdapter.json | 2 +- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 8 ++--- metadata/modules/appushBidAdapter.json | 2 +- metadata/modules/apsBidAdapter.json | 2 +- metadata/modules/apstreamBidAdapter.json | 2 +- metadata/modules/audiencerunBidAdapter.json | 2 +- metadata/modules/axisBidAdapter.json | 2 +- metadata/modules/azerionedgeRtdProvider.json | 2 +- metadata/modules/beachfrontBidAdapter.json | 2 +- metadata/modules/beopBidAdapter.json | 2 +- metadata/modules/betweenBidAdapter.json | 2 +- metadata/modules/bidmaticBidAdapter.json | 4 +-- metadata/modules/bidtheatreBidAdapter.json | 2 +- metadata/modules/bliinkBidAdapter.json | 2 +- metadata/modules/blockthroughBidAdapter.json | 2 +- metadata/modules/blueBidAdapter.json | 2 +- metadata/modules/bmsBidAdapter.json | 2 +- metadata/modules/boldwinBidAdapter.json | 2 +- metadata/modules/bridBidAdapter.json | 2 +- metadata/modules/browsiBidAdapter.json | 2 +- metadata/modules/bucksenseBidAdapter.json | 2 +- metadata/modules/carodaBidAdapter.json | 2 +- metadata/modules/categoryTranslation.json | 2 +- metadata/modules/ceeIdSystem.json | 2 +- metadata/modules/chromeAiRtdProvider.json | 2 +- metadata/modules/clickioBidAdapter.json | 2 +- metadata/modules/compassBidAdapter.json | 2 +- metadata/modules/conceptxBidAdapter.json | 2 +- metadata/modules/connatixBidAdapter.json | 2 +- metadata/modules/connectIdSystem.json | 2 +- metadata/modules/connectadBidAdapter.json | 2 +- .../modules/contentexchangeBidAdapter.json | 4 +-- metadata/modules/conversantBidAdapter.json | 2 +- metadata/modules/copper6sspBidAdapter.json | 2 +- metadata/modules/cpmstarBidAdapter.json | 2 +- metadata/modules/criteoBidAdapter.json | 2 +- metadata/modules/criteoIdSystem.json | 2 +- metadata/modules/cwireBidAdapter.json | 2 +- metadata/modules/czechAdIdSystem.json | 2 +- metadata/modules/dailymotionBidAdapter.json | 2 +- metadata/modules/debugging.json | 2 +- metadata/modules/deepintentBidAdapter.json | 4 +-- metadata/modules/defineMediaBidAdapter.json | 2 +- metadata/modules/deltaprojectsBidAdapter.json | 2 +- metadata/modules/dianomiBidAdapter.json | 2 +- metadata/modules/digitalMatterBidAdapter.json | 2 +- metadata/modules/distroscaleBidAdapter.json | 2 +- .../modules/docereeAdManagerBidAdapter.json | 2 +- metadata/modules/docereeBidAdapter.json | 2 +- metadata/modules/dspxBidAdapter.json | 2 +- metadata/modules/e_volutionBidAdapter.json | 2 +- metadata/modules/edge226BidAdapter.json | 2 +- metadata/modules/empowerBidAdapter.json | 2 +- metadata/modules/equativBidAdapter.json | 2 +- metadata/modules/eskimiBidAdapter.json | 2 +- metadata/modules/etargetBidAdapter.json | 2 +- metadata/modules/euidIdSystem.json | 2 +- metadata/modules/exadsBidAdapter.json | 2 +- metadata/modules/feedadBidAdapter.json | 2 +- metadata/modules/fwsspBidAdapter.json | 2 +- metadata/modules/gamoshiBidAdapter.json | 8 ++--- metadata/modules/gemiusIdSystem.json | 2 +- metadata/modules/glomexBidAdapter.json | 2 +- metadata/modules/goldbachBidAdapter.json | 2 +- metadata/modules/gridBidAdapter.json | 2 +- metadata/modules/gumgumBidAdapter.json | 2 +- metadata/modules/hadronIdSystem.json | 2 +- metadata/modules/hadronRtdProvider.json | 2 +- metadata/modules/harionBidAdapter.json | 2 +- metadata/modules/holidBidAdapter.json | 2 +- metadata/modules/hybridBidAdapter.json | 2 +- metadata/modules/id5IdSystem.json | 2 +- metadata/modules/identityLinkIdSystem.json | 2 +- metadata/modules/illuminBidAdapter.json | 2 +- metadata/modules/impactifyBidAdapter.json | 2 +- .../modules/improvedigitalBidAdapter.json | 2 +- metadata/modules/inmobiBidAdapter.json | 2 +- metadata/modules/insticatorBidAdapter.json | 2 +- metadata/modules/insuradsBidAdapter.json | 2 +- metadata/modules/intentIqIdSystem.json | 2 +- metadata/modules/invibesBidAdapter.json | 2 +- metadata/modules/ipromBidAdapter.json | 2 +- metadata/modules/ixBidAdapter.json | 2 +- metadata/modules/justIdSystem.json | 2 +- metadata/modules/justpremiumBidAdapter.json | 2 +- metadata/modules/jwplayerBidAdapter.json | 2 +- metadata/modules/kargoBidAdapter.json | 2 +- metadata/modules/kueezRtbBidAdapter.json | 2 +- .../modules/limelightDigitalBidAdapter.json | 11 +++++-- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 2 +- metadata/modules/magniteBidAdapter.json | 18 ++++++++++++ metadata/modules/marsmediaBidAdapter.json | 2 +- .../modules/mediaConsortiumBidAdapter.json | 2 +- metadata/modules/mediaforceBidAdapter.json | 2 +- metadata/modules/mediafuseBidAdapter.json | 2 +- metadata/modules/mediagoBidAdapter.json | 2 +- metadata/modules/mediakeysBidAdapter.json | 2 +- metadata/modules/medianetBidAdapter.json | 4 +-- metadata/modules/mediasquareBidAdapter.json | 2 +- metadata/modules/mgidBidAdapter.json | 2 +- metadata/modules/mgidRtdProvider.json | 2 +- metadata/modules/mgidXBidAdapter.json | 2 +- metadata/modules/minutemediaBidAdapter.json | 2 +- metadata/modules/missenaBidAdapter.json | 2 +- metadata/modules/mobianRtdProvider.json | 2 +- metadata/modules/mobkoiBidAdapter.json | 4 +-- metadata/modules/mobkoiIdSystem.json | 4 +-- metadata/modules/msftBidAdapter.json | 2 +- metadata/modules/nativeryBidAdapter.json | 2 +- metadata/modules/nativoBidAdapter.json | 2 +- metadata/modules/newspassidBidAdapter.json | 2 +- .../modules/nextMillenniumBidAdapter.json | 2 +- metadata/modules/nextrollBidAdapter.json | 2 +- metadata/modules/nexx360BidAdapter.json | 10 +++---- metadata/modules/nobidBidAdapter.json | 2 +- metadata/modules/nodalsAiRtdProvider.json | 2 +- metadata/modules/novatiqIdSystem.json | 2 +- metadata/modules/oguryBidAdapter.json | 2 +- metadata/modules/omnidexBidAdapter.json | 2 +- metadata/modules/omsBidAdapter.json | 2 +- metadata/modules/onetagBidAdapter.json | 2 +- metadata/modules/openwebBidAdapter.json | 2 +- metadata/modules/openxBidAdapter.json | 2 +- metadata/modules/operaadsBidAdapter.json | 2 +- metadata/modules/optidigitalBidAdapter.json | 2 +- metadata/modules/optoutBidAdapter.json | 2 +- metadata/modules/orbidderBidAdapter.json | 2 +- metadata/modules/outbrainBidAdapter.json | 2 +- metadata/modules/ozoneBidAdapter.json | 2 +- metadata/modules/pairIdSystem.json | 2 +- metadata/modules/panxoBidAdapter.json | 2 +- metadata/modules/performaxBidAdapter.json | 2 +- .../permutiveIdentityManagerIdSystem.json | 2 +- metadata/modules/permutiveRtdProvider.json | 2 +- metadata/modules/pixfutureBidAdapter.json | 2 +- metadata/modules/playdigoBidAdapter.json | 2 +- metadata/modules/prebid-core.json | 4 +-- metadata/modules/precisoBidAdapter.json | 2 +- metadata/modules/programmaticXBidAdapter.json | 2 +- metadata/modules/proxistoreBidAdapter.json | 2 +- metadata/modules/publinkIdSystem.json | 2 +- metadata/modules/pubmaticBidAdapter.json | 2 +- metadata/modules/pubmaticIdSystem.json | 2 +- metadata/modules/pubstackBidAdapter.json | 2 +- metadata/modules/pulsepointBidAdapter.json | 2 +- metadata/modules/quantcastBidAdapter.json | 2 +- metadata/modules/quantcastIdSystem.json | 2 +- metadata/modules/r2b2BidAdapter.json | 2 +- metadata/modules/readpeakBidAdapter.json | 2 +- metadata/modules/relayBidAdapter.json | 2 +- .../modules/relevantdigitalBidAdapter.json | 2 +- metadata/modules/resetdigitalBidAdapter.json | 2 +- metadata/modules/responsiveAdsBidAdapter.json | 2 +- metadata/modules/revcontentBidAdapter.json | 2 +- metadata/modules/revnewBidAdapter.json | 2 +- metadata/modules/rhythmoneBidAdapter.json | 2 +- metadata/modules/richaudienceBidAdapter.json | 2 +- metadata/modules/riseBidAdapter.json | 4 +-- metadata/modules/rixengineBidAdapter.json | 2 +- metadata/modules/rtbhouseBidAdapter.json | 2 +- metadata/modules/rubiconBidAdapter.json | 2 +- metadata/modules/scaliburBidAdapter.json | 2 +- metadata/modules/screencoreBidAdapter.json | 2 +- .../modules/seedingAllianceBidAdapter.json | 2 +- metadata/modules/seedtagBidAdapter.json | 2 +- metadata/modules/semantiqRtdProvider.json | 2 +- metadata/modules/sevioBidAdapter.json | 2 +- metadata/modules/sharedIdSystem.json | 2 +- metadata/modules/sharethroughBidAdapter.json | 2 +- metadata/modules/showheroes-bsBidAdapter.json | 2 +- metadata/modules/silvermobBidAdapter.json | 2 +- metadata/modules/sirdataRtdProvider.json | 2 +- metadata/modules/smaatoBidAdapter.json | 2 +- metadata/modules/smartadserverBidAdapter.json | 2 +- metadata/modules/smartxBidAdapter.json | 2 +- metadata/modules/smartyadsBidAdapter.json | 2 +- metadata/modules/smilewantedBidAdapter.json | 2 +- metadata/modules/snigelBidAdapter.json | 2 +- metadata/modules/sonaradsBidAdapter.json | 2 +- metadata/modules/sonobiBidAdapter.json | 2 +- metadata/modules/sovrnBidAdapter.json | 2 +- metadata/modules/sparteoBidAdapter.json | 2 +- metadata/modules/ssmasBidAdapter.json | 2 +- metadata/modules/sspBCBidAdapter.json | 2 +- metadata/modules/stackadaptBidAdapter.json | 2 +- metadata/modules/startioBidAdapter.json | 2 +- metadata/modules/stroeerCoreBidAdapter.json | 2 +- metadata/modules/stvBidAdapter.json | 2 +- metadata/modules/sublimeBidAdapter.json | 2 +- metadata/modules/taboolaBidAdapter.json | 2 +- metadata/modules/taboolaIdSystem.json | 2 +- metadata/modules/tadvertisingBidAdapter.json | 2 +- metadata/modules/tappxBidAdapter.json | 2 +- metadata/modules/targetVideoBidAdapter.json | 2 +- metadata/modules/teadsBidAdapter.json | 2 +- metadata/modules/teadsIdSystem.json | 2 +- metadata/modules/tealBidAdapter.json | 2 +- metadata/modules/tncIdSystem.json | 2 +- metadata/modules/topicsFpdModule.json | 2 +- metadata/modules/toponBidAdapter.json | 2 +- metadata/modules/tripleliftBidAdapter.json | 2 +- metadata/modules/ttdBidAdapter.json | 2 +- metadata/modules/twistDigitalBidAdapter.json | 2 +- metadata/modules/underdogmediaBidAdapter.json | 2 +- metadata/modules/undertoneBidAdapter.json | 2 +- metadata/modules/unifiedIdSystem.json | 2 +- metadata/modules/unrulyBidAdapter.json | 2 +- metadata/modules/userId.json | 2 +- metadata/modules/utiqIdSystem.json | 2 +- metadata/modules/utiqMtpIdSystem.json | 2 +- metadata/modules/validationFpdModule.json | 2 +- metadata/modules/valuadBidAdapter.json | 2 +- metadata/modules/vidazooBidAdapter.json | 2 +- metadata/modules/vidoomyBidAdapter.json | 2 +- metadata/modules/viouslyBidAdapter.json | 2 +- metadata/modules/visxBidAdapter.json | 2 +- metadata/modules/vlybyBidAdapter.json | 2 +- metadata/modules/voxBidAdapter.json | 2 +- metadata/modules/vrtcalBidAdapter.json | 2 +- metadata/modules/vuukleBidAdapter.json | 2 +- metadata/modules/weboramaRtdProvider.json | 6 ++-- metadata/modules/welectBidAdapter.json | 2 +- metadata/modules/yahooAdsBidAdapter.json | 2 +- metadata/modules/yaleoBidAdapter.json | 2 +- metadata/modules/yieldloveBidAdapter.json | 2 +- metadata/modules/yieldmoBidAdapter.json | 2 +- metadata/modules/zeotapIdPlusIdSystem.json | 2 +- metadata/modules/zeta_globalBidAdapter.json | 2 +- .../modules/zeta_global_sspBidAdapter.json | 2 +- package-lock.json | 28 +++++++++--------- package.json | 2 +- 279 files changed, 408 insertions(+), 319 deletions(-) create mode 100644 metadata/modules/magniteBidAdapter.json diff --git a/metadata/modules.json b/metadata/modules.json index 60c9f672a99..1a7ea39c1ac 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -2955,6 +2955,13 @@ "gvlid": null, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "embimedia", + "aliasOf": "limelightDigital", + "gvlid": null, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "livewrapped", @@ -3067,6 +3074,13 @@ "gvlid": 153, "disclosureURL": null }, + { + "componentType": "bidder", + "componentName": "magnite", + "aliasOf": null, + "gvlid": 52, + "disclosureURL": null + }, { "componentType": "bidder", "componentName": "malltv", diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 79ecf25a705..0e1c39d23dc 100644 --- a/metadata/modules/33acrossBidAdapter.json +++ b/metadata/modules/33acrossBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-02T14:44:46.319Z", + "timestamp": "2026-03-12T20:07:50.567Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 3adce4bba03..c7f80d837a8 100644 --- a/metadata/modules/33acrossIdSystem.json +++ b/metadata/modules/33acrossIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-02T14:44:46.422Z", + "timestamp": "2026-03-12T20:07:50.688Z", "disclosures": [] } }, diff --git a/metadata/modules/aceexBidAdapter.json b/metadata/modules/aceexBidAdapter.json index f443e609ec4..d6564b624e0 100644 --- a/metadata/modules/aceexBidAdapter.json +++ b/metadata/modules/aceexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://aceex.io/tcf.json": { - "timestamp": "2026-03-02T14:44:46.425Z", + "timestamp": "2026-03-12T20:07:50.692Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index 01786a901ea..d76e93e97e1 100644 --- a/metadata/modules/acuityadsBidAdapter.json +++ b/metadata/modules/acuityadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.acuityads.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:44:46.471Z", + "timestamp": "2026-03-12T20:07:50.756Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 728761cdcc3..abb88d1925b 100644 --- a/metadata/modules/adagioBidAdapter.json +++ b/metadata/modules/adagioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:44:46.503Z", + "timestamp": "2026-03-12T20:07:50.795Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index 01aba8c973b..961cce01df6 100644 --- a/metadata/modules/adagioRtdProvider.json +++ b/metadata/modules/adagioRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:44:46.575Z", + "timestamp": "2026-03-12T20:07:50.890Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index c1607d5b2ea..9a24a867779 100644 --- a/metadata/modules/adbroBidAdapter.json +++ b/metadata/modules/adbroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tag.adbro.me/privacy/devicestorage.json": { - "timestamp": "2026-03-02T14:44:46.575Z", + "timestamp": "2026-03-12T20:07:50.890Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 1cd4c70fc97..b86e5bfd3fd 100644 --- a/metadata/modules/addefendBidAdapter.json +++ b/metadata/modules/addefendBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.addefend.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:44:46.894Z", + "timestamp": "2026-03-12T20:07:51.046Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index 8e01eb95878..c105cd988cc 100644 --- a/metadata/modules/adfBidAdapter.json +++ b/metadata/modules/adfBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://site.adform.com/assets/devicestorage.json": { - "timestamp": "2026-03-02T14:45:00.675Z", + "timestamp": "2026-03-12T20:09:22.750Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index bec524db96d..a0e1fd451fb 100644 --- a/metadata/modules/adfusionBidAdapter.json +++ b/metadata/modules/adfusionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spicyrtb.com/static/iab-disclosure.json": { - "timestamp": "2026-03-02T14:45:00.676Z", + "timestamp": "2026-03-12T20:09:22.751Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index 341d5b1c818..60c2184abe5 100644 --- a/metadata/modules/adheseBidAdapter.json +++ b/metadata/modules/adheseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adhese.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:01.041Z", + "timestamp": "2026-03-12T20:09:23.109Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 54ee67e7037..ab333fa1f46 100644 --- a/metadata/modules/adipoloBidAdapter.json +++ b/metadata/modules/adipoloBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adipolo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:01.314Z", + "timestamp": "2026-03-12T20:09:23.379Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index 0516f01d293..46808fd002e 100644 --- a/metadata/modules/adkernelAdnBidAdapter.json +++ b/metadata/modules/adkernelAdnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:01.470Z", + "timestamp": "2026-03-12T20:09:23.518Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index 8b11e6d7d24..038bf8eb8bd 100644 --- a/metadata/modules/adkernelBidAdapter.json +++ b/metadata/modules/adkernelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:01.554Z", + "timestamp": "2026-03-12T20:09:23.617Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:01.554Z", + "timestamp": "2026-03-12T20:09:23.617Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-03-02T14:45:01.604Z", + "timestamp": "2026-03-12T20:09:23.664Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:02.320Z", + "timestamp": "2026-03-12T20:09:24.388Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:02.337Z", + "timestamp": "2026-03-12T20:09:24.422Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index ded364cbc31..a928792b968 100644 --- a/metadata/modules/admaticBidAdapter.json +++ b/metadata/modules/admaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.admatic.de/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-02T14:45:02.895Z", + "timestamp": "2026-03-12T20:09:24.986Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:02.895Z", + "timestamp": "2026-03-12T20:09:24.986Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 8f6d6f99397..96196502c47 100644 --- a/metadata/modules/admixerBidAdapter.json +++ b/metadata/modules/admixerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-02T14:45:02.896Z", + "timestamp": "2026-03-12T20:09:24.987Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index f8bd80f3bca..9827e71ba9e 100644 --- a/metadata/modules/admixerIdSystem.json +++ b/metadata/modules/admixerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-02T14:45:03.276Z", + "timestamp": "2026-03-12T20:09:25.396Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index cb5aa129b20..b802e0d92a7 100644 --- a/metadata/modules/adnowBidAdapter.json +++ b/metadata/modules/adnowBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adnow.com/vdsod.json": { - "timestamp": "2026-03-02T14:45:03.276Z", + "timestamp": "2026-03-12T20:09:25.396Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index c2a643ef408..52ae74419d3 100644 --- a/metadata/modules/adnuntiusBidAdapter.json +++ b/metadata/modules/adnuntiusBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:16.546Z", + "timestamp": "2026-03-12T20:09:25.634Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 9c3af875f20..5618b819f8a 100644 --- a/metadata/modules/adnuntiusRtdProvider.json +++ b/metadata/modules/adnuntiusRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:16.877Z", + "timestamp": "2026-03-12T20:09:25.968Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index c97b093663c..1f66da9ae87 100644 --- a/metadata/modules/adoceanBidAdapter.json +++ b/metadata/modules/adoceanBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-02T14:45:16.878Z", + "timestamp": "2026-03-12T20:09:25.968Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 9a38b9d66e3..1a1d4c84741 100644 --- a/metadata/modules/adotBidAdapter.json +++ b/metadata/modules/adotBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.adotmob.com/tcf/tcf.json": { - "timestamp": "2026-03-02T14:45:17.430Z", + "timestamp": "2026-03-12T20:09:26.419Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index 7022a062e25..304a33511d0 100644 --- a/metadata/modules/adponeBidAdapter.json +++ b/metadata/modules/adponeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.adpone.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:17.575Z", + "timestamp": "2026-03-12T20:09:26.467Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 6ee1ee7d899..fe621713b29 100644 --- a/metadata/modules/adqueryBidAdapter.json +++ b/metadata/modules/adqueryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-02T14:45:17.832Z", + "timestamp": "2026-03-12T20:09:26.494Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index d97e79287da..c31f49b997b 100644 --- a/metadata/modules/adqueryIdSystem.json +++ b/metadata/modules/adqueryIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-02T14:45:18.165Z", + "timestamp": "2026-03-12T20:09:26.871Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 1683ae8d010..1c06ed4a78a 100644 --- a/metadata/modules/adrinoBidAdapter.json +++ b/metadata/modules/adrinoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.adrino.cloud/iab/device-storage.json": { - "timestamp": "2026-03-02T14:45:18.166Z", + "timestamp": "2026-03-12T20:09:26.872Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index c0bcc3c0438..09861de2d3a 100644 --- a/metadata/modules/ads_interactiveBidAdapter.json +++ b/metadata/modules/ads_interactiveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adsinteractive.com/vendor.json": { - "timestamp": "2026-03-02T14:45:18.257Z", + "timestamp": "2026-03-12T20:09:27.247Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 643548e6a49..3b9eb9e75d1 100644 --- a/metadata/modules/adtargetBidAdapter.json +++ b/metadata/modules/adtargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:18.549Z", + "timestamp": "2026-03-12T20:09:27.561Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 68c149946c6..fb531219fe7 100644 --- a/metadata/modules/adtelligentBidAdapter.json +++ b/metadata/modules/adtelligentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:18.549Z", + "timestamp": "2026-03-12T20:09:27.561Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:31.321Z", + "timestamp": "2026-03-12T20:10:28.745Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 63773fb0465..b6814532fcf 100644 --- a/metadata/modules/adtelligentIdSystem.json +++ b/metadata/modules/adtelligentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:31.395Z", + "timestamp": "2026-03-12T20:10:28.821Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index 63a0f554c7f..c5e40557617 100644 --- a/metadata/modules/aduptechBidAdapter.json +++ b/metadata/modules/aduptechBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.d.adup-tech.com/gdpr/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:31.395Z", + "timestamp": "2026-03-12T20:10:28.822Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index 80d9bc14b7e..4e85fd6fd91 100644 --- a/metadata/modules/adyoulikeBidAdapter.json +++ b/metadata/modules/adyoulikeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adyoulike.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-02T14:45:31.420Z", + "timestamp": "2026-03-12T20:10:28.837Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index e127fd2d00b..b0f1252e046 100644 --- a/metadata/modules/airgridRtdProvider.json +++ b/metadata/modules/airgridRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.wearemiq.com/privacy-and-compliance/devicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:31.871Z", + "timestamp": "2026-03-12T20:10:29.271Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index a9638ab0a47..029144473d8 100644 --- a/metadata/modules/alkimiBidAdapter.json +++ b/metadata/modules/alkimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d1xjh92lb8fey3.cloudfront.net/tcf/alkimi_exchange_tcf.json": { - "timestamp": "2026-03-02T14:45:31.902Z", + "timestamp": "2026-03-12T20:10:29.333Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index 0d631041437..836db85bca7 100644 --- a/metadata/modules/allegroBidAdapter.json +++ b/metadata/modules/allegroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.allegrostatic.com/dsp-tcf-external/device-storage.json": { - "timestamp": "2026-03-02T14:45:32.202Z", + "timestamp": "2026-03-12T20:10:29.620Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 8a4900eb3fb..86f492334f9 100644 --- a/metadata/modules/amxBidAdapter.json +++ b/metadata/modules/amxBidAdapter.json @@ -2,8 +2,33 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-02T14:45:32.667Z", - "disclosures": [] + "timestamp": "2026-03-12T20:10:30.066Z", + "disclosures": [ + { + "identifier": "amuid2", + "type": "cookie", + "maxAgeSeconds": 31536000, + "cookieRefresh": false, + "purposes": [ + 1, + 2, + 4, + 7 + ] + }, + { + "identifier": "__amuidpb", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1, + 2, + 4, + 7 + ] + } + ] } }, "components": [ diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index a204078442a..b9b195ab95f 100644 --- a/metadata/modules/amxIdSystem.json +++ b/metadata/modules/amxIdSystem.json @@ -2,8 +2,33 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-02T14:45:32.757Z", - "disclosures": [] + "timestamp": "2026-03-12T20:10:30.199Z", + "disclosures": [ + { + "identifier": "amuid2", + "type": "cookie", + "maxAgeSeconds": 31536000, + "cookieRefresh": false, + "purposes": [ + 1, + 2, + 4, + 7 + ] + }, + { + "identifier": "__amuidpb", + "type": "web", + "maxAgeSeconds": null, + "cookieRefresh": false, + "purposes": [ + 1, + 2, + 4, + 7 + ] + } + ] } }, "components": [ diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index 0bc1a7c7752..fed22a340f9 100644 --- a/metadata/modules/aniviewBidAdapter.json +++ b/metadata/modules/aniviewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.aniview.com/gdpr/gdpr.json": { - "timestamp": "2026-03-02T14:45:32.758Z", + "timestamp": "2026-03-12T20:10:30.199Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 7593b8bcb11..eb60678a613 100644 --- a/metadata/modules/anonymisedRtdProvider.json +++ b/metadata/modules/anonymisedRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn1.anonymised.io/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:32.828Z", + "timestamp": "2026-03-12T20:10:30.342Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/apesterBidAdapter.json b/metadata/modules/apesterBidAdapter.json index 372e0ba6a78..b95402aef5b 100644 --- a/metadata/modules/apesterBidAdapter.json +++ b/metadata/modules/apesterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apester.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:33.047Z", + "timestamp": "2026-03-12T20:10:30.884Z", "disclosures": [] } }, diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 8d113568028..df89dfbee09 100644 --- a/metadata/modules/appStockSSPBidAdapter.json +++ b/metadata/modules/appStockSSPBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://app-stock.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:33.167Z", + "timestamp": "2026-03-12T20:10:31.017Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index bc805982843..6fa15f241e2 100644 --- a/metadata/modules/appierBidAdapter.json +++ b/metadata/modules/appierBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.appier.com/deviceStorage2025.json": { - "timestamp": "2026-03-02T14:45:33.203Z", + "timestamp": "2026-03-12T20:10:31.063Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index 59027a399d7..f6c2f1e1acb 100644 --- a/metadata/modules/appnexusBidAdapter.json +++ b/metadata/modules/appnexusBidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:33.960Z", + "timestamp": "2026-03-12T20:10:31.660Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:33.279Z", + "timestamp": "2026-03-12T20:10:31.176Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:33.420Z", + "timestamp": "2026-03-12T20:10:31.204Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-03-02T14:45:33.960Z", + "timestamp": "2026-03-12T20:10:31.660Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index 15019d11110..c9a7e356891 100644 --- a/metadata/modules/appushBidAdapter.json +++ b/metadata/modules/appushBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.thebiding.com/disclosures.json": { - "timestamp": "2026-03-02T14:45:33.989Z", + "timestamp": "2026-03-12T20:10:31.689Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index 7132ee483e9..cedf2e2134d 100644 --- a/metadata/modules/apsBidAdapter.json +++ b/metadata/modules/apsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://m.media-amazon.com/images/G/01/adprefs/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:34.055Z", + "timestamp": "2026-03-12T20:10:31.776Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index b7a2fb2671d..ed0eaf1954b 100644 --- a/metadata/modules/apstreamBidAdapter.json +++ b/metadata/modules/apstreamBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sak.userreport.com/tcf.json": { - "timestamp": "2026-03-02T14:45:34.071Z", + "timestamp": "2026-03-12T20:10:31.795Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index ad7d2d71fc5..204e61370e6 100644 --- a/metadata/modules/audiencerunBidAdapter.json +++ b/metadata/modules/audiencerunBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.audiencerun.com/tcf.json": { - "timestamp": "2026-03-02T14:45:34.115Z", + "timestamp": "2026-03-12T20:10:31.819Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 33ade7a617c..5a6e16a454c 100644 --- a/metadata/modules/axisBidAdapter.json +++ b/metadata/modules/axisBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://axis-marketplace.com/tcf.json": { - "timestamp": "2026-03-02T14:45:34.167Z", + "timestamp": "2026-03-12T20:10:31.864Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 198266cb6ac..0fca848ffe0 100644 --- a/metadata/modules/azerionedgeRtdProvider.json +++ b/metadata/modules/azerionedgeRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-02T14:45:34.210Z", + "timestamp": "2026-03-12T20:10:31.904Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index b7f18cad4eb..87c694c9db7 100644 --- a/metadata/modules/beachfrontBidAdapter.json +++ b/metadata/modules/beachfrontBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-02T14:45:34.236Z", + "timestamp": "2026-03-12T20:10:31.932Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 84b4ecc7f33..883a8b3a883 100644 --- a/metadata/modules/beopBidAdapter.json +++ b/metadata/modules/beopBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://beop.io/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:34.257Z", + "timestamp": "2026-03-12T20:10:32.319Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index b48dadd84d0..09f0274974e 100644 --- a/metadata/modules/betweenBidAdapter.json +++ b/metadata/modules/betweenBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.betweenx.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:34.379Z", + "timestamp": "2026-03-12T20:10:32.538Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index 320c69b6e66..cd5e45abbbf 100644 --- a/metadata/modules/bidmaticBidAdapter.json +++ b/metadata/modules/bidmaticBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidmatic.io/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:34.611Z", - "disclosures": null + "timestamp": "2026-03-12T20:11:05.133Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 987e79d876e..ac282d8a5f5 100644 --- a/metadata/modules/bidtheatreBidAdapter.json +++ b/metadata/modules/bidtheatreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.bidtheatre.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:34.658Z", + "timestamp": "2026-03-12T20:11:05.188Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index 9ae146cb5de..9213fa6afd6 100644 --- a/metadata/modules/bliinkBidAdapter.json +++ b/metadata/modules/bliinkBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bliink.io/disclosures.json": { - "timestamp": "2026-03-02T14:45:34.957Z", + "timestamp": "2026-03-12T20:11:05.474Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index b3c53c81f31..74021003963 100644 --- a/metadata/modules/blockthroughBidAdapter.json +++ b/metadata/modules/blockthroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://blockthrough.com/tcf_disclosures.json": { - "timestamp": "2026-03-02T14:45:35.252Z", + "timestamp": "2026-03-12T20:11:05.793Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index fdefc12b8e7..f3e2ef25e3d 100644 --- a/metadata/modules/blueBidAdapter.json +++ b/metadata/modules/blueBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://getblue.io/iab/iab.json": { - "timestamp": "2026-03-02T14:45:35.438Z", + "timestamp": "2026-03-12T20:11:05.921Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 59d3a1914dc..fbc0c323e58 100644 --- a/metadata/modules/bmsBidAdapter.json +++ b/metadata/modules/bmsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bluems.com/iab.json": { - "timestamp": "2026-03-02T14:45:35.784Z", + "timestamp": "2026-03-12T20:11:06.274Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index e8f694fd2b9..eceb7585fb6 100644 --- a/metadata/modules/boldwinBidAdapter.json +++ b/metadata/modules/boldwinBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://magav.videowalldirect.com/iab/videowalldirectiab.json": { - "timestamp": "2026-03-02T14:45:35.801Z", + "timestamp": "2026-03-12T20:11:06.301Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 4475c15ed3a..f4da6115f85 100644 --- a/metadata/modules/bridBidAdapter.json +++ b/metadata/modules/bridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-02T14:45:35.828Z", + "timestamp": "2026-03-12T20:11:06.339Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index ddc532d4e32..156b8b1f0da 100644 --- a/metadata/modules/browsiBidAdapter.json +++ b/metadata/modules/browsiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.browsiprod.com/ads/tcf.json": { - "timestamp": "2026-03-02T14:45:35.966Z", + "timestamp": "2026-03-12T20:11:06.477Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 571e8dac8d9..6ee6ef19f94 100644 --- a/metadata/modules/bucksenseBidAdapter.json +++ b/metadata/modules/bucksenseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://j.bksnimages.com/iab/devsto02.json": { - "timestamp": "2026-03-02T14:45:36.025Z", + "timestamp": "2026-03-12T20:11:06.559Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index 06cedda2cbb..2bd1430c020 100644 --- a/metadata/modules/carodaBidAdapter.json +++ b/metadata/modules/carodaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn2.caroda.io/tcfvds/2022-05-17/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:36.084Z", + "timestamp": "2026-03-12T20:11:06.670Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index 1b8ecd5185e..4a46a6c4cf7 100644 --- a/metadata/modules/categoryTranslation.json +++ b/metadata/modules/categoryTranslation.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json": { - "timestamp": "2026-03-02T14:44:46.317Z", + "timestamp": "2026-03-12T20:07:50.564Z", "disclosures": [ { "identifier": "iabToFwMappingkey", diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 414cf9f61c3..114245b1539 100644 --- a/metadata/modules/ceeIdSystem.json +++ b/metadata/modules/ceeIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:36.379Z", + "timestamp": "2026-03-12T20:11:06.979Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 0374f04c2e3..73259594275 100644 --- a/metadata/modules/chromeAiRtdProvider.json +++ b/metadata/modules/chromeAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/chromeAiRtdProvider.json": { - "timestamp": "2026-03-02T14:45:36.729Z", + "timestamp": "2026-03-12T20:11:07.299Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index ec57e47521f..bde0f1dd83f 100644 --- a/metadata/modules/clickioBidAdapter.json +++ b/metadata/modules/clickioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://o.clickiocdn.com/tcf_storage_info.json": { - "timestamp": "2026-03-02T14:45:36.730Z", + "timestamp": "2026-03-12T20:11:07.300Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 145961029d1..2e71f4e263c 100644 --- a/metadata/modules/compassBidAdapter.json +++ b/metadata/modules/compassBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-02T14:45:37.148Z", + "timestamp": "2026-03-12T20:11:07.736Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index 41d01c918e0..a8c476966c2 100644 --- a/metadata/modules/conceptxBidAdapter.json +++ b/metadata/modules/conceptxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cncptx.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:37.166Z", + "timestamp": "2026-03-12T20:11:07.755Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index a1b7a772220..6164e1f1041 100644 --- a/metadata/modules/connatixBidAdapter.json +++ b/metadata/modules/connatixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://connatix.com/iab-tcf-disclosure.json": { - "timestamp": "2026-03-02T14:45:37.190Z", + "timestamp": "2026-03-12T20:11:07.789Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 0cd76902e9b..8b37eb7102d 100644 --- a/metadata/modules/connectIdSystem.json +++ b/metadata/modules/connectIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:45:37.270Z", + "timestamp": "2026-03-12T20:11:07.874Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index 96bc8e141d6..8122cd66c61 100644 --- a/metadata/modules/connectadBidAdapter.json +++ b/metadata/modules/connectadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.connectad.io/tcf_storage_info.json": { - "timestamp": "2026-03-02T14:45:37.290Z", + "timestamp": "2026-03-12T20:11:07.894Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index 7b5a61930c9..3c95b10aa4e 100644 --- a/metadata/modules/contentexchangeBidAdapter.json +++ b/metadata/modules/contentexchangeBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://hb.contentexchange.me/template/device_storage.json": { - "timestamp": "2026-03-02T14:45:37.325Z", - "disclosures": null + "timestamp": "2026-03-12T20:11:08.321Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index 5999b1b9c9a..5750fd85f21 100644 --- a/metadata/modules/conversantBidAdapter.json +++ b/metadata/modules/conversantBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:37.366Z", + "timestamp": "2026-03-12T20:11:08.796Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 1bc771d2901..c9dd6c7c032 100644 --- a/metadata/modules/copper6sspBidAdapter.json +++ b/metadata/modules/copper6sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.copper6.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:37.458Z", + "timestamp": "2026-03-12T20:11:08.910Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 255976fa486..d9be74ba790 100644 --- a/metadata/modules/cpmstarBidAdapter.json +++ b/metadata/modules/cpmstarBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-02T14:45:37.500Z", + "timestamp": "2026-03-12T20:11:08.952Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index fb94d78e961..8242d0382ae 100644 --- a/metadata/modules/criteoBidAdapter.json +++ b/metadata/modules/criteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-02T14:45:37.599Z", + "timestamp": "2026-03-12T20:11:09.031Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index d0e56b65ba1..2b0b0039acb 100644 --- a/metadata/modules/criteoIdSystem.json +++ b/metadata/modules/criteoIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-02T14:45:37.624Z", + "timestamp": "2026-03-12T20:11:09.043Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index 265357a9c7f..d117de0f525 100644 --- a/metadata/modules/cwireBidAdapter.json +++ b/metadata/modules/cwireBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.cwi.re/artifacts/iab/iab.json": { - "timestamp": "2026-03-02T14:45:37.625Z", + "timestamp": "2026-03-12T20:11:09.044Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index 7a945949fe0..752dd8d4ece 100644 --- a/metadata/modules/czechAdIdSystem.json +++ b/metadata/modules/czechAdIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cpex.cz/storagedisclosure.json": { - "timestamp": "2026-03-02T14:45:37.988Z", + "timestamp": "2026-03-12T20:11:09.430Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index 6779bc9b8a9..e31ccfb9b1d 100644 --- a/metadata/modules/dailymotionBidAdapter.json +++ b/metadata/modules/dailymotionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://statics.dmcdn.net/a/vds.json": { - "timestamp": "2026-03-02T14:45:38.394Z", + "timestamp": "2026-03-12T20:11:09.837Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index d8035174738..3daf12d1d24 100644 --- a/metadata/modules/debugging.json +++ b/metadata/modules/debugging.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-02T14:44:46.316Z", + "timestamp": "2026-03-12T20:07:50.563Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index f9698c4095a..0b4267fd5eb 100644 --- a/metadata/modules/deepintentBidAdapter.json +++ b/metadata/modules/deepintentBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.deepintent.com/iabeurope_vendor_disclosures.json": { - "timestamp": "2026-02-23T16:45:55.384Z", - "disclosures": [] + "timestamp": "2026-03-12T20:11:09.936Z", + "disclosures": null } }, "components": [ diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index cc0c299d4f7..e1fca53370a 100644 --- a/metadata/modules/defineMediaBidAdapter.json +++ b/metadata/modules/defineMediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://definemedia.de/tcf/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-02T14:45:38.686Z", + "timestamp": "2026-03-12T20:11:10.124Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index ca2f76b4695..72390b58d0d 100644 --- a/metadata/modules/deltaprojectsBidAdapter.json +++ b/metadata/modules/deltaprojectsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.de17a.com/policy/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:39.098Z", + "timestamp": "2026-03-12T20:11:10.548Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 1ee3130984b..515481d3cbf 100644 --- a/metadata/modules/dianomiBidAdapter.json +++ b/metadata/modules/dianomiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.dianomi.com/device_storage.json": { - "timestamp": "2026-03-02T14:45:44.672Z", + "timestamp": "2026-03-12T20:11:10.960Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 8d8e30c4102..bb6331b17fe 100644 --- a/metadata/modules/digitalMatterBidAdapter.json +++ b/metadata/modules/digitalMatterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://digitalmatter.ai/disclosures.json": { - "timestamp": "2026-03-02T14:45:44.672Z", + "timestamp": "2026-03-12T20:11:10.962Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index b19604b5704..574cb7cc65a 100644 --- a/metadata/modules/distroscaleBidAdapter.json +++ b/metadata/modules/distroscaleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.jsrdn.com/tcf/tcf-vendor-disclosure.json": { - "timestamp": "2026-03-02T14:45:45.048Z", + "timestamp": "2026-03-12T20:11:11.313Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index 676eab6e3c8..e06663e0606 100644 --- a/metadata/modules/docereeAdManagerBidAdapter.json +++ b/metadata/modules/docereeAdManagerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:45.334Z", + "timestamp": "2026-03-12T20:11:11.567Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index 14a0769efca..a9a1a8cf57a 100644 --- a/metadata/modules/docereeBidAdapter.json +++ b/metadata/modules/docereeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:46.086Z", + "timestamp": "2026-03-12T20:11:12.332Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 579ccaef7ac..c845b8efcad 100644 --- a/metadata/modules/dspxBidAdapter.json +++ b/metadata/modules/dspxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/os.json": { - "timestamp": "2026-03-02T14:45:46.087Z", + "timestamp": "2026-03-12T20:11:12.333Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 1410a3e4917..2fcfcf002a3 100644 --- a/metadata/modules/e_volutionBidAdapter.json +++ b/metadata/modules/e_volutionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://e-volution.ai/file.json": { - "timestamp": "2026-03-02T14:45:46.760Z", + "timestamp": "2026-03-12T20:11:13.020Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index 58cc40d3ace..d8a0cdd66f1 100644 --- a/metadata/modules/edge226BidAdapter.json +++ b/metadata/modules/edge226BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.serveteck.com/cdn_storage/tcf/tcf.json?a=1.io": { - "timestamp": "2026-03-02T14:45:47.084Z", + "timestamp": "2026-03-12T20:11:13.364Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index 5b8c1e60d93..5a62cd67cec 100644 --- a/metadata/modules/empowerBidAdapter.json +++ b/metadata/modules/empowerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.empower.net/vendor/vendor.json": { - "timestamp": "2026-03-02T14:45:47.136Z", + "timestamp": "2026-03-12T20:11:13.431Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index a618d1b2dd7..a422499f825 100644 --- a/metadata/modules/equativBidAdapter.json +++ b/metadata/modules/equativBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-02T14:45:47.169Z", + "timestamp": "2026-03-12T20:11:13.509Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 8908f454cef..386a154d268 100644 --- a/metadata/modules/eskimiBidAdapter.json +++ b/metadata/modules/eskimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://dsp-media.eskimi.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:47.201Z", + "timestamp": "2026-03-12T20:11:13.545Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index db71948ec37..fba4142690a 100644 --- a/metadata/modules/etargetBidAdapter.json +++ b/metadata/modules/etargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.etarget.sk/cookies3.json": { - "timestamp": "2026-03-02T14:45:47.226Z", + "timestamp": "2026-03-12T20:11:13.660Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 5d3d333813e..8f0b38df221 100644 --- a/metadata/modules/euidIdSystem.json +++ b/metadata/modules/euidIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-02T14:45:47.806Z", + "timestamp": "2026-03-12T20:11:14.217Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 4f78604a882..9e0dd173878 100644 --- a/metadata/modules/exadsBidAdapter.json +++ b/metadata/modules/exadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.native7.com/tcf/deviceStorage.php": { - "timestamp": "2026-03-02T14:45:48.013Z", + "timestamp": "2026-03-12T20:11:14.418Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index 898c77b0f70..1d8b5b0c310 100644 --- a/metadata/modules/feedadBidAdapter.json +++ b/metadata/modules/feedadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.feedad.com/tcf-device-disclosures.json": { - "timestamp": "2026-03-02T14:45:48.194Z", + "timestamp": "2026-03-12T20:11:14.607Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index c950b152e00..59e2c3ad004 100644 --- a/metadata/modules/fwsspBidAdapter.json +++ b/metadata/modules/fwsspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.fwmrm.net/g/devicedisclosure.json": { - "timestamp": "2026-03-02T14:45:48.313Z", + "timestamp": "2026-03-12T20:11:14.721Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index 05ce430c856..b6f44b5107b 100644 --- a/metadata/modules/gamoshiBidAdapter.json +++ b/metadata/modules/gamoshiBidAdapter.json @@ -1,9 +1,9 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://www.gamoshi.com/disclosures-client-storage.json": { - "timestamp": "2026-03-02T14:45:48.653Z", - "disclosures": null + "https://resources.gamoshi.io/disclosures-client-storage.json": { + "timestamp": "2026-03-12T20:11:15.132Z", + "disclosures": [] } }, "components": [ @@ -12,7 +12,7 @@ "componentName": "gamoshi", "aliasOf": null, "gvlid": 644, - "disclosureURL": "https://www.gamoshi.com/disclosures-client-storage.json" + "disclosureURL": "https://resources.gamoshi.io/disclosures-client-storage.json" }, { "componentType": "bidder", diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 9aa5b49be2d..ff5fd230cdb 100644 --- a/metadata/modules/gemiusIdSystem.json +++ b/metadata/modules/gemiusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-02T14:45:49.814Z", + "timestamp": "2026-03-12T20:11:15.223Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 3ad4e7c7df2..87fc4ec2f4d 100644 --- a/metadata/modules/glomexBidAdapter.json +++ b/metadata/modules/glomexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:49.816Z", + "timestamp": "2026-03-12T20:11:15.225Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 61b5f9f87a3..1164ae6805a 100644 --- a/metadata/modules/goldbachBidAdapter.json +++ b/metadata/modules/goldbachBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gb-next.ch/TcfGoldbachDeviceStorage.json": { - "timestamp": "2026-03-02T14:45:49.839Z", + "timestamp": "2026-03-12T20:11:15.246Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index 0420f51b838..f60dc04e2c5 100644 --- a/metadata/modules/gridBidAdapter.json +++ b/metadata/modules/gridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.themediagrid.com/devicestorage.json": { - "timestamp": "2026-03-02T14:45:49.864Z", + "timestamp": "2026-03-12T20:11:15.267Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 9131fda39bc..c4fd644e3f1 100644 --- a/metadata/modules/gumgumBidAdapter.json +++ b/metadata/modules/gumgumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://marketing.gumgum.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:49.990Z", + "timestamp": "2026-03-12T20:11:15.550Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index a02dd109e19..11184791a95 100644 --- a/metadata/modules/hadronIdSystem.json +++ b/metadata/modules/hadronIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-02T14:45:50.048Z", + "timestamp": "2026-03-12T20:11:15.614Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index b5eb21067e8..ce1bf8d8cef 100644 --- a/metadata/modules/hadronRtdProvider.json +++ b/metadata/modules/hadronRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-02T14:45:50.186Z", + "timestamp": "2026-03-12T20:11:15.773Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json index dc71450537a..6d7129b9598 100644 --- a/metadata/modules/harionBidAdapter.json +++ b/metadata/modules/harionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://markappmedia.site/vendor.json": { - "timestamp": "2026-03-02T14:45:50.186Z", + "timestamp": "2026-03-12T20:11:15.774Z", "disclosures": [] } }, diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index d5fc01d7c33..55a7b2e5424 100644 --- a/metadata/modules/holidBidAdapter.json +++ b/metadata/modules/holidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ads.holid.io/devicestorage.json": { - "timestamp": "2026-03-02T14:45:50.576Z", + "timestamp": "2026-03-12T20:11:16.125Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index efc1583902c..b5baca953a2 100644 --- a/metadata/modules/hybridBidAdapter.json +++ b/metadata/modules/hybridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:50.868Z", + "timestamp": "2026-03-12T20:11:16.400Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index 886832932a7..5a38dec5061 100644 --- a/metadata/modules/id5IdSystem.json +++ b/metadata/modules/id5IdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://id5-sync.com/tcf/disclosures.json": { - "timestamp": "2026-03-02T14:45:51.113Z", + "timestamp": "2026-03-12T20:11:16.606Z", "disclosures": [ { "identifier": "id5id", diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 03bcaedd660..06964ba96f0 100644 --- a/metadata/modules/identityLinkIdSystem.json +++ b/metadata/modules/identityLinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.ats.rlcdn.com/device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:45:51.402Z", + "timestamp": "2026-03-12T20:11:16.894Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index eadb5eea6ca..fd78e53a732 100644 --- a/metadata/modules/illuminBidAdapter.json +++ b/metadata/modules/illuminBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admanmedia.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:51.424Z", + "timestamp": "2026-03-12T20:11:16.911Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index a84d454fd03..35eb971b862 100644 --- a/metadata/modules/impactifyBidAdapter.json +++ b/metadata/modules/impactifyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.impactify.io/tcfvendors.json": { - "timestamp": "2026-03-02T14:45:51.728Z", + "timestamp": "2026-03-12T20:11:17.209Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index a6e1e421e03..cb0958f04f3 100644 --- a/metadata/modules/improvedigitalBidAdapter.json +++ b/metadata/modules/improvedigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-02T14:45:52.052Z", + "timestamp": "2026-03-12T20:11:17.518Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index 818bc7ee34a..26ca9f21313 100644 --- a/metadata/modules/inmobiBidAdapter.json +++ b/metadata/modules/inmobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publisher.inmobi.com/public/disclosure": { - "timestamp": "2026-03-02T14:45:52.052Z", + "timestamp": "2026-03-12T20:11:17.519Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index 93ac9d8e09a..c8a51f7ea7b 100644 --- a/metadata/modules/insticatorBidAdapter.json +++ b/metadata/modules/insticatorBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.insticator.com/iab/device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:45:52.089Z", + "timestamp": "2026-03-12T20:11:17.593Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json index 05a0e236aad..ea09dd5c241 100644 --- a/metadata/modules/insuradsBidAdapter.json +++ b/metadata/modules/insuradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.insurads.com/tcf-vdsod.json": { - "timestamp": "2026-03-02T14:45:52.162Z", + "timestamp": "2026-03-12T20:11:17.626Z", "disclosures": [ { "identifier": "___iat_ses", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 43fe76d2cad..f7c01ea4a5a 100644 --- a/metadata/modules/intentIqIdSystem.json +++ b/metadata/modules/intentIqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://agent.intentiq.com/GDPR/gdpr.json": { - "timestamp": "2026-03-02T14:45:52.351Z", + "timestamp": "2026-03-12T20:11:17.895Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index 9b23ea0bc54..fc83638a37b 100644 --- a/metadata/modules/invibesBidAdapter.json +++ b/metadata/modules/invibesBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.invibes.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:52.421Z", + "timestamp": "2026-03-12T20:11:17.964Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 7f9b4c104c2..736f6281095 100644 --- a/metadata/modules/ipromBidAdapter.json +++ b/metadata/modules/ipromBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://core.iprom.net/info/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:52.785Z", + "timestamp": "2026-03-12T20:11:18.278Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index ba7581331d0..be2026be623 100644 --- a/metadata/modules/ixBidAdapter.json +++ b/metadata/modules/ixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.indexexchange.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:53.259Z", + "timestamp": "2026-03-12T20:11:18.712Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index f20fbceea31..9351b401c5a 100644 --- a/metadata/modules/justIdSystem.json +++ b/metadata/modules/justIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audience-solutions.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:53.334Z", + "timestamp": "2026-03-12T20:11:19.366Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 7fb5438d310..0b309c8ca3a 100644 --- a/metadata/modules/justpremiumBidAdapter.json +++ b/metadata/modules/justpremiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.justpremium.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:53.863Z", + "timestamp": "2026-03-12T20:11:19.885Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 6695015fb1e..f97568d4722 100644 --- a/metadata/modules/jwplayerBidAdapter.json +++ b/metadata/modules/jwplayerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.jwplayer.com/devicestorage.json": { - "timestamp": "2026-03-02T14:45:53.882Z", + "timestamp": "2026-03-12T20:11:19.906Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 18c7713446a..05b15738560 100644 --- a/metadata/modules/kargoBidAdapter.json +++ b/metadata/modules/kargoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://storage.cloud.kargo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:54.165Z", + "timestamp": "2026-03-12T20:11:20.159Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 38316d32257..1b94386e67b 100644 --- a/metadata/modules/kueezRtbBidAdapter.json +++ b/metadata/modules/kueezRtbBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.kueez.com/tcf.json": { - "timestamp": "2026-03-02T14:45:54.193Z", + "timestamp": "2026-03-12T20:11:20.174Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index 14e44dfc1e0..7eae689d1e8 100644 --- a/metadata/modules/limelightDigitalBidAdapter.json +++ b/metadata/modules/limelightDigitalBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://policy.iion.io/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:54.240Z", + "timestamp": "2026-03-12T20:11:20.226Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:45:54.278Z", + "timestamp": "2026-03-12T20:11:20.262Z", "disclosures": [] } }, @@ -129,6 +129,13 @@ "aliasOf": "limelightDigital", "gvlid": null, "disclosureURL": null + }, + { + "componentType": "bidder", + "componentName": "embimedia", + "aliasOf": "limelightDigital", + "gvlid": null, + "disclosureURL": null } ] } \ No newline at end of file diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index 73069893950..5289ecde4f0 100644 --- a/metadata/modules/liveIntentIdSystem.json +++ b/metadata/modules/liveIntentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:54.278Z", + "timestamp": "2026-03-12T20:11:20.262Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index 8c36b5b69d2..6f0fd14b227 100644 --- a/metadata/modules/liveIntentRtdProvider.json +++ b/metadata/modules/liveIntentRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:54.418Z", + "timestamp": "2026-03-12T20:11:20.275Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index a5fb8083be9..b6705c3cd50 100644 --- a/metadata/modules/livewrappedBidAdapter.json +++ b/metadata/modules/livewrappedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://content.lwadm.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:54.419Z", + "timestamp": "2026-03-12T20:11:20.276Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 33632aaca12..28eb3f63796 100644 --- a/metadata/modules/loopmeBidAdapter.json +++ b/metadata/modules/loopmeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://co.loopme.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:54.453Z", + "timestamp": "2026-03-12T20:11:20.296Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 1a84524e9d6..3c8d2d3380a 100644 --- a/metadata/modules/lotamePanoramaIdSystem.json +++ b/metadata/modules/lotamePanoramaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tags.crwdcntrl.net/privacy/tcf-purposes.json": { - "timestamp": "2026-03-02T14:45:54.576Z", + "timestamp": "2026-03-12T20:11:20.406Z", "disclosures": [ { "identifier": "_cc_id", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 96e987d9dc2..499502984fe 100644 --- a/metadata/modules/luponmediaBidAdapter.json +++ b/metadata/modules/luponmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://luponmedia.com/vendor_device_storage.json": { - "timestamp": "2026-03-02T14:45:54.640Z", + "timestamp": "2026-03-12T20:11:20.422Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 096ca78f06b..164856f93a4 100644 --- a/metadata/modules/madvertiseBidAdapter.json +++ b/metadata/modules/madvertiseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.bluestack.app/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:55.051Z", + "timestamp": "2026-03-12T20:11:20.863Z", "disclosures": [] } }, diff --git a/metadata/modules/magniteBidAdapter.json b/metadata/modules/magniteBidAdapter.json new file mode 100644 index 00000000000..55cc4871605 --- /dev/null +++ b/metadata/modules/magniteBidAdapter.json @@ -0,0 +1,18 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { + "timestamp": "2026-03-12T20:11:21.235Z", + "disclosures": [] + } + }, + "components": [ + { + "componentType": "bidder", + "componentName": "magnite", + "aliasOf": null, + "gvlid": 52, + "disclosureURL": "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index e20881844c8..4721d4a929e 100644 --- a/metadata/modules/marsmediaBidAdapter.json +++ b/metadata/modules/marsmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mars.media/apis/tcf-v2.json": { - "timestamp": "2026-03-02T14:45:55.409Z", + "timestamp": "2026-03-12T20:11:21.470Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index 19b9a989b2c..625e6f6294f 100644 --- a/metadata/modules/mediaConsortiumBidAdapter.json +++ b/metadata/modules/mediaConsortiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.hubvisor.io/assets/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:55.518Z", + "timestamp": "2026-03-12T20:11:21.598Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index 43aee7b5d99..41e289b9f64 100644 --- a/metadata/modules/mediaforceBidAdapter.json +++ b/metadata/modules/mediaforceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://comparisons.org/privacy.json": { - "timestamp": "2026-03-02T14:45:55.650Z", + "timestamp": "2026-03-12T20:11:21.746Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index 64dfcf767a5..59801f17cb6 100644 --- a/metadata/modules/mediafuseBidAdapter.json +++ b/metadata/modules/mediafuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:55.669Z", + "timestamp": "2026-03-12T20:11:21.765Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index ffa894357ad..7201be4734f 100644 --- a/metadata/modules/mediagoBidAdapter.json +++ b/metadata/modules/mediagoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.mediago.io/js/tcf.json": { - "timestamp": "2026-03-02T14:45:55.670Z", + "timestamp": "2026-03-12T20:11:21.766Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index c41604e62be..1fe6ac3ca16 100644 --- a/metadata/modules/mediakeysBidAdapter.json +++ b/metadata/modules/mediakeysBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.eu-west-3.amazonaws.com/adserving.resourcekeys.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:55.769Z", + "timestamp": "2026-03-12T20:11:21.845Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index b62853ac937..a5d1a4bfe81 100644 --- a/metadata/modules/medianetBidAdapter.json +++ b/metadata/modules/medianetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.media.net/tcfv2/gvl/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:56.051Z", + "timestamp": "2026-03-12T20:11:22.142Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:56.188Z", + "timestamp": "2026-03-12T20:11:22.201Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index b3c225207bc..340bcb9bcf7 100644 --- a/metadata/modules/mediasquareBidAdapter.json +++ b/metadata/modules/mediasquareBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediasquare.fr/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:56.239Z", + "timestamp": "2026-03-12T20:11:22.264Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index d6da81c2fe5..bfe38b60a6b 100644 --- a/metadata/modules/mgidBidAdapter.json +++ b/metadata/modules/mgidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-02T14:45:56.794Z", + "timestamp": "2026-03-12T20:11:22.768Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index d6dd6937212..fd1898859ff 100644 --- a/metadata/modules/mgidRtdProvider.json +++ b/metadata/modules/mgidRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-02T14:45:56.864Z", + "timestamp": "2026-03-12T20:11:22.871Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index d0da32736ce..e51b39a6b9b 100644 --- a/metadata/modules/mgidXBidAdapter.json +++ b/metadata/modules/mgidXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-02T14:45:56.864Z", + "timestamp": "2026-03-12T20:11:22.871Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 9908f3b78ee..3f03181a705 100644 --- a/metadata/modules/minutemediaBidAdapter.json +++ b/metadata/modules/minutemediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://disclosures.mmctsvc.com/device-storage.json": { - "timestamp": "2026-03-02T14:45:56.865Z", + "timestamp": "2026-03-12T20:11:22.872Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index 14a16812ce4..1a3b9cd10ed 100644 --- a/metadata/modules/missenaBidAdapter.json +++ b/metadata/modules/missenaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.missena.io/iab.json": { - "timestamp": "2026-03-02T14:45:56.886Z", + "timestamp": "2026-03-12T20:11:22.914Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index 1ce8457b058..4c18273a9eb 100644 --- a/metadata/modules/mobianRtdProvider.json +++ b/metadata/modules/mobianRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.outcomes.net/tcf.json": { - "timestamp": "2026-03-02T14:45:56.944Z", + "timestamp": "2026-03-12T20:11:22.968Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 3982ab341b1..530df43fa58 100644 --- a/metadata/modules/mobkoiBidAdapter.json +++ b/metadata/modules/mobkoiBidAdapter.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:57.043Z", - "disclosures": null + "timestamp": "2026-03-12T20:11:22.987Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index 8f9ed0091ef..07e4e43f9b9 100644 --- a/metadata/modules/mobkoiIdSystem.json +++ b/metadata/modules/mobkoiIdSystem.json @@ -2,8 +2,8 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:57.065Z", - "disclosures": null + "timestamp": "2026-03-12T20:11:23.006Z", + "disclosures": [] } }, "components": [ diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index 8ac1b408eae..2d4a38088e8 100644 --- a/metadata/modules/msftBidAdapter.json +++ b/metadata/modules/msftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-02T14:45:57.066Z", + "timestamp": "2026-03-12T20:11:23.006Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index 2a3f0c5c99a..cc14f80cf4a 100644 --- a/metadata/modules/nativeryBidAdapter.json +++ b/metadata/modules/nativeryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnimg.nativery.com/widget/js/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:45:57.067Z", + "timestamp": "2026-03-12T20:11:23.007Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index 59662e64c8a..cd2d8eb4ff0 100644 --- a/metadata/modules/nativoBidAdapter.json +++ b/metadata/modules/nativoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.nativo.com/tcf-disclosures.json": { - "timestamp": "2026-03-02T14:45:57.381Z", + "timestamp": "2026-03-12T20:11:23.341Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index 57fd3223351..cb053bbd510 100644 --- a/metadata/modules/newspassidBidAdapter.json +++ b/metadata/modules/newspassidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-02T14:45:57.429Z", + "timestamp": "2026-03-12T20:11:23.359Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index d57a7a52695..442bc0882fc 100644 --- a/metadata/modules/nextMillenniumBidAdapter.json +++ b/metadata/modules/nextMillenniumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://nextmillennium.io/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:57.430Z", + "timestamp": "2026-03-12T20:11:23.359Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index f366a7a8120..fd1b5b4eada 100644 --- a/metadata/modules/nextrollBidAdapter.json +++ b/metadata/modules/nextrollBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.adroll.com/shares/device_storage.json": { - "timestamp": "2026-03-02T14:45:57.516Z", + "timestamp": "2026-03-12T20:11:23.445Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 00c1ef09267..780447392c6 100644 --- a/metadata/modules/nexx360BidAdapter.json +++ b/metadata/modules/nexx360BidAdapter.json @@ -6,15 +6,15 @@ "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-03-02T14:45:57.804Z", + "timestamp": "2026-03-12T20:11:26.237Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:57.826Z", + "timestamp": "2026-03-12T20:11:26.259Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:45:57.949Z", + "timestamp": "2026-03-12T20:11:26.390Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-03-02T14:45:57.949Z", + "timestamp": "2026-03-12T20:11:26.390Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-03-02T14:45:58.021Z", + "timestamp": "2026-03-12T20:11:26.404Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index 056f7d06193..cb25ac48be7 100644 --- a/metadata/modules/nobidBidAdapter.json +++ b/metadata/modules/nobidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://public.servenobid.com/gdpr_tcf/vendor_device_storage_operational_disclosures.json": { - "timestamp": "2026-03-02T14:45:58.405Z", + "timestamp": "2026-03-12T20:11:26.768Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 3c0a03590fe..1a79c120609 100644 --- a/metadata/modules/nodalsAiRtdProvider.json +++ b/metadata/modules/nodalsAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.nodals.ai/vendor.json": { - "timestamp": "2026-03-02T14:45:58.420Z", + "timestamp": "2026-03-12T20:11:26.782Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 704ad832740..244157c18b0 100644 --- a/metadata/modules/novatiqIdSystem.json +++ b/metadata/modules/novatiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://novatiq.com/privacy/iab/novatiq.json": { - "timestamp": "2026-03-02T14:46:00.252Z", + "timestamp": "2026-03-12T20:11:28.609Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index e0eda9c248a..606ac38059f 100644 --- a/metadata/modules/oguryBidAdapter.json +++ b/metadata/modules/oguryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.ogury.co/disclosure.json": { - "timestamp": "2026-03-02T14:46:00.621Z", + "timestamp": "2026-03-12T20:11:28.957Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index b9cfde98549..b75c033a945 100644 --- a/metadata/modules/omnidexBidAdapter.json +++ b/metadata/modules/omnidexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.omni-dex.io/devicestorage.json": { - "timestamp": "2026-03-02T14:46:00.670Z", + "timestamp": "2026-03-12T20:11:29.014Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index bf3d6e763b9..dbc0d956b44 100644 --- a/metadata/modules/omsBidAdapter.json +++ b/metadata/modules/omsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-02T14:46:00.723Z", + "timestamp": "2026-03-12T20:11:29.092Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 94e1cbede34..a2aa53fa410 100644 --- a/metadata/modules/onetagBidAdapter.json +++ b/metadata/modules/onetagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://onetag-cdn.com/privacy/tcf_storage.json": { - "timestamp": "2026-03-02T14:46:00.724Z", + "timestamp": "2026-03-12T20:11:29.093Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index b4ee396237c..34d9caa88d1 100644 --- a/metadata/modules/openwebBidAdapter.json +++ b/metadata/modules/openwebBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-02T14:46:01.014Z", + "timestamp": "2026-03-12T20:11:29.449Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index dfb10023709..5e5f4609f56 100644 --- a/metadata/modules/openxBidAdapter.json +++ b/metadata/modules/openxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.openx.com/device-storage.json": { - "timestamp": "2026-03-02T14:46:01.052Z", + "timestamp": "2026-03-12T20:11:29.492Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index be92865dfd2..3069740fe52 100644 --- a/metadata/modules/operaadsBidAdapter.json +++ b/metadata/modules/operaadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://res.adx.opera.com/dsd.json": { - "timestamp": "2026-03-02T14:46:01.082Z", + "timestamp": "2026-03-12T20:11:29.614Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 51ed301458d..7ae84f13ed8 100644 --- a/metadata/modules/optidigitalBidAdapter.json +++ b/metadata/modules/optidigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://scripts.opti-digital.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:01.269Z", + "timestamp": "2026-03-12T20:11:29.821Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index fef02111513..77117bb6283 100644 --- a/metadata/modules/optoutBidAdapter.json +++ b/metadata/modules/optoutBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserving.optoutadvertising.com/dsd": { - "timestamp": "2026-03-02T14:46:01.427Z", + "timestamp": "2026-03-12T20:11:29.896Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index b9571894c8a..88dc55bba12 100644 --- a/metadata/modules/orbidderBidAdapter.json +++ b/metadata/modules/orbidderBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://orbidder.otto.de/disclosure/dsd.json": { - "timestamp": "2026-03-02T14:46:01.687Z", + "timestamp": "2026-03-12T20:11:30.155Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 325c70c587c..44bdd3c24cd 100644 --- a/metadata/modules/outbrainBidAdapter.json +++ b/metadata/modules/outbrainBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.outbrain.com/privacy/wp-json/privacy/v2/devicestorage.json": { - "timestamp": "2026-03-02T14:46:02.006Z", + "timestamp": "2026-03-12T20:11:30.739Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index 23d5ad8eb09..f68b2802dee 100644 --- a/metadata/modules/ozoneBidAdapter.json +++ b/metadata/modules/ozoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://prebid.the-ozone-project.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:02.249Z", + "timestamp": "2026-03-12T20:11:30.935Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index d4c804fcc8c..6c7f5c67486 100644 --- a/metadata/modules/pairIdSystem.json +++ b/metadata/modules/pairIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gstatic.com/iabtcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:02.471Z", + "timestamp": "2026-03-12T20:11:31.107Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index e06de639217..93eb698942d 100644 --- a/metadata/modules/panxoBidAdapter.json +++ b/metadata/modules/panxoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.panxo.ai/tcf/device-storage.json": { - "timestamp": "2026-03-02T14:46:02.495Z", + "timestamp": "2026-03-12T20:11:31.127Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index 5bdea8a1dd9..53ca7cb7487 100644 --- a/metadata/modules/performaxBidAdapter.json +++ b/metadata/modules/performaxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.performax.cz/device_storage.json": { - "timestamp": "2026-03-02T14:46:02.691Z", + "timestamp": "2026-03-12T20:11:31.316Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index 647b813dccd..b53e2de5d6f 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-02T14:46:03.107Z", + "timestamp": "2026-03-12T20:11:31.732Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 0f41067e138..b065a04e4a9 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-02T14:46:03.283Z", + "timestamp": "2026-03-12T20:11:31.893Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index c04bfe3f164..e94791a0315 100644 --- a/metadata/modules/pixfutureBidAdapter.json +++ b/metadata/modules/pixfutureBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.pixfuture.com/vendor-disclosures.json": { - "timestamp": "2026-03-02T14:46:03.285Z", + "timestamp": "2026-03-12T20:11:31.895Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index 56ff6f0ebc5..ccfe45d4adf 100644 --- a/metadata/modules/playdigoBidAdapter.json +++ b/metadata/modules/playdigoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://playdigo.com/file.json": { - "timestamp": "2026-03-02T14:46:03.345Z", + "timestamp": "2026-03-12T20:11:31.948Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 3ef5aaa70c7..a50a8a98e53 100644 --- a/metadata/modules/prebid-core.json +++ b/metadata/modules/prebid-core.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json": { - "timestamp": "2026-03-02T14:44:46.315Z", + "timestamp": "2026-03-12T20:07:50.563Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-02T14:44:46.315Z", + "timestamp": "2026-03-12T20:07:50.563Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index 63ae55fec56..2c6a845c71b 100644 --- a/metadata/modules/precisoBidAdapter.json +++ b/metadata/modules/precisoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://preciso.net/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:03.527Z", + "timestamp": "2026-03-12T20:11:32.121Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index e88a5f370ba..dfb2b79b7fa 100644 --- a/metadata/modules/programmaticXBidAdapter.json +++ b/metadata/modules/programmaticXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://progrtb.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-02T14:46:03.818Z", + "timestamp": "2026-03-12T20:11:32.346Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 905a96a3b49..547aec36e91 100644 --- a/metadata/modules/proxistoreBidAdapter.json +++ b/metadata/modules/proxistoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://abs.proxistore.com/assets/json/proxistore_device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:46:03.882Z", + "timestamp": "2026-03-12T20:11:32.436Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index 7ad66a8914c..23e4ed5202c 100644 --- a/metadata/modules/publinkIdSystem.json +++ b/metadata/modules/publinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:46:04.343Z", + "timestamp": "2026-03-12T20:11:32.973Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index 7dc64f1d033..87e63de47a8 100644 --- a/metadata/modules/pubmaticBidAdapter.json +++ b/metadata/modules/pubmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-02T14:46:04.343Z", + "timestamp": "2026-03-12T20:11:32.974Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 2545e56212f..2349c934fb1 100644 --- a/metadata/modules/pubmaticIdSystem.json +++ b/metadata/modules/pubmaticIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-02T14:46:04.569Z", + "timestamp": "2026-03-12T20:11:33.242Z", "disclosures": [] } }, diff --git a/metadata/modules/pubstackBidAdapter.json b/metadata/modules/pubstackBidAdapter.json index 5081e22a00d..aa1ddf64183 100644 --- a/metadata/modules/pubstackBidAdapter.json +++ b/metadata/modules/pubstackBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json": { - "timestamp": "2026-03-02T14:46:04.602Z", + "timestamp": "2026-03-12T20:11:33.275Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index 3134a58466f..6eef450f1d9 100644 --- a/metadata/modules/pulsepointBidAdapter.json +++ b/metadata/modules/pulsepointBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bh.contextweb.com/tcf/vendorInfo.json": { - "timestamp": "2026-03-02T14:46:04.603Z", + "timestamp": "2026-03-12T20:11:33.276Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index 0b8de794a6c..2090cda7418 100644 --- a/metadata/modules/quantcastBidAdapter.json +++ b/metadata/modules/quantcastBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2026-03-02T14:46:04.619Z", + "timestamp": "2026-03-12T20:11:33.292Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json index 0ac7d60db4a..2febdb304ad 100644 --- a/metadata/modules/quantcastIdSystem.json +++ b/metadata/modules/quantcastIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2026-03-02T14:46:04.798Z", + "timestamp": "2026-03-12T20:11:33.482Z", "disclosures": [ { "identifier": "__qca", diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index 673069e3845..3c4c80f82bb 100644 --- a/metadata/modules/r2b2BidAdapter.json +++ b/metadata/modules/r2b2BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.r2b2.io/cookie_disclosure": { - "timestamp": "2026-03-02T14:46:04.800Z", + "timestamp": "2026-03-12T20:11:33.485Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 752bc1a2f76..6274ad4e666 100644 --- a/metadata/modules/readpeakBidAdapter.json +++ b/metadata/modules/readpeakBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.readpeak.com/tcf/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:05.271Z", + "timestamp": "2026-03-12T20:11:33.949Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index 622cc457012..4d43e803ec1 100644 --- a/metadata/modules/relayBidAdapter.json +++ b/metadata/modules/relayBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://relay42.com/hubfs/raw_assets/public/IAB.json": { - "timestamp": "2026-03-02T14:46:05.294Z", + "timestamp": "2026-03-12T20:11:34.069Z", "disclosures": null } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index 5addfe8ad58..e0a8261fec7 100644 --- a/metadata/modules/relevantdigitalBidAdapter.json +++ b/metadata/modules/relevantdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.relevant-digital.com/resources/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:06.100Z", + "timestamp": "2026-03-12T20:11:34.965Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index dd359941cee..f964e2ead56 100644 --- a/metadata/modules/resetdigitalBidAdapter.json +++ b/metadata/modules/resetdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resetdigital.co/GDPR-TCF.json": { - "timestamp": "2026-03-02T14:46:06.261Z", + "timestamp": "2026-03-12T20:11:35.258Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index 37dc05e23e9..07a704af4c8 100644 --- a/metadata/modules/responsiveAdsBidAdapter.json +++ b/metadata/modules/responsiveAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publish.responsiveads.com/tcf/tcf-v2.json": { - "timestamp": "2026-03-02T14:46:06.304Z", + "timestamp": "2026-03-12T20:11:35.297Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index 4c36e64fbd2..f41c650d5ec 100644 --- a/metadata/modules/revcontentBidAdapter.json +++ b/metadata/modules/revcontentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sothebys.revcontent.com/static/device_storage.json": { - "timestamp": "2026-03-02T14:46:06.345Z", + "timestamp": "2026-03-12T20:11:35.416Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 6a2c2b0b465..c5f38bf839d 100644 --- a/metadata/modules/revnewBidAdapter.json +++ b/metadata/modules/revnewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediafuse.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:06.391Z", + "timestamp": "2026-03-12T20:11:35.434Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index b15321eabee..e38b166c978 100644 --- a/metadata/modules/rhythmoneBidAdapter.json +++ b/metadata/modules/rhythmoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:06.448Z", + "timestamp": "2026-03-12T20:11:35.509Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index 10611865e4a..5ff6b446c54 100644 --- a/metadata/modules/richaudienceBidAdapter.json +++ b/metadata/modules/richaudienceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnj.richaudience.com/52a26ab9400b2a9f5aabfa20acf3196g.json": { - "timestamp": "2026-03-02T14:46:06.805Z", + "timestamp": "2026-03-12T20:11:35.743Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index 3891bd224cb..70083f7a8ca 100644 --- a/metadata/modules/riseBidAdapter.json +++ b/metadata/modules/riseBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d2pm7iglz0b6eq.cloudfront.net/RiseDeviceStorage.json": { - "timestamp": "2026-03-02T14:46:06.861Z", + "timestamp": "2026-03-12T20:11:35.809Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-02T14:46:06.861Z", + "timestamp": "2026-03-12T20:11:35.809Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 980500fae02..fd34d74beb1 100644 --- a/metadata/modules/rixengineBidAdapter.json +++ b/metadata/modules/rixengineBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.algorix.co/gdpr-disclosure.json": { - "timestamp": "2026-03-02T14:46:06.862Z", + "timestamp": "2026-03-12T20:11:35.809Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index 31714df7b2e..4266e327a99 100644 --- a/metadata/modules/rtbhouseBidAdapter.json +++ b/metadata/modules/rtbhouseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://rtbhouse.com/DeviceStorage.json": { - "timestamp": "2026-03-02T14:46:06.887Z", + "timestamp": "2026-03-12T20:11:35.832Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index 68ecbd8f4a9..95d716a4111 100644 --- a/metadata/modules/rubiconBidAdapter.json +++ b/metadata/modules/rubiconBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2026-03-02T14:46:07.020Z", + "timestamp": "2026-03-12T20:11:36.035Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index b39b894bd9f..79f28b667db 100644 --- a/metadata/modules/scaliburBidAdapter.json +++ b/metadata/modules/scaliburBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://legal.overwolf.com/docs/overwolf/website/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:07.254Z", + "timestamp": "2026-03-12T20:11:36.036Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 1772818cedc..b68b2cc41a9 100644 --- a/metadata/modules/screencoreBidAdapter.json +++ b/metadata/modules/screencoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://screencore.io/tcf.json": { - "timestamp": "2026-03-02T14:46:07.274Z", + "timestamp": "2026-03-12T20:11:36.052Z", "disclosures": [] } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index 801af2fb55e..81094252aa2 100644 --- a/metadata/modules/seedingAllianceBidAdapter.json +++ b/metadata/modules/seedingAllianceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.nativendo.de/cdn/asset/tcf/purpose-specific-storage-and-access-information.json": { - "timestamp": "2026-03-02T14:46:07.339Z", + "timestamp": "2026-03-12T20:11:36.091Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index 3ad01be8a3a..f8b40063651 100644 --- a/metadata/modules/seedtagBidAdapter.json +++ b/metadata/modules/seedtagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-02T14:46:07.366Z", + "timestamp": "2026-03-12T20:11:36.190Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index 296ab8e1865..24037259a84 100644 --- a/metadata/modules/semantiqRtdProvider.json +++ b/metadata/modules/semantiqRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-02T14:46:07.366Z", + "timestamp": "2026-03-12T20:11:36.190Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 4326d208d40..3548db73da5 100644 --- a/metadata/modules/sevioBidAdapter.json +++ b/metadata/modules/sevioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sevio.com/tcf.json": { - "timestamp": "2026-03-02T14:46:07.609Z", + "timestamp": "2026-03-12T20:11:36.338Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index cda116dbd54..d15a47b9708 100644 --- a/metadata/modules/sharedIdSystem.json +++ b/metadata/modules/sharedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-02T14:46:07.758Z", + "timestamp": "2026-03-12T20:11:36.485Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 8c2ce012b1d..8b44c7457fa 100644 --- a/metadata/modules/sharethroughBidAdapter.json +++ b/metadata/modules/sharethroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.sharethrough.com/gvl.json": { - "timestamp": "2026-03-02T14:46:07.758Z", + "timestamp": "2026-03-12T20:11:36.486Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index 9abcf1c23b4..724df8ea8f2 100644 --- a/metadata/modules/showheroes-bsBidAdapter.json +++ b/metadata/modules/showheroes-bsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static-origin.showheroes.com/gvl_storage_disclosure.json": { - "timestamp": "2026-03-02T14:46:07.777Z", + "timestamp": "2026-03-12T20:11:36.512Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index c5aa4e9afcb..12c18df10a7 100644 --- a/metadata/modules/silvermobBidAdapter.json +++ b/metadata/modules/silvermobBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://silvermob.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:08.247Z", + "timestamp": "2026-03-12T20:11:36.973Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index 348ce7f9fd4..754925f0047 100644 --- a/metadata/modules/sirdataRtdProvider.json +++ b/metadata/modules/sirdataRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sirdata.eu/sirdata_device_storage_disclosure.json": { - "timestamp": "2026-03-02T14:46:08.262Z", + "timestamp": "2026-03-12T20:11:36.987Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index 413fb0f4ca3..d5ed1d7ce7b 100644 --- a/metadata/modules/smaatoBidAdapter.json +++ b/metadata/modules/smaatoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.smaato.com/hubfs/Smaato/IAB/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:08.585Z", + "timestamp": "2026-03-12T20:11:37.372Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index a1c3212113e..b853fbe9811 100644 --- a/metadata/modules/smartadserverBidAdapter.json +++ b/metadata/modules/smartadserverBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-02T14:46:08.665Z", + "timestamp": "2026-03-12T20:11:37.465Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 06854dd00c3..400cd24ff4a 100644 --- a/metadata/modules/smartxBidAdapter.json +++ b/metadata/modules/smartxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.smartclip.net/iab/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:08.666Z", + "timestamp": "2026-03-12T20:11:37.466Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index 202d8317bad..aebee2b3d91 100644 --- a/metadata/modules/smartyadsBidAdapter.json +++ b/metadata/modules/smartyadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smartyads.com/tcf.json": { - "timestamp": "2026-03-02T14:46:08.685Z", + "timestamp": "2026-03-12T20:11:37.486Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index 7b14099448a..b33a727f620 100644 --- a/metadata/modules/smilewantedBidAdapter.json +++ b/metadata/modules/smilewantedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smilewanted.com/vendor-device-storage-disclosures.json": { - "timestamp": "2026-03-02T14:46:08.723Z", + "timestamp": "2026-03-12T20:11:37.530Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index ef1115d5554..4bc40278ffa 100644 --- a/metadata/modules/snigelBidAdapter.json +++ b/metadata/modules/snigelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.snigelweb.com/gvl/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:09.181Z", + "timestamp": "2026-03-12T20:11:37.995Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index b75cfe08e7c..b766c46d051 100644 --- a/metadata/modules/sonaradsBidAdapter.json +++ b/metadata/modules/sonaradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bridgeupp.com/device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:46:09.233Z", + "timestamp": "2026-03-12T20:11:38.038Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index 6ebb47d3e03..ed56986d707 100644 --- a/metadata/modules/sonobiBidAdapter.json +++ b/metadata/modules/sonobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sonobi.com/tcf2-device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:46:09.460Z", + "timestamp": "2026-03-12T20:11:38.270Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index 0abfb9c0b85..98c099901de 100644 --- a/metadata/modules/sovrnBidAdapter.json +++ b/metadata/modules/sovrnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sovrn.com/tcf-cookie-disclosure/disclosure.json": { - "timestamp": "2026-03-02T14:46:09.690Z", + "timestamp": "2026-03-12T20:11:38.860Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index b89e3d7a3d7..2c52fe3f093 100644 --- a/metadata/modules/sparteoBidAdapter.json +++ b/metadata/modules/sparteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:09.709Z", + "timestamp": "2026-03-12T20:11:38.883Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index be24aefdf2e..f20c3fa3ddc 100644 --- a/metadata/modules/ssmasBidAdapter.json +++ b/metadata/modules/ssmasBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://semseoymas.com/iab.json": { - "timestamp": "2026-03-02T14:46:09.989Z", + "timestamp": "2026-03-12T20:11:39.155Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index a99dc0c8ea5..8c5d18e38f7 100644 --- a/metadata/modules/sspBCBidAdapter.json +++ b/metadata/modules/sspBCBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:10.636Z", + "timestamp": "2026-03-12T20:11:39.803Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index 6a53c79a6f2..4cf9b9fff76 100644 --- a/metadata/modules/stackadaptBidAdapter.json +++ b/metadata/modules/stackadaptBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.amazonaws.com/stackadapt_public/disclosures.json": { - "timestamp": "2026-03-02T14:46:10.637Z", + "timestamp": "2026-03-12T20:11:39.806Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index 62dadaf9188..05caa7e39b2 100644 --- a/metadata/modules/startioBidAdapter.json +++ b/metadata/modules/startioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://info.startappservice.com/tcf/start.io_domains.json": { - "timestamp": "2026-03-02T14:46:10.672Z", + "timestamp": "2026-03-12T20:11:39.842Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index 4830347118e..9d7db402d7f 100644 --- a/metadata/modules/stroeerCoreBidAdapter.json +++ b/metadata/modules/stroeerCoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.stroeer.de/StroeerSSP_deviceStorage.json": { - "timestamp": "2026-03-02T14:46:10.688Z", + "timestamp": "2026-03-12T20:11:39.866Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 0409bb9984d..caaeb30d31d 100644 --- a/metadata/modules/stvBidAdapter.json +++ b/metadata/modules/stvBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/stv.json": { - "timestamp": "2026-03-02T14:46:11.041Z", + "timestamp": "2026-03-12T20:11:40.103Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index babdae509ce..c695270e0bf 100644 --- a/metadata/modules/sublimeBidAdapter.json +++ b/metadata/modules/sublimeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.ayads.co/cookiepolicy.json": { - "timestamp": "2026-03-02T14:46:11.678Z", + "timestamp": "2026-03-12T20:11:40.754Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index c829cd46687..9c84128f44f 100644 --- a/metadata/modules/taboolaBidAdapter.json +++ b/metadata/modules/taboolaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-02T14:46:11.938Z", + "timestamp": "2026-03-12T20:11:41.026Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 6fcf6a5ec28..91fc868c67e 100644 --- a/metadata/modules/taboolaIdSystem.json +++ b/metadata/modules/taboolaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-02T14:46:12.149Z", + "timestamp": "2026-03-12T20:11:41.237Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index 35fb4310c9c..7d1f6de3040 100644 --- a/metadata/modules/tadvertisingBidAdapter.json +++ b/metadata/modules/tadvertisingBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:12.150Z", + "timestamp": "2026-03-12T20:11:41.238Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index 6a1843cea0d..590ceffbc8e 100644 --- a/metadata/modules/tappxBidAdapter.json +++ b/metadata/modules/tappxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tappx.com/devicestorage.json": { - "timestamp": "2026-03-02T14:46:12.408Z", + "timestamp": "2026-03-12T20:11:41.356Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index d785b33951a..29cbb932764 100644 --- a/metadata/modules/targetVideoBidAdapter.json +++ b/metadata/modules/targetVideoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-02T14:46:12.436Z", + "timestamp": "2026-03-12T20:11:41.388Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 669db4c09d6..94e93dcba32 100644 --- a/metadata/modules/teadsBidAdapter.json +++ b/metadata/modules/teadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:12.437Z", + "timestamp": "2026-03-12T20:11:41.389Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index a84486229b0..3a60f771278 100644 --- a/metadata/modules/teadsIdSystem.json +++ b/metadata/modules/teadsIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:12.457Z", + "timestamp": "2026-03-12T20:11:41.405Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index fb6a14599bd..14358d19951 100644 --- a/metadata/modules/tealBidAdapter.json +++ b/metadata/modules/tealBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://c.bids.ws/iab/disclosures.json": { - "timestamp": "2026-03-02T14:46:12.457Z", + "timestamp": "2026-03-12T20:11:41.405Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index dd50f1b4493..504abd41eff 100644 --- a/metadata/modules/tncIdSystem.json +++ b/metadata/modules/tncIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.tncid.app/iab-tcf-device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:46:12.515Z", + "timestamp": "2026-03-12T20:11:41.441Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index e4683b84727..4b6225eb212 100644 --- a/metadata/modules/topicsFpdModule.json +++ b/metadata/modules/topicsFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/topicsFpdModule.json": { - "timestamp": "2026-03-02T14:44:46.316Z", + "timestamp": "2026-03-12T20:07:50.563Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index 719b4868651..988adcf8143 100644 --- a/metadata/modules/toponBidAdapter.json +++ b/metadata/modules/toponBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json": { - "timestamp": "2026-03-02T14:46:12.534Z", + "timestamp": "2026-03-12T20:11:41.459Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index ae8f461ead0..0ce178689e5 100644 --- a/metadata/modules/tripleliftBidAdapter.json +++ b/metadata/modules/tripleliftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://triplelift.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:12.661Z", + "timestamp": "2026-03-12T20:11:41.513Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index 825c0388f7a..037eeef5100 100644 --- a/metadata/modules/ttdBidAdapter.json +++ b/metadata/modules/ttdBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-02T14:46:12.692Z", + "timestamp": "2026-03-12T20:11:41.579Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index fc4095423c5..ab2d9b4db60 100644 --- a/metadata/modules/twistDigitalBidAdapter.json +++ b/metadata/modules/twistDigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://twistdigital.net/iab.json": { - "timestamp": "2026-03-02T14:46:12.692Z", + "timestamp": "2026-03-12T20:11:41.580Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index dbd497898d7..e7ce67005c2 100644 --- a/metadata/modules/underdogmediaBidAdapter.json +++ b/metadata/modules/underdogmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.underdog.media/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:12.772Z", + "timestamp": "2026-03-12T20:11:41.653Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index 0b02483de93..9bca4b7519d 100644 --- a/metadata/modules/undertoneBidAdapter.json +++ b/metadata/modules/undertoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.undertone.com/js/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:12.790Z", + "timestamp": "2026-03-12T20:11:41.673Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index cc27f390b4d..dce29c2e63d 100644 --- a/metadata/modules/unifiedIdSystem.json +++ b/metadata/modules/unifiedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-02T14:46:12.878Z", + "timestamp": "2026-03-12T20:11:41.687Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index 5d00220a7b8..2fb06c3d405 100644 --- a/metadata/modules/unrulyBidAdapter.json +++ b/metadata/modules/unrulyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:12.878Z", + "timestamp": "2026-03-12T20:11:41.688Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index a7288064da5..7c95aa4a6b2 100644 --- a/metadata/modules/userId.json +++ b/metadata/modules/userId.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/userId-optout.json": { - "timestamp": "2026-03-02T14:44:46.317Z", + "timestamp": "2026-03-12T20:07:50.565Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 6e7e629b0ac..cca2a9f1ea2 100644 --- a/metadata/modules/utiqIdSystem.json +++ b/metadata/modules/utiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:12.878Z", + "timestamp": "2026-03-12T20:11:41.689Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index 8e9e76248b8..ea36b41af13 100644 --- a/metadata/modules/utiqMtpIdSystem.json +++ b/metadata/modules/utiqMtpIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:12.879Z", + "timestamp": "2026-03-12T20:11:41.690Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 05f5921394d..7d108a1a057 100644 --- a/metadata/modules/validationFpdModule.json +++ b/metadata/modules/validationFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-02T14:44:46.316Z", + "timestamp": "2026-03-12T20:07:50.564Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index 4bcb9939955..3cd263b0d34 100644 --- a/metadata/modules/valuadBidAdapter.json +++ b/metadata/modules/valuadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.valuad.cloud/tcfdevice.json": { - "timestamp": "2026-03-02T14:46:12.879Z", + "timestamp": "2026-03-12T20:11:41.690Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index d2fee191de4..4570e2a3edf 100644 --- a/metadata/modules/vidazooBidAdapter.json +++ b/metadata/modules/vidazooBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidazoo.com/gdpr-tcf/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:13.094Z", + "timestamp": "2026-03-12T20:11:41.879Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index 4902540cf3f..c6797679e03 100644 --- a/metadata/modules/vidoomyBidAdapter.json +++ b/metadata/modules/vidoomyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidoomy.com/storageurl/devicestoragediscurl.json": { - "timestamp": "2026-03-02T14:46:13.172Z", + "timestamp": "2026-03-12T20:11:41.962Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index c26728473ea..a7d7c3a9cd8 100644 --- a/metadata/modules/viouslyBidAdapter.json +++ b/metadata/modules/viouslyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:18.010Z", + "timestamp": "2026-03-12T20:11:42.666Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index de208d4fa21..7abfa31481d 100644 --- a/metadata/modules/visxBidAdapter.json +++ b/metadata/modules/visxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.yoc.com/visx/sellers/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:18.011Z", + "timestamp": "2026-03-12T20:11:42.669Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index a8a8be09eb6..66bef133fbb 100644 --- a/metadata/modules/vlybyBidAdapter.json +++ b/metadata/modules/vlybyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vlyby.com/conf/iab/gvl.json": { - "timestamp": "2026-03-02T14:46:18.324Z", + "timestamp": "2026-03-12T20:11:42.975Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index 71d1adacb65..525a2118d6c 100644 --- a/metadata/modules/voxBidAdapter.json +++ b/metadata/modules/voxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:18.645Z", + "timestamp": "2026-03-12T20:11:43.258Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 492cc4a9c29..df26ce38481 100644 --- a/metadata/modules/vrtcalBidAdapter.json +++ b/metadata/modules/vrtcalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vrtcal.com/docs/gdpr-tcf-disclosures.json": { - "timestamp": "2026-03-02T14:46:18.645Z", + "timestamp": "2026-03-12T20:11:43.265Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 9ff99596a87..1028e338b79 100644 --- a/metadata/modules/vuukleBidAdapter.json +++ b/metadata/modules/vuukleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vuukle.com/data-privacy/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:18.662Z", + "timestamp": "2026-03-12T20:11:43.284Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 3d5c7c23945..0963d9026be 100644 --- a/metadata/modules/weboramaRtdProvider.json +++ b/metadata/modules/weboramaRtdProvider.json @@ -1,8 +1,8 @@ { "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { - "https://weborama.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:18.964Z", + "https://cstatic.weborama.fr/tcf/deviceStorage.json": { + "timestamp": "2026-03-12T20:11:43.566Z", "disclosures": [] } }, @@ -11,7 +11,7 @@ "componentType": "rtd", "componentName": "weborama", "gvlid": 284, - "disclosureURL": "https://weborama.com/deviceStorage.json" + "disclosureURL": "https://cstatic.weborama.fr/tcf/deviceStorage.json" } ] } \ No newline at end of file diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index f6fe924992e..cb2e266c374 100644 --- a/metadata/modules/welectBidAdapter.json +++ b/metadata/modules/welectBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.welect.de/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:19.465Z", + "timestamp": "2026-03-12T20:11:43.736Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index 214ac134bbe..95b9e950f9b 100644 --- a/metadata/modules/yahooAdsBidAdapter.json +++ b/metadata/modules/yahooAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-02T14:46:19.840Z", + "timestamp": "2026-03-12T20:11:44.115Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json index b78e431d9c6..e089bceedc2 100644 --- a/metadata/modules/yaleoBidAdapter.json +++ b/metadata/modules/yaleoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-02T14:46:19.841Z", + "timestamp": "2026-03-12T20:11:44.118Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index e0f3e660ae6..7cdbb1f26e5 100644 --- a/metadata/modules/yieldloveBidAdapter.json +++ b/metadata/modules/yieldloveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn-a.yieldlove.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:20.279Z", + "timestamp": "2026-03-12T20:11:45.208Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index ce311353d52..b7c1e714517 100644 --- a/metadata/modules/yieldmoBidAdapter.json +++ b/metadata/modules/yieldmoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://devicestoragedisclosureurl.yieldmo.com/deviceStorage.json": { - "timestamp": "2026-03-02T14:46:20.302Z", + "timestamp": "2026-03-12T20:11:45.243Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 3f9dfb1b424..68c20adb254 100644 --- a/metadata/modules/zeotapIdPlusIdSystem.json +++ b/metadata/modules/zeotapIdPlusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spl.zeotap.com/assets/iab-disclosure.json": { - "timestamp": "2026-03-02T14:46:20.390Z", + "timestamp": "2026-03-12T20:11:45.345Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index f215e1e0463..c2404c7d89b 100644 --- a/metadata/modules/zeta_globalBidAdapter.json +++ b/metadata/modules/zeta_globalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:20.525Z", + "timestamp": "2026-03-12T20:11:45.463Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index 517fd76fb4c..60a59e3cc1f 100644 --- a/metadata/modules/zeta_global_sspBidAdapter.json +++ b/metadata/modules/zeta_global_sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-02T14:46:20.656Z", + "timestamp": "2026-03-12T20:11:45.550Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index 85fc646f8bf..9b4d0cd9a83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.28.0-pre", + "version": "10.28.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.28.0-pre", + "version": "10.28.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", @@ -7429,9 +7429,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==", "bin": { "baseline-browser-mapping": "dist/cli.cjs" }, @@ -7869,9 +7869,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001775", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", - "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==", "funding": [ { "type": "opencollective", @@ -27000,9 +27000,9 @@ "dev": true }, "baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==" + "version": "2.10.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.7.tgz", + "integrity": "sha512-1ghYO3HnxGec0TCGBXiDLVns4eCSx4zJpxnHrlqFQajmhfKMQBzUGDdkMK7fUW7PTHTeLf+j87aTuKuuwWzMGw==" }, "basic-auth": { "version": "2.0.1", @@ -27298,9 +27298,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001775", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", - "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==" + "version": "1.0.30001778", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001778.tgz", + "integrity": "sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==" }, "chai": { "version": "4.4.1", diff --git a/package.json b/package.json index d32c77e681c..0d2e1343f2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.28.0-pre", + "version": "10.28.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 415eed014a973cf124994e80bafadf58447a82b2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Mar 2026 20:12:57 +0000 Subject: [PATCH 14/50] Increment version to 10.28.1-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b4d0cd9a83..a34b76e84fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.28.0", + "version": "10.28.1-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.28.0", + "version": "10.28.1-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 0d2e1343f2a..ab91b85026c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.28.0", + "version": "10.28.1-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From ab366223297eeb580015cd209e959470b646d7bc Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 12 Mar 2026 21:31:14 +0100 Subject: [PATCH 15/50] Core: bid targeting exclusion (#14453) * Core: bid targeting exclusion * passing filteredBids to exclusion function * Add disableFingerprintingApis option in config Added option to disable specific fingerprinting APIs. * Update config.ts * handling exceptions * Update bidTargetingExclusion documentation Clarify the description of bidTargetingExclusion function. --------- Co-authored-by: Demetrio Girardi Co-authored-by: Patrick McCann --- src/config.ts | 7 +++ src/targeting.ts | 19 ++++++- test/spec/unit/core/targeting_spec.js | 76 +++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index 827856df0f0..01874d74329 100644 --- a/src/config.ts +++ b/src/config.ts @@ -21,6 +21,7 @@ import type { UserSyncConfig } from "./userSync.ts"; import type { DeepPartial, DeepProperty, DeepPropertyName, TypeOfDeepProperty } from "./types/objects.d.ts"; import type { BidderCode } from "./types/common.d.ts"; import type { ORTBRequest } from "./types/ortb/request.d.ts"; +import { Bid } from './bidfactory.ts'; const DEFAULT_DEBUG = getParameterByName(DEBUG_MODE).toUpperCase() === 'TRUE'; const DEFAULT_BIDDER_TIMEOUT = 3000; @@ -254,6 +255,12 @@ export interface Config { * https://docs.prebid.org/features/firstPartyData.html */ ortb2?: DeepPartial; + /** + * When set, only bids for which this function returns a truthy value are included in setTargeting. + * The function is called with the bid and the initially filtered bids (bidsReceived) that passed adunit, zero cpm, and other filters for comparison purposes within the function. + * Return false to exclude a bid from targeting. + */ + bidTargetingExclusion?: (bid: Bid, bids: Bid[]) => boolean; /** * List of fingerprinting APIs to disable. When an API is listed, the corresponding library * returns a safe default instead of reading the real value. Supported: 'devicepixelratio', 'webdriver', 'resolvedoptions'. diff --git a/src/targeting.ts b/src/targeting.ts index 8b2f44b9a5b..371436e614a 100644 --- a/src/targeting.ts +++ b/src/targeting.ts @@ -513,12 +513,27 @@ export function newTargeting(auctionManager) { const customKeysByUnit = {}; const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals'); - bidsReceived.forEach(bid => { + const bidTargetingExclusion = config.getConfig('bidTargetingExclusion'); + + const initiallyFilteredBids = bidsReceived.filter(bid => { const adUnitIsEligible = adUnitCodes.includes(bid.adUnitCode); const cpmAllowed = bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; const isPreferredDeal = alwaysIncludeDeals && bid.dealId; + return adUnitIsEligible && (isPreferredDeal || cpmAllowed); + }); + + initiallyFilteredBids.forEach(bid => { + let notExcludedByConfig = true; + if (typeof bidTargetingExclusion === 'function') { + try { + notExcludedByConfig = bidTargetingExclusion(bid, initiallyFilteredBids); + } catch (e) { + logWarn(`Error in bidTargetingExclusion function - excluding bid ${bid.bidderCode} [${bid.adUnitCode}]`); + notExcludedByConfig = false; + } + } - if (adUnitIsEligible && (isPreferredDeal || cpmAllowed)) { + if (notExcludedByConfig) { filteredBids.push(bid); Object.keys(bid.adserverTargeting) .filter(getCustomKeys()) diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 3c98b2d181e..28937d70f8b 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1124,6 +1124,82 @@ describe('targeting tests', function () { expect(targeting['/123456/header-bid-tag-0']).to.contain.keys('hb_deal', 'hb_adid', 'hb_bidder'); expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); }); + + describe('bidTargetingExclusion', function () { + it('includes all bids in targeting when bidTargetingExclusion is not set', function () { + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.53'); + }); + + it('includes bid in targeting when bidTargetingExclusion returns true for that bid', function () { + config.setConfig({ + bidTargetingExclusion: (bid) => bid.cpm >= 0.5 + }); + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0']); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.53'); + config.resetConfig(); + }); + + it('excludes bid from targeting when bidTargetingExclusion returns false for that bid', function () { + config.setConfig({ + bidTargetingExclusion: (bid) => bid.adId !== bid1.adId + }); + // Pass bidsReceived so both bid1 and bid2 are in the pool (getBidsReceived() returns only one per bidder per ad unit) + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0'], undefined, bidsReceived); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid2.adId); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.25'); + config.resetConfig(); + }); + + it('excludes all bids for ad unit when bidTargetingExclusion returns false for all', function () { + config.setConfig({ + bidTargetingExclusion: () => false + }); + // Pass bidsReceived so both ad units have bids; all excluded so no winner for either + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0', '/123456/header-bid-tag-1'], undefined, bidsReceived); + expect(targeting).to.contain.key('/123456/header-bid-tag-0'); + expect(targeting).to.contain.key('/123456/header-bid-tag-1'); + expect(targeting['/123456/header-bid-tag-0']).to.not.contain.key('hb_adid'); + expect(targeting['/123456/header-bid-tag-1']).to.not.contain.key('hb_adid'); + config.resetConfig(); + }); + + it('calls bidTargetingExclusion with (bid, initiallyFilteredBids) and uses second argument', function () { + config.setConfig({ + bidTargetingExclusion: (bid, initiallyFilteredBids) => { + const sameUnit = initiallyFilteredBids.filter(b => b.adUnitCode === bid.adUnitCode); + return sameUnit.length > 1; + } + }); + // tag-0 has bid1 and bid2 (2 bids), tag-1 has bid3 only (1 bid) → only tag-0 bids included + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0', '/123456/header-bid-tag-1'], undefined, bidsReceived); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid1.adId); + expect(targeting['/123456/header-bid-tag-1']).to.not.contain.key('hb_adid'); + config.resetConfig(); + }); + + it('excludes bid from targeting when bidTargetingExclusion throws and logs warning', function () { + logWarnStub.resetHistory(); + config.setConfig({ + bidTargetingExclusion: (bid) => { + if (bid.adId === bid1.adId) { + throw new Error('test error'); + } + return true; + } + }); + const targeting = targetingInstance.getAllTargeting(['/123456/header-bid-tag-0'], undefined, bidsReceived); + expect(targeting['/123456/header-bid-tag-0']['hb_adid']).to.equal(bid2.adId); + expect(targeting['/123456/header-bid-tag-0']['hb_pb']).to.equal('0.25'); + expect(logWarnStub.calledOnce).to.be.true; + expect(logWarnStub.firstCall.args[0]).to.include('Error in bidTargetingExclusion function'); + expect(logWarnStub.firstCall.args[0]).to.include('rubicon'); + expect(logWarnStub.firstCall.args[0]).to.include('/123456/header-bid-tag-0'); + config.resetConfig(); + }); + }); }); // end getAllTargeting tests describe('getAllTargeting will work correctly when a hook raises has modified flag in getHighestCpmBidsFromBidPool', function () { From 8be6dfb10665e9c0728c521c6fcc8a1218ba39ef Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 12 Mar 2026 16:31:31 -0400 Subject: [PATCH 16/50] Document common adapter types and references (#14577) Added guidelines for common adapter types and type references. --- AGENTS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index c340fee56ff..3cfbcda98f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,3 +48,12 @@ This file contains instructions for the Codex agent and its friends when working ## Additional context - for additional context on repo history, consult https://github.com/prebid/github-activity-db/blob/main/CLAUDE.md on how to download and access repo history in a database you can search locally. + +## Common adapter types +- When bid adapter changes need shared type references, look in the core source modules first: +- `src/adapters/bidderFactory.js` for bidder registration/build and bidder-spec wiring concepts. +- `src/userSync.js` for user sync interfaces, sync option handling, and sync registration behavior. +- `src/adapterManager.js` for adapter manager orchestration and type usage patterns around bidder lifecycle. +- Prefer importing or mirroring conventions from these modules instead of redefining local ad-hoc shapes. +- Use imported types for id, analytics, and rtd modules as well whenever possible. +- Always define types for public interface to an adapter, eg each bidder parameter. From bd487f3d7881957a075391ded2aa33821daf8b92 Mon Sep 17 00:00:00 2001 From: Parth Shah Date: Fri, 13 Mar 2026 02:02:15 +0530 Subject: [PATCH 17/50] adds badv and bcat support for deepintent bid adapter (#14528) --- modules/deepintentBidAdapter.js | 10 +++++ .../spec/modules/deepintentBidAdapter_spec.js | 37 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 280b6f735c0..0d5d2bb6435 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -98,6 +98,16 @@ export const spec = { deepSetValue(openRtbBidRequest, 'regs.coppa', 1); } + // ortb2 blocking: bcat, badv (with optional params fallback) + const bcat = bidderRequest?.ortb2?.bcat || deepAccess(validBidRequests, '0.params.bcat'); + const badv = bidderRequest?.ortb2?.badv || deepAccess(validBidRequests, '0.params.badv'); + if (isArray(bcat) && bcat.length > 0) { + openRtbBidRequest.bcat = bcat; + } + if (isArray(badv) && badv.length > 0) { + openRtbBidRequest.badv = badv; + } + injectEids(openRtbBidRequest, validBidRequests); return { diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index b14faa92b56..2e33359dada 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -403,6 +403,43 @@ describe('Deepintent adapter', function () { expect(data.regs.coppa).to.equal(1); }); }); + describe('ortb2 blocking (bcat, badv)', function() { + it('should add bcat and badv to payload when bidderRequest.ortb2 has them', function() { + const bidderReq = { + ortb2: { + bcat: ['IAB1', 'IAB2'], + badv: ['example.com'] + } + }; + const bRequest = spec.buildRequests(request, bidderReq); + const data = JSON.parse(bRequest.data); + expect(data.bcat).to.deep.equal(['IAB1', 'IAB2']); + expect(data.badv).to.deep.equal(['example.com']); + }); + it('should not add bcat or badv when bidderRequest.ortb2 does not have them', function() { + const bidderReq = {ortb2: {}}; + const bRequest = spec.buildRequests(request, bidderReq); + const data = JSON.parse(bRequest.data); + expect(data.bcat).to.be.undefined; + expect(data.badv).to.be.undefined; + }); + it('should use params.bcat and params.badv as fallback when ortb2 does not set them', function() { + const requestWithParams = [{ + bidder: 'deepintent', + bidId: 'test-bid-id', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + tagId: '100013', + bcat: ['IAB25'], + badv: ['blocked-advertiser.com'] + } + }]; + const bRequest = spec.buildRequests(requestWithParams); + const data = JSON.parse(bRequest.data); + expect(data.bcat).to.deep.equal(['IAB25']); + expect(data.badv).to.deep.equal(['blocked-advertiser.com']); + }); + }); describe('deals functionality', function() { it('should add PMP deals when valid deals array is provided', function() { const requestWithDeals = [{ From 66e75d8e9e9456ddc2dcdaad0ad729ef545cebfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kv=C3=A1=C4=8Dek?= Date: Thu, 12 Mar 2026 21:33:58 +0100 Subject: [PATCH 18/50] Performax adapter: Add user sync and reporting URLs (#14547) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add user sync and reporting urls * add tests, minor refactor * add window.addEventListener only once * fix JSON.parse can return null * Fix unconditional setting user.ext.uids * Add test * swap uids from storage and original user.ext.uids * Add keepalive and log only when debug is turned on * add stub * Fixed codex issues with json parsing * Fix merge * Fix linter --------- Co-authored-by: Michal Kváček Co-authored-by: Martin Mach --- modules/performaxBidAdapter.js | 156 +++++++- test/spec/modules/performaxBidAdapter_spec.js | 355 +++++++++++++++++- 2 files changed, 509 insertions(+), 2 deletions(-) diff --git a/modules/performaxBidAdapter.js b/modules/performaxBidAdapter.js index 8a7b8569594..04208811f1b 100644 --- a/modules/performaxBidAdapter.js +++ b/modules/performaxBidAdapter.js @@ -1,12 +1,95 @@ -import { deepSetValue, deepAccess } from '../src/utils.js'; +import { logWarn, logError, deepSetValue, deepAccess, safeJSONEncode, debugTurnedOn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; const BIDDER_CODE = 'performax'; const BIDDER_SHORT_CODE = 'px'; const GVLID = 732 const ENDPOINT = 'https://dale.performax.cz/ortb' +const USER_SYNC_URL = 'https://cdn.performax.cz/px2/cookie_sync_bundle.html'; +const USER_SYNC_ORIGIN = 'https://cdn.performax.cz'; +const UIDS_STORAGE_KEY = BIDDER_SHORT_CODE + '_uids'; +const LOG_EVENT_URL = 'https://chip.performax.cz/error'; +const LOG_EVENT_SAMPLE_RATE = 1; +const LOG_EVENT_TYPE_BIDDER_ERROR = 'bidderError'; +const LOG_EVENT_TYPE_INTERVENTION = 'intervention'; +const LOG_EVENT_TYPE_TIMEOUT = 'timeout'; + +let isUserSyncsInit = false; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +/** + * Sends diagnostic events. + * @param {string} type - The category of the event + * @param {Object|Array|string} payload - The data to be logged + * @param {number} [sampleRate=LOG_EVENT_SAMPLE_RATE] - The probability of logging the event + * @returns {void} + */ +function logEvent(type, payload, sampleRate = LOG_EVENT_SAMPLE_RATE) { + if (sampleRate <= Math.random()) { + return; + } + + const data = { type, payload }; + const options = { method: 'POST', withCredentials: true, contentType: 'application/json' }; + + ajax(LOG_EVENT_URL, undefined, safeJSONEncode(data), options); +} + +/** + * Serializes and stores data. + * @param {string} key - The unique identifier + * @param {any} value - The data to store + * @returns {void} + */ +export function storeData(key, value) { + if (!storage.localStorageIsEnabled()) { + if (debugTurnedOn()) logWarn('Local Storage is not enabled'); + return; + } + + try { + storage.setDataInLocalStorage(key, JSON.stringify(value)); + } catch (err) { + logError('Failed to store data: ', err); + } +} + +/** + * Retrieves and parses data. + * @param {string} key - The unique identifier + * @param {any} defaultValue - The value to return if the key is missing or parsing fails. + * @returns {any} The parsed data + */ +export function readData(key, defaultValue) { + if (!storage.localStorageIsEnabled()) { + if (debugTurnedOn()) logWarn('Local Storage is not enabled'); + return defaultValue; + } + + let rawData = storage.getDataFromLocalStorage(key); + + if (rawData === null) { + return defaultValue; + } + + try { + const parsed = JSON.parse(rawData); + return (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : defaultValue; + } catch (err) { + logError(`Error parsing data for key "${key}": `, err); + return defaultValue; + } +} + +export function resetUserSyncsInit() { + isUserSyncsInit = false; +} + export const converter = ortbConverter({ imp(buildImp, bidRequest, context) { @@ -40,6 +123,23 @@ export const spec = { buildRequests: function (bidRequests, bidderRequest) { const data = converter.toORTB({ bidderRequest, bidRequests }) + + const uids = readData(UIDS_STORAGE_KEY, {}); + if (Object.keys(uids).length > 0) { + if (!data.user) { + data.user = {}; + } + + if (!data.user.ext) { + data.user.ext = {}; + } + + data.user.ext.uids = { + ...uids, + ...(data.user.ext.uids ?? {}) + }; + } + return [{ method: 'POST', url: ENDPOINT, @@ -71,7 +171,61 @@ export const spec = { }; return converter.fromORTB({ response: data, request: request.data }).bids }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + const syncs = []; + + if (!syncOptions.iframeEnabled) { + if (debugTurnedOn()) { + logWarn('User sync is supported only via iframe'); + } + return syncs; + } + + let url = USER_SYNC_URL; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `?gdpr_consent=${gdprConsent.consentString}`; + } + } + syncs.push({ + type: 'iframe', + url: url + }); + + if (!isUserSyncsInit) { + window.addEventListener('message', function (event) { + if (!event.data || event.origin !== USER_SYNC_ORIGIN || !event.data.flexo_sync_cookie) { + return; + } + + const { uid, vendor } = event.data.flexo_sync_cookie; + + if (!uid || !vendor) { + return; + } + + const uids = readData(UIDS_STORAGE_KEY, {}); + uids[vendor] = uid; + storeData(UIDS_STORAGE_KEY, uids); + }); + isUserSyncsInit = true; + } + + return syncs; + }, + onTimeout: function(timeoutData) { + logEvent(LOG_EVENT_TYPE_TIMEOUT, timeoutData); + }, + onBidderError: function({ bidderRequest }) { + logEvent(LOG_EVENT_TYPE_BIDDER_ERROR, bidderRequest); + }, + onIntervention: function({ bid }) { + logEvent(LOG_EVENT_TYPE_INTERVENTION, bid); + } } registerBidder(spec); diff --git a/test/spec/modules/performaxBidAdapter_spec.js b/test/spec/modules/performaxBidAdapter_spec.js index e71ac16eb68..f26024eb47d 100644 --- a/test/spec/modules/performaxBidAdapter_spec.js +++ b/test/spec/modules/performaxBidAdapter_spec.js @@ -1,5 +1,8 @@ import { expect } from 'chai'; -import { spec, converter } from 'modules/performaxBidAdapter.js'; +import { spec, converter, storeData, readData, storage, resetUserSyncsInit } from 'modules/performaxBidAdapter.js'; +import * as utils from '../../../src/utils.js'; +import * as ajax from 'src/ajax.js'; +import sinon from 'sinon'; describe('Performax adapter', function () { const bids = [{ @@ -129,6 +132,56 @@ describe('Performax adapter', function () { }) describe('buildRequests', function () { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should inject stored UIDs into user.ext.uids if they exist', function() { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs('px_uids') // BIDDER_SHORT_CODE + '_uids' + .returns(JSON.stringify({ someVendor: '12345' })); + + const requests = spec.buildRequests(bids, bidderRequest); + const data = requests[0].data; + + expect(data.user).to.exist; + expect(data.user.ext).to.exist; + expect(data.user.ext.uids).to.deep.include({ someVendor: '12345' }); + }); + + it('should merge stored UIDs with existing user.ext.uids (preserving existing)', function() { + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs('px_uids') + .returns(JSON.stringify({ storedVendor: 'storedId' })); + + const requestWithUids = { + ...bidderRequest, + ortb2: { + user: { + ext: { + uids: { existingVendor: 'existingId' } + } + } + } + }; + + const requests = spec.buildRequests(bids, requestWithUids); + const data = requests[0].data; + + expect(data.user.ext.uids).to.deep.equal({ + existingVendor: 'existingId', + storedVendor: 'storedId' + }); + }); + it('should set correct request method and url', function () { const requests = spec.buildRequests([bids[0]], bidderRequest); expect(requests).to.be.an('array').that.has.lengthOf(1); @@ -163,6 +216,11 @@ describe('Performax adapter', function () { }); describe('interpretResponse', function () { + it('should return an empty array if the response body is missing', function () { + const result = spec.interpretResponse({}, {}); + expect(result).to.deep.equal([]); + }); + it('should map params correctly', function () { const ortbRequest = { data: converter.toORTB({ bidderRequest, bids }) }; serverResponse.body.id = ortbRequest.data.id; @@ -181,4 +239,299 @@ describe('Performax adapter', function () { expect(bid.creativeId).to.equal('sample'); }); }); + + describe('Storage Helpers', () => { + let sandbox; + let logWarnSpy; + let logErrorSpy; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(storage, 'localStorageIsEnabled'); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage'); + sandbox.stub(utils, 'debugTurnedOn').returns(true); + + logWarnSpy = sandbox.stub(utils, 'logWarn'); + logErrorSpy = sandbox.stub(utils, 'logError'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('storeData', () => { + it('should store serialized data when local storage is enabled', () => { + storage.localStorageIsEnabled.returns(true); + const testData = { foo: 'bar' }; + + storeData('testKey', testData); + + sandbox.assert.calledWithExactly( + storage.setDataInLocalStorage, + 'testKey', + JSON.stringify(testData) + ); + }); + + it('should log a warning and exit if local storage is disabled', () => { + storage.localStorageIsEnabled.returns(false); + + storeData('testKey', { foo: 'bar' }); + + expect(storage.setDataInLocalStorage.called).to.be.false; + sandbox.assert.calledOnce(logWarnSpy); + }); + + it('should log an error if setDataInLocalStorage throws', () => { + storage.localStorageIsEnabled.returns(true); + storage.setDataInLocalStorage.throws(new Error('QuotaExceeded')); + + storeData('testKey', 'someValue'); + + sandbox.assert.calledOnce(logErrorSpy); + }); + }); + + describe('readData', () => { + it('should return parsed data when it exists in storage', () => { + storage.localStorageIsEnabled.returns(true); + const mockValue = { id: 123 }; + storage.getDataFromLocalStorage.withArgs('myKey').returns(JSON.stringify(mockValue)); + + const result = readData('myKey', {}); + + expect(result).to.deep.equal(mockValue); + }); + + it('should return defaultValue if local storage is disabled', () => { + storage.localStorageIsEnabled.returns(false); + const defaultValue = { status: 'default' }; + + const result = readData('myKey', defaultValue); + + expect(result).to.equal(defaultValue); + sandbox.assert.calledOnce(logWarnSpy); + }); + + it('should return defaultValue if the key does not exist (returns null)', () => { + storage.localStorageIsEnabled.returns(true); + storage.getDataFromLocalStorage.returns(null); + + const result = readData('missingKey', 'fallback'); + + expect(result).to.equal('fallback'); + }); + + it('should return defaultValue when stored value is a JSON primitive', () => { + storage.localStorageIsEnabled.returns(true); + const defaultValue = { fallback: true }; + + storage.getDataFromLocalStorage.returns('"hello"'); + expect(readData('k', defaultValue)).to.deep.equal(defaultValue); + + storage.getDataFromLocalStorage.returns('42'); + expect(readData('k', defaultValue)).to.deep.equal(defaultValue); + + storage.getDataFromLocalStorage.returns('true'); + expect(readData('k', defaultValue)).to.deep.equal(defaultValue); + }); + + it('should return defaultValue when stored value is a JSON array', () => { + storage.localStorageIsEnabled.returns(true); + const defaultValue = { fallback: true }; + storage.getDataFromLocalStorage.returns('[1,2]'); + + expect(readData('k', defaultValue)).to.deep.equal(defaultValue); + }); + + it('should return defaultValue and log an error if JSON is malformed', () => { + storage.localStorageIsEnabled.returns(true); + storage.getDataFromLocalStorage.returns('not-valid-json{'); + + const result = readData('badKey', { error: true }); + + expect(result).to.deep.equal({ error: true }); + sandbox.assert.calledOnce(logErrorSpy); + }); + }); + }); + + describe('logging', function () { + let ajaxStub; + let randomStub; + + beforeEach(() => { + ajaxStub = sinon.stub(ajax, 'ajax'); + randomStub = sinon.stub(Math, 'random').returns(0); + }); + + afterEach(() => { + ajaxStub.restore(); + randomStub.restore(); + }); + + it('should call ajax when onTimeout is triggered', function () { + const timeoutData = [{ bidId: '123' }]; + spec.onTimeout(timeoutData); + + expect(ajaxStub.calledOnce).to.be.true; + + const [url, callback, data, options] = ajaxStub.firstCall.args; + const parsedData = JSON.parse(data); + + expect(parsedData.type).to.equal('timeout'); + expect(parsedData.payload).to.deep.equal(timeoutData); + expect(options.method).to.equal('POST'); + }); + + it('should call ajax when onBidderError is triggered', function () { + const errorData = { bidderRequest: { some: 'data' } }; + spec.onBidderError(errorData); + + expect(ajaxStub.calledOnce).to.be.true; + + const [url, callback, data] = ajaxStub.firstCall.args; + const parsedData = JSON.parse(data); + + expect(parsedData.type).to.equal('bidderError'); + expect(parsedData.payload).to.deep.equal(errorData.bidderRequest); + }); + + it('should NOT call ajax if sampling logic fails', function () { + randomStub.returns(1.1); + + spec.onTimeout({}); + expect(ajaxStub.called).to.be.false; + }); + + it('should call ajax with correct type "intervention"', function () { + const bidData = { bidId: 'abc' }; + spec.onIntervention({ bid: bidData }); + + expect(ajaxStub.calledOnce).to.be.true; + const [url, callback, data] = ajaxStub.firstCall.args; + const parsed = JSON.parse(data); + + expect(parsed.type).to.equal('intervention'); + expect(parsed.payload).to.deep.equal(bidData); + }); + }); + + describe('getUserSyncs', function () { + let sandbox; + let logWarnSpy; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'debugTurnedOn').returns(true); + logWarnSpy = sandbox.stub(utils, 'logWarn'); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + resetUserSyncsInit(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return empty array and log warning if iframeEnabled is false', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: false }); + expect(syncs).to.deep.equal([]); + expect(logWarnSpy.calledOnce).to.be.true; + }); + + it('should return correct iframe sync url without GDPR', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true }); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://cdn.performax.cz/px2/cookie_sync_bundle.html'); + }); + + it('should append GDPR params when gdprApplies is a boolean', function () { + const consent = { gdprApplies: true, consentString: 'abc' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], consent); + + expect(syncs[0].url).to.include('?gdpr=1&gdpr_consent=abc'); + }); + + it('should append GDPR params when gdprApplies is undefined/non-boolean', function () { + const consent = { gdprApplies: undefined, consentString: 'abc' }; + const syncs = spec.getUserSyncs({ iframeEnabled: true }, [], consent); + + expect(syncs[0].url).to.include('?gdpr_consent=abc'); + }); + + describe('PostMessage Listener', function () { + it('should store data when valid message is received', function () { + const addEventListenerStub = sandbox.stub(window, 'addEventListener'); + spec.getUserSyncs({ iframeEnabled: true }); + expect(addEventListenerStub.calledWith('message')).to.be.true; + const callback = addEventListenerStub.args.find(arg => arg[0] === 'message')[1]; + + const mockEvent = { + origin: 'https://cdn.performax.cz', + data: { + flexo_sync_cookie: { + uid: 'user123', + vendor: 'vendorXYZ' + } + } + }; + + callback(mockEvent); + + expect(storage.setDataInLocalStorage.calledOnce).to.be.true; + + const [key, value] = storage.setDataInLocalStorage.firstCall.args; + expect(key).to.equal('px_uids'); + expect(JSON.parse(value)).to.deep.equal({ + vendorXYZ: 'user123' + }); + }); + + it('should ignore messages from invalid origins', function () { + const addEventListenerStub = sandbox.stub(window, 'addEventListener'); + spec.getUserSyncs({ iframeEnabled: true }); + + const callback = addEventListenerStub.args.find(arg => arg[0] === 'message')[1]; + + const mockEvent = { + origin: 'https://not.cdn.performax.cz', + data: { flexo_sync_cookie: { uid: '1', vendor: '2' } } + }; + + callback(mockEvent); + + expect(storage.setDataInLocalStorage.called).to.be.false; + }); + + it('should ignore messages with missing structure', function () { + const addEventListenerStub = sandbox.stub(window, 'addEventListener'); + spec.getUserSyncs({ iframeEnabled: true }); + + const callback = addEventListenerStub.args.find(arg => arg[0] === 'message')[1]; + + const mockEvent = { + origin: 'https://cdn.performax.cz', + data: { wrong_key: 123 } // Missing flexo_sync_cookie + }; + + callback(mockEvent); + + expect(storage.setDataInLocalStorage.called).to.be.false; + }); + + it('should not register duplicate listeners on multiple calls', function () { + const addEventListenerStub = sandbox.stub(window, 'addEventListener'); + + spec.getUserSyncs({ iframeEnabled: true }); + expect(addEventListenerStub.calledOnce).to.be.true; + + spec.getUserSyncs({ iframeEnabled: true }); + expect(addEventListenerStub.calledOnce).to.be.true; + }); + }); + }); }); From e512167fbc332c677e794aa93635bb41c6668bca Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 12 Mar 2026 21:36:02 +0100 Subject: [PATCH 19/50] Optout Bid Adapter: migrate endpoints and batch requests with custom params (#14282) * updated optout adapter to include single request, custom params * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * changes for copilot review * more fixes for copilot review * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add more test coverage and improvements to adapter * fix errors * remove duplicate code/refactored code * fixed test case with invalid url * fixed test case with invalid url attempt 2 * remove invalid url check altogether * remove trailing spaces * remove trailing spaces * Update modules/optoutBidAdapter.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/spec/modules/optoutBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/spec/modules/optoutBidAdapter_spec.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- modules/optoutBidAdapter.js | 324 ++++++- test/spec/modules/optoutBidAdapter_spec.js | 946 +++++++++++++++++++-- 2 files changed, 1158 insertions(+), 112 deletions(-) diff --git a/modules/optoutBidAdapter.js b/modules/optoutBidAdapter.js index 0e91112bf49..f338983b584 100644 --- a/modules/optoutBidAdapter.js +++ b/modules/optoutBidAdapter.js @@ -1,78 +1,314 @@ -import { deepAccess } from '../src/utils.js'; +import { deepAccess, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { BANNER } from '../src/mediaTypes.js'; const BIDDER_CODE = 'optout'; const GVLID = 227; +const DEFAULT_TTL = 300; +const DEFAULT_CURRENCY = 'EUR'; +/** + * Sanitizes a URL by removing query parameters and fragments to prevent data leakage + * @param {string} rawUrl - The URL to sanitize + * @returns {string} Sanitized URL (origin + pathname) or empty string if invalid + */ +function sanitizeUrl(rawUrl) { + if (!rawUrl) return ''; + try { + const u = new URL(rawUrl, deepAccess(window, 'location.href')); + // Avoid leaking query params / fragments + const sanitized = `${u.origin}${u.pathname}`; + // Ensure we never return null or undefined as a string + return sanitized && sanitized !== 'null' && sanitized !== 'undefined' ? sanitized : ''; + } catch (e) { + // If it's not a valid URL, return an empty string to avoid leaking potentially sensitive data + logWarn(`${BIDDER_CODE}: Invalid URL provided: ${rawUrl}`); + return ''; + } +} + +/** + * Gets the domain/URL from bidderRequest with fallbacks + * Priority: canonicalUrl > page > window.location.href + * @param {Object} bidderRequest - The bidder request object + * @returns {string} Sanitized domain URL + */ function getDomain(bidderRequest) { - return deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(window, 'location.href'); + const fromCanonical = deepAccess(bidderRequest, 'refererInfo.canonicalUrl'); + if (fromCanonical) return sanitizeUrl(fromCanonical); + + const fromPage = deepAccess(bidderRequest, 'refererInfo.page'); + if (fromPage) return sanitizeUrl(fromPage); + + const href = deepAccess(window, 'location.href'); + return sanitizeUrl(href); } +/** + * Gets currency configuration from Prebid config + * @returns {Object} Currency config object with adServerCurrency and granularityMultiplier + */ function getCurrency() { - let cur = config.getConfig('currency'); - if (cur === undefined) { - cur = { - adServerCurrency: 'EUR', - granularityMultiplier: 1 - }; + const cur = config.getConfig('currency'); + if (!cur) { + return { adServerCurrency: DEFAULT_CURRENCY, granularityMultiplier: 1 }; } return cur; } +/** + * Normalize customs: + * - arrays -> CSV string + * - null/undefined -> removed + * - objects -> JSON string (removed if not serializable / circular) + * - primitives -> String(value) + * + * Returns a new object (never mutates input). + */ +function normalizeCustoms(input) { + if (!input || typeof input !== 'object' || Array.isArray(input)) { + return {}; + } + + const out = Object.assign({}, input); + + Object.entries(out).forEach(([key, value]) => { + if (Array.isArray(value)) { + out[key] = value.join(','); + return; + } + + if (value === null || value === undefined) { + delete out[key]; + return; + } + + if (typeof value === 'object') { + try { + const str = JSON.stringify(value); + if (str === undefined) { + delete out[key]; + } else { + out[key] = str; + } + } catch (e) { + // e.g. circular structure + delete out[key]; + } + return; + } + + out[key] = String(value); + }); + + return out; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, + supportedMediaTypes: [BANNER], - isBidRequestValid: function(bid) { - return !!bid.params.publisher && !!bid.params.adslot; + /** + * Determines if a bid request is valid + * @param {Object} bid - The bid to validate + * @returns {boolean} True if valid, false otherwise + */ + isBidRequestValid: function (bid) { + const params = bid && bid.params; + const adSlot = params && (params.adSlot || params.adslot); + return !!(params && params.publisher && adSlot); }, - buildRequests: function(validBidRequests, bidderRequest) { - return validBidRequests.map(bidRequest => { - let endPoint = 'https://adscience-nocookie.nl/prebid/display'; - let consentString = ''; - let gdpr = 0; - if (bidRequest.gdprConsent) { - gdpr = (typeof bidRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidRequest.gdprConsent.gdprApplies) : 0; - consentString = bidRequest.gdprConsent.consentString; - if (!gdpr || hasPurpose1Consent(bidRequest.gdprConsent)) { - endPoint = 'https://prebid.adscience.nl/prebid/display'; - } + /** + * Builds bid requests from valid bid requests + * @param {Array} validBidRequests - Array of valid bid requests + * @param {Object} bidderRequest - The bidder request object + * @returns {Array} Array containing the bid request object + */ + buildRequests: function (validBidRequests, bidderRequest) { + if (!Array.isArray(validBidRequests) || validBidRequests.length === 0) { + return []; + } + + const firstBid = validBidRequests[0]; + + let endPoint = 'https://prebid.optoutadserving.com/prebid/display'; + + const gdprConsent = + bidderRequest && typeof bidderRequest === 'object' ? bidderRequest.gdprConsent : null; + + let consentString = ''; + let gdpr = 0; + + if (gdprConsent && typeof gdprConsent === 'object') { + gdpr = + typeof gdprConsent.gdprApplies === 'boolean' + ? Number(gdprConsent.gdprApplies) + : 0; + + consentString = gdprConsent.consentString || ''; + + if (!gdpr || hasPurpose1Consent(gdprConsent)) { + endPoint = 'https://prebid.optinadserving.com/prebid/display'; } - return { - method: 'POST', - url: endPoint, - data: { - requestId: bidRequest.bidId, - publisher: bidRequest.params.publisher, - adSlot: bidRequest.params.adslot, - cur: getCurrency(), - url: getDomain(bidRequest), - ortb2: bidderRequest.ortb2, - consent: consentString, - gdpr: gdpr - - }, + } + + const shouldIncludeOrtb2 = validBidRequests.some((b) => !!b?.params?.includeOrtb2); + + const slots = validBidRequests.map((b) => { + const slotCustoms = normalizeCustoms(b?.params?.customs); + const adSlotValue = b.params.adSlot || b.params.adslot; + + const slot = { + adSlot: adSlotValue, + requestId: b.bidId, }; + + // Use explicit id if provided, otherwise use adSlot value + if (b.params && b.params.id != null) { + slot.id = String(b.params.id); + } else { + slot.id = adSlotValue; + } + + if (Object.keys(slotCustoms).length) slot.customs = slotCustoms; + return slot; }); + + const mergedCustoms = Object.assign( + {}, + firstBid?.params?.customs, + bidderRequest?.ortb2?.ext?.data, + bidderRequest?.ortb2?.site?.ext?.data, + bidderRequest?.ortb2?.app?.ext?.data, + bidderRequest?.ortb2?.user?.ext?.data + ); + + const customs = normalizeCustoms(mergedCustoms); + + const data = { + publisher: firstBid.params.publisher, // intentionally uses the first bid's publisher when batching + slots, + cur: getCurrency(), + url: getDomain(bidderRequest), + sdk_version: 'prebid', + consent: consentString, + gdpr + }; + + if (Object.keys(customs).length) data.customs = customs; + + if (shouldIncludeOrtb2 && bidderRequest?.ortb2) { + data.ortb2 = JSON.stringify(bidderRequest.ortb2); + } + + return [ + { + method: 'POST', + url: endPoint, + data + } + ]; }, + /** + * Interprets the server response and returns valid bids + * @param {Object} serverResponse - The server response object + * @param {Object} bidRequest - The original bid request + * @returns {Array} Array of valid bid objects + */ interpretResponse: function (serverResponse, bidRequest) { - return serverResponse.body; + const body = serverResponse?.body; + + const bids = Array.isArray(body) + ? body + : Array.isArray(body?.bids) + ? body.bids + : []; + + const sentSlots = bidRequest?.data?.slots || []; + + const slotIdToPrebidId = new Map( + sentSlots.map((s) => [String(s.id), String(s.requestId)]) + ); + + const prebidIds = new Set(sentSlots.map((s) => String(s.requestId))); + + return bids + .map((bid) => { + // Defensive handling of malformed bids + if (!bid || !bid.requestId) return null; + if (!bid.currency || !bid.ad || bid.width == null || bid.height == null) return null; + + const cpmNum = Number(bid.cpm); + if (bid.cpm == null || Number.isNaN(cpmNum) || cpmNum <= 0) return null; + + const w = Number(bid.width); + const h = Number(bid.height); + if (!Number.isFinite(w) || w <= 0 || !Number.isFinite(h) || h <= 0) return null; + + const serverSlotOrReq = String(bid.requestId); + + const prebidRequestId = prebidIds.has(serverSlotOrReq) + ? serverSlotOrReq + : slotIdToPrebidId.get(serverSlotOrReq); + + if (!prebidRequestId) return null; + + return { + requestId: prebidRequestId, + cpm: cpmNum, + currency: bid.currency, + width: w, + height: h, + ad: bid.ad, + ttl: Number(bid.ttl) || DEFAULT_TTL, + creativeId: bid.creativeId || String(bid.requestId), + netRevenue: true, + optOutExt: bid.optOutExt, + meta: bid.meta + }; + }) + .filter(Boolean); }, + /** + * Returns user sync pixels/iframes based on consent + * @param {Object} syncOptions - Sync options from Prebid + * @param {Array} responses - Server responses + * @param {Object} gdprConsent - GDPR consent data + * @returns {Array} Array of user sync objects + */ getUserSyncs: function (syncOptions, responses, gdprConsent) { - if (gdprConsent) { - const gdpr = (typeof gdprConsent.gdprApplies === 'boolean') ? Number(gdprConsent.gdprApplies) : 0; - if (syncOptions.iframeEnabled && (!gdprConsent.gdprApplies || hasPurpose1Consent(gdprConsent))) { - return [{ + if (!gdprConsent || typeof gdprConsent !== 'object') return []; + + const gdprApplies = typeof gdprConsent.gdprApplies === 'boolean' + ? gdprConsent.gdprApplies + : false; + + const gdpr = gdprApplies ? 1 : 0; + + if ( + syncOptions.iframeEnabled && + (!gdprApplies || hasPurpose1Consent(gdprConsent)) + ) { + return [ + { type: 'iframe', - url: 'https://umframe.adscience.nl/matching/iframe?gdpr=' + gdpr + '&gdpr_consent=' + gdprConsent.consentString - }]; - } + url: + 'https://umframe.optinadserving.com/matching/iframe?gdpr=' + + gdpr + + '&gdpr_consent=' + + encodeURIComponent(gdprConsent.consentString || '') + } + ]; } - }, + + return []; + } }; + registerBidder(spec); diff --git a/test/spec/modules/optoutBidAdapter_spec.js b/test/spec/modules/optoutBidAdapter_spec.js index e92e76fc39d..886d2359c61 100644 --- a/test/spec/modules/optoutBidAdapter_spec.js +++ b/test/spec/modules/optoutBidAdapter_spec.js @@ -1,115 +1,925 @@ import { expect } from 'chai'; -import { spec } from 'modules/optoutBidAdapter.js'; +import sinon from 'sinon'; import { config } from 'src/config.js'; +import * as gdprUtils from 'src/utils/gdpr.js'; +import { spec } from 'modules/optoutBidAdapter.js'; describe('optoutAdapterTest', function () { - describe('bidRequestValidity', function () { - it('bidRequest with adslot param', function () { + afterEach(function () { + config.resetConfig(); + sinon.restore(); + }); + + describe('isBidRequestValid', function () { + it('valid when publisher + adSlot exist', function () { expect(spec.isBidRequestValid({ bidder: 'optout', - params: { - 'adslot': 'prebid_demo', - 'publisher': '8' - } + params: { adSlot: 'prebid_demo', publisher: '8' } })).to.equal(true); }); - it('bidRequest with no adslot param', function () { + it('valid when publisher + adslot (lowercase) exist', function () { expect(spec.isBidRequestValid({ bidder: 'optout', - params: { - 'publisher': '8' - } + params: { adslot: 'prebid_demo', publisher: '8' } + })).to.equal(true); + }); + + it('invalid when adSlot missing', function () { + expect(spec.isBidRequestValid({ + bidder: 'optout', + params: { publisher: '8' } })).to.equal(false); }); - it('bidRequest with no publisher param', function () { + it('invalid when publisher missing', function () { expect(spec.isBidRequestValid({ bidder: 'optout', - params: { - 'adslot': 'prebid_demo' - } + params: { adSlot: 'prebid_demo' } })).to.equal(false); }); - it('bidRequest without params', function () { + it('invalid when params missing', function () { expect(spec.isBidRequestValid({ bidder: 'optout', - params: { } + params: {} })).to.equal(false); }); + + it('invalid when bid is null', function () { + expect(spec.isBidRequestValid(null)).to.equal(false); + }); + + it('invalid when bid is undefined', function () { + expect(spec.isBidRequestValid(undefined)).to.equal(false); + }); }); - describe('bidRequest', function () { - const bidRequests = [{ - 'bidder': 'optout', - 'params': { - 'adslot': 'prebid_demo', - 'publisher': '8' - }, - 'adUnitCode': 'aaa', - 'transactionId': '1b8389fe-615c-482d-9f1a-177fb8f7d5b0', - 'bidId': '9304jr394ddfj', - 'bidderRequestId': '70deaff71c281d', - 'auctionId': '5c66da22-426a-4bac-b153-77360bef5337' - }, - { - 'bidder': 'optout', - 'params': { - 'adslot': 'testslot2', - 'publisher': '2' - }, - 'adUnitCode': 'bbb', - 'transactionId': '193995b4-7122-4739-959b-2463282a138b', - 'bidId': '893j4f94e8jei', - 'bidderRequestId': '70deaff71c281d', - 'gdprConsent': { - consentString: '', - gdprApplies: true, - apiVersion: 2 + describe('buildRequests', function () { + const bidRequests = [ + { + bidder: 'optout', + params: { adSlot: 'prebid_demo', publisher: '8' }, + bidId: 'bidA' }, - 'auctionId': 'e97cafd0-ebfc-4f5c-b7c9-baa0fd335a4a' - }]; + { + bidder: 'optout', + params: { adSlot: 'testslot2', publisher: '8' }, + bidId: 'bidB' + } + ]; - it('bidRequest HTTP method', function () { + it('returns [] when no validBidRequests', function () { + const requests = spec.buildRequests([], {}); + expect(requests).to.deep.equal([]); + }); + + it('returns a single POST request', function () { const requests = spec.buildRequests(bidRequests, {}); - requests.forEach(function(requestItem) { - expect(requestItem.method).to.equal('POST'); - }); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); }); - it('bidRequest url without consent', function () { + it('uses optout endpoint when no gdprConsent', function () { const requests = spec.buildRequests(bidRequests, {}); - requests.forEach(function(requestItem) { - expect(requestItem.url).to.match(new RegExp('adscience-nocookie\\.nl/prebid/display')); - }); + expect(requests[0].url).to.match(/optoutadserving\.com\/prebid\/display/); + }); + + it('uses optin endpoint when gdprApplies is false', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: false, + consentString: 'test', + apiVersion: 2 + } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.match(/optinadserving\.com\/prebid\/display/); + expect(requests[0].data.gdpr).to.equal(0); }); - it('bidRequest id', function () { + it('handles gdprConsent with apiVersion 1 (no special branching)', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: '', + apiVersion: 1 + } + }; + + // purpose1 not stubbed; safest expected is still optout when gdprApplies true & hasPurpose1Consent false/unknown + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(false); + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.gdpr).to.equal(1); + expect(requests[0].data.consent).to.equal(''); + expect(requests[0].url).to.match(/optoutadserving\.com\/prebid\/display/); + }); + + it('always includes sdk_version=prebid', function () { const requests = spec.buildRequests(bidRequests, {}); - expect(requests[0].data.requestId).to.equal('9304jr394ddfj'); - expect(requests[1].data.requestId).to.equal('893j4f94e8jei'); + expect(requests[0].data.sdk_version).to.equal('prebid'); }); - it('bidRequest with config for currency', function () { + it('currency defaults to EUR when not configured', function () { + config.resetConfig(); + const requests = spec.buildRequests(bidRequests, {}); + expect(requests[0].data.cur.adServerCurrency).to.equal('EUR'); + }); + + it('currency uses config when provided', function () { config.setConfig({ - currency: { - adServerCurrency: 'USD', - granularityMultiplier: 1 - } - }) + currency: { adServerCurrency: 'USD', granularityMultiplier: 1 } + }); const requests = spec.buildRequests(bidRequests, {}); expect(requests[0].data.cur.adServerCurrency).to.equal('USD'); - expect(requests[1].data.cur.adServerCurrency).to.equal('USD'); }); - it('bidRequest without config for currency', function () { - config.resetConfig(); + it('builds slots with id and requestId', function () { + const requests = spec.buildRequests(bidRequests, {}); + const slots = requests[0].data.slots; + + expect(slots[0].id).to.equal('prebid_demo'); + expect(slots[0].requestId).to.equal('bidA'); + expect(slots[1].id).to.equal('testslot2'); + expect(slots[1].requestId).to.equal('bidB'); + }); + + it('uses refererInfo.canonicalUrl when present (sanitized to origin+pathname)', function () { + const bidderRequest = { refererInfo: { canonicalUrl: 'https://example.com/path?secret=1#frag' } }; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.url).to.equal('https://example.com/path'); + }); + + it('falls back to refererInfo.page when canonicalUrl missing (sanitized)', function () { + const bidderRequest = { refererInfo: { page: 'https://example.com/page?x=1#y' } }; + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.url).to.equal('https://example.com/page'); + }); + it('falls back to window.location (sanitized) when refererInfo missing', function () { const requests = spec.buildRequests(bidRequests, {}); - expect(requests[0].data.cur.adServerCurrency).to.equal('EUR'); - expect(requests[1].data.cur.adServerCurrency).to.equal('EUR'); + // The adapter sanitizes to origin+pathname + expect(requests[0].data.url).to.equal(window.location.origin + window.location.pathname); + }); + + it('uses publisher from the first bid when batching, even if later bids differ', function () { + const br = [ + { bidder: 'optout', params: { adSlot: 'slot1', publisher: 'PUB1' }, bidId: '1' }, + { bidder: 'optout', params: { adSlot: 'slot2', publisher: 'PUB2' }, bidId: '2' } + ]; + + const requests = spec.buildRequests(br, {}); + expect(requests[0].data.publisher).to.equal('PUB1'); + // Slots are still batched into one request + expect(requests[0].data.slots).to.have.lengthOf(2); + }); + + it('normalizes customs to strings, flattens arrays, stringifies objects, and drops invalid (circular omitted)', function () { + const circular = {}; + circular.self = circular; + + const br = [{ + bidder: 'optout', + params: { + adSlot: 'slot', + publisher: '8', + customs: { foo: 'bar', obj: { k: 'v' }, bad: circular } + }, + bidId: '1' + }]; + + const bidderRequest = { + ortb2: { + ext: { data: { a: ['x', 'y'], b: 123, c: null } } + } + }; + + const requests = spec.buildRequests(br, bidderRequest); + const customs = requests[0].data.customs; + + expect(customs.foo).to.equal('bar'); + expect(customs.a).to.equal('x,y'); + expect(customs.b).to.equal('123'); + expect(customs.obj).to.equal(JSON.stringify({ k: 'v' })); + expect(customs).to.not.have.property('c'); + // 'bad' was circular, so it must be dropped + expect(customs).to.not.have.property('bad'); + }); + + it('handles customs as null/undefined without throwing', function () { + const br = [{ + bidder: 'optout', + params: { + adSlot: 'slot', + publisher: '8', + customs: null + }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + // customs absent -> no slot.customs + expect(requests[0].data.slots[0]).to.not.have.property('customs'); + }); + + it('does not mutate input customs objects', function () { + const original = { a: ['x', 'y'], obj: { k: 'v' } }; + + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8', customs: original }, + bidId: '1' + }]; + + spec.buildRequests(br, {}); + expect(original.a).to.deep.equal(['x', 'y']); + expect(original.obj).to.deep.equal({ k: 'v' }); + }); + + it('includes per-slot customs when provided in bid params', function () { + const br = [{ + bidder: 'optout', + params: { + adSlot: 'slot', + publisher: '8', + customs: { x: 1, arr: ['a', 'b'], obj: { p: true } } + }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + const slotCustoms = requests[0].data.slots[0].customs; + + expect(slotCustoms.x).to.equal('1'); + expect(slotCustoms.arr).to.equal('a,b'); + expect(slotCustoms.obj).to.equal(JSON.stringify({ p: true })); + }); + + it('includes ortb2 payload when any bid sets includeOrtb2', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8', includeOrtb2: true }, + bidId: '1' + }]; + + const bidderRequest = { ortb2: { site: { domain: 'example.com' } } }; + const requests = spec.buildRequests(br, bidderRequest); + + expect(requests[0].data.ortb2).to.equal(JSON.stringify(bidderRequest.ortb2)); + }); + + it('does not include ortb2 payload when includeOrtb2 is false/absent', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { ortb2: { site: { domain: 'example.com' } } }; + const requests = spec.buildRequests(br, bidderRequest); + + expect(requests[0].data).to.not.have.property('ortb2'); + }); + + it('includes slot id when params.id is explicitly set', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8', id: 'customId123' }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + expect(requests[0].data.slots[0].id).to.equal('customId123'); + }); + + it('handles when validBidRequests is null', function () { + const requests = spec.buildRequests(null, {}); + expect(requests).to.deep.equal([]); + }); + + it('handles when validBidRequests is not an array', function () { + const requests = spec.buildRequests('not-an-array', {}); + expect(requests).to.deep.equal([]); + }); + + it('handles when bidderRequest is null', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, null); + expect(requests).to.have.lengthOf(1); + expect(requests[0].data.gdpr).to.equal(0); + }); + + it('handles when gdprConsent is a non-object truthy value', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { gdprConsent: 'some-string' }; + const requests = spec.buildRequests(br, bidderRequest); + expect(requests[0].data.gdpr).to.equal(0); + expect(requests[0].url).to.match(/optoutadserving\.com\/prebid\/display/); + }); + + it('merges customs from ortb2.site.ext.data', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { + ortb2: { + site: { ext: { data: { siteKey: 'siteValue' } } } + } + }; + + const requests = spec.buildRequests(br, bidderRequest); + expect(requests[0].data.customs.siteKey).to.equal('siteValue'); + }); + + it('merges customs from ortb2.app.ext.data', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { + ortb2: { + app: { ext: { data: { appKey: 'appValue' } } } + } + }; + + const requests = spec.buildRequests(br, bidderRequest); + expect(requests[0].data.customs.appKey).to.equal('appValue'); + }); + + it('merges customs from ortb2.user.ext.data', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { + ortb2: { + user: { ext: { data: { userKey: 'userValue' } } } + } + }; + + const requests = spec.buildRequests(br, bidderRequest); + expect(requests[0].data.customs.userKey).to.equal('userValue'); + }); + + it('does not include slot.customs when params.customs is empty/undefined', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + expect(requests[0].data.slots[0]).to.not.have.property('customs'); + }); + + it('builds slots with lowercase adslot param', function () { + const br = [{ + bidder: 'optout', + params: { adslot: 'lowercase_slot', publisher: '8' }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + const slot = requests[0].data.slots[0]; + + expect(slot.adSlot).to.equal('lowercase_slot'); + expect(slot.requestId).to.equal('1'); + }); + + it('normalizes customs with non-object input (returns empty object)', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8', customs: 'not-an-object' }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + expect(requests[0].data.slots[0]).to.not.have.property('customs'); + }); + + it('normalizes customs with undefined input', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8', customs: undefined }, + bidId: '1' + }]; + + const requests = spec.buildRequests(br, {}); + expect(requests[0].data.slots[0]).to.not.have.property('customs'); + }); + + it('handles gdprConsent.consentString as empty when missing', function () { + const br = [{ + bidder: 'optout', + params: { adSlot: 'slot', publisher: '8' }, + bidId: '1' + }]; + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + apiVersion: 2 + } + }; + + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(false); + + const requests = spec.buildRequests(br, bidderRequest); + expect(requests[0].data.consent).to.equal(''); + }); + }); + + describe('buildRequests (GDPR Purpose 1)', function () { + const bidRequests = [ + { bidder: 'optout', params: { adSlot: 'slot', publisher: '8' }, bidId: '1' } + ]; + + it('routes to optin when purpose1 consent is true', function () { + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(true); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'CONSENT', + apiVersion: 2 + } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.match(/optinadserving\.com\/prebid\/display/); + expect(requests[0].data.gdpr).to.equal(1); + }); + + it('routes to optout when purpose1 consent is false', function () { + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(false); + + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'CONSENT', + apiVersion: 2 + } + }; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.match(/optoutadserving\.com\/prebid\/display/); + expect(requests[0].data.gdpr).to.equal(1); + }); + }); + + describe('interpretResponse', function () { + // Helper to create standard bidRequest structure + const createBidRequest = (slots) => ({ data: { slots } }); + + // Helper to create standard serverResponse structure + const createServerResponse = (bids) => ({ body: { bids } }); + + // Standard slot and bid for reuse + const standardSlot = { id: 'slotA', requestId: 'bidA' }; + + it('maps bids correctly using slot id or requestId', function () { + const bidRequest = createBidRequest([ + { id: 'slotA', requestId: 'bidA' }, + { id: 'slotB', requestId: 'bidB' } + ]); + + const serverResponse = createServerResponse([ + { requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
', ttl: 300, creativeId: 'c1' }, + { requestId: 'slotB', cpm: 2, currency: 'EUR', width: 728, height: 90, ad: '
', ttl: 300, creativeId: 'c2' } + ]); + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.have.lengthOf(2); + expect(out[0].netRevenue).to.equal(true); + }); + + it('filters bids whose requestId does not map to a sent slot/requestId', function () { + const bidRequest = createBidRequest([standardSlot]); + const serverResponse = createServerResponse([ + { requestId: 'unknown', cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
', ttl: 300, creativeId: 'c1' } + ]); + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('supports serverResponse.body as an array (defensive parsing)', function () { + // This also verifies numeric coercion is robust if server returns strings. + const bidRequest = createBidRequest([standardSlot]); + const serverResponse = { + body: [{ requestId: 'bidA', cpm: '1.2', currency: 'EUR', width: '300', height: '250', ad: '
', ttl: '120', creativeId: 'c1' }] + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.have.lengthOf(1); + expect(out[0].cpm).to.equal(1.2); + expect(out[0].ttl).to.equal(120); + }); + + it('drops bids with missing/invalid requestId', function () { + const bidRequest = createBidRequest([standardSlot]); + const serverResponse = createServerResponse([ + { requestId: null, cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
' }, + { /* missing requestId */ cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
' } + ]); + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops incomplete bids missing required fields', function () { + const bidRequest = createBidRequest([standardSlot]); + const serverResponse = createServerResponse([ + { requestId: 'bidA', cpm: 1, /* currency missing */ width: 300, height: 250, ad: '
' }, + { requestId: 'bidA', cpm: 1, currency: 'EUR', /* width missing */ height: 250, ad: '
' }, + { requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, /* height missing */ ad: '
' }, + { requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: 250 /* ad missing */ } + ]); + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with cpm = 0', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 0, currency: 'EUR', width: 300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with negative cpm', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: -1, currency: 'EUR', width: 300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with NaN cpm', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: NaN, currency: 'EUR', width: 300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with cpm = null', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: null, currency: 'EUR', width: 300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with width = 0', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 0, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with negative width', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: -300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with non-finite width', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: Infinity, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with height = 0', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: 0, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with negative height', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: -250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('drops bids with non-finite height', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: Infinity, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('defaults ttl to 300 when missing', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
', creativeId: 'c1' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out[0].ttl).to.equal(300); + }); + + it('passes through optOutExt and meta when present', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ + requestId: 'bidA', + cpm: 1, + currency: 'EUR', + width: 300, + height: 250, + ad: '
', + ttl: 300, + creativeId: 'c1', + optOutExt: { foo: 'bar' }, + meta: { advertiserDomains: ['example.com'] } + }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out[0].optOutExt).to.deep.equal({ foo: 'bar' }); + expect(out[0].meta).to.deep.equal({ advertiserDomains: ['example.com'] }); + }); + + it('uses requestId as creativeId fallback when creativeId is missing', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const serverResponse = { + body: { + bids: [{ + requestId: 'bidA', + cpm: 1, + currency: 'EUR', + width: 300, + height: 250, + ad: '
', + ttl: 300 + // creativeId intentionally missing + }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.have.lengthOf(1); + expect(out[0].creativeId).to.equal('bidA'); // falls back to requestId + }); + + it('handles when serverResponse is null', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const out = spec.interpretResponse(null, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('handles when serverResponse is undefined', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const out = spec.interpretResponse(undefined, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('handles when serverResponse.body is null', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const out = spec.interpretResponse({ body: null }, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('handles when serverResponse.body is undefined', function () { + const bidRequest = { + data: { slots: [{ id: 'slotA', requestId: 'bidA' }] } + }; + + const out = spec.interpretResponse({ body: undefined }, bidRequest); + expect(out).to.deep.equal([]); + }); + + it('handles when bidRequest.data.slots is missing', function () { + const bidRequest = { data: {} }; + + const serverResponse = { + body: { + bids: [{ requestId: 'bidA', cpm: 1, currency: 'EUR', width: 300, height: 250, ad: '
' }] + } + }; + + const out = spec.interpretResponse(serverResponse, bidRequest); + expect(out).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('returns [] when gdprConsent missing', function () { + const out = spec.getUserSyncs({ iframeEnabled: true }, [], null); + expect(out).to.deep.equal([]); + }); + + it('returns [] when gdprConsent is a non-object truthy value', function () { + const out = spec.getUserSyncs({ iframeEnabled: true }, [], 'CONSENTSTRING'); + expect(out).to.deep.equal([]); + }); + + it('returns [] when iframeEnabled is false', function () { + const out = spec.getUserSyncs( + { iframeEnabled: false }, + [], + { gdprApplies: false, consentString: 'abc' } + ); + expect(out).to.deep.equal([]); + }); + + it('returns iframe sync when iframeEnabled and gdprApplies is false', function () { + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: false, consentString: 'abc' } + ); + + expect(out).to.have.lengthOf(1); + expect(out[0].type).to.equal('iframe'); + expect(out[0].url).to.include('gdpr=0'); + expect(out[0].url).to.include('gdpr_consent=' + encodeURIComponent('abc')); + }); + + it('returns iframe sync when gdprApplies true and purpose1 consent true', function () { + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(true); + + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: true, consentString: 'CONSENT' } + ); + + expect(out).to.have.lengthOf(1); + expect(out[0].type).to.equal('iframe'); + expect(out[0].url).to.include('gdpr=1'); + expect(out[0].url).to.include('gdpr_consent=' + encodeURIComponent('CONSENT')); + }); + + it('returns [] when gdprApplies true and purpose1 consent false', function () { + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(false); + + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: true, consentString: 'CONSENT' } + ); + + expect(out).to.deep.equal([]); + }); + + it('handles missing consentString gracefully', function () { + sinon.stub(gdprUtils, 'hasPurpose1Consent').returns(true); + + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: true } + ); + + expect(out).to.have.lengthOf(1); + expect(out[0].url).to.include('gdpr_consent='); + }); + + it('handles empty consentString', function () { + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: false, consentString: '' } + ); + + expect(out).to.have.lengthOf(1); + expect(out[0].url).to.include('gdpr_consent='); + }); + + it('handles gdprApplies as non-boolean (converts to 0)', function () { + const out = spec.getUserSyncs( + { iframeEnabled: true }, + [], + { gdprApplies: 'not-a-boolean', consentString: 'abc' } + ); + + expect(out).to.have.lengthOf(1); + expect(out[0].url).to.include('gdpr=0'); }); }); }); From f19113f5cdae1b60eb044c7cd742191697426fe2 Mon Sep 17 00:00:00 2001 From: ronishefi <168074830+ronishefi9@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:38:02 +0200 Subject: [PATCH 20/50] Taboola: support native (#14486) * add deferredBilling support using onBidBillable * update burl setting * support nurl firing logic * add extra signals to taboola request * add extra ad signals * fix missing semicolon * use Prebid's built-in counters * updated detectBot logic * In Taboola adapter, added support for native and adjusted the existing banner support. Added and updated tests. * In Taboola adapter, added support for native and adjusted the existing banner support. Added and updated tests. * removed test page pushed accidentally * Wrapped native tests with NATIVE feature check * updated media type checks * added missing tab * removed tab * support multiformat * updated media type resolving, removed some redundant code that's default behavior in prebid * removed mtype handling as taboola server doesn't return mtype, mediaType is based on the request context which is set per media type --------- Co-authored-by: Tal Avital Co-authored-by: tal avital --- modules/taboolaBidAdapter.js | 113 +++-- test/spec/modules/taboolaBidAdapter_spec.js | 475 +++++++++++++++++--- 2 files changed, 479 insertions(+), 109 deletions(-) diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 8bae68d9bf9..a82cdf0adf0 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -1,7 +1,7 @@ 'use strict'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject, getWinDimensions } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -15,7 +15,8 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC const BIDDER_CODE = 'taboola'; const GVLID = 42; const CURRENCY = 'USD'; -export const END_POINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; +export const BANNER_ENDPOINT_URL = 'https://display.bidder.taboola.com/OpenRTB/TaboolaHB/auction'; +export const NATIVE_ENDPOINT_URL = 'https://native.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'; @@ -169,12 +170,11 @@ export function getElementSignals(adUnitCode) { const converter = ortbConverter({ context: { netRevenue: true, - mediaType: BANNER, ttl: 300 }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - fillTaboolaImpData(bidRequest, imp); + fillTaboolaImpData(bidRequest, imp, context); return imp; }, request(buildRequest, imps, bidderRequest, context) { @@ -183,12 +183,21 @@ const converter = ortbConverter({ return reqData; }, bidResponse(buildBidResponse, bid, context) { + if (context.mediaType === NATIVE) { + const admObj = safeJSONParse(bid.adm); + if (admObj?.native) { + bid.adm = JSON.stringify(admObj.native); + } + } + const bidResponse = buildBidResponse(bid, context); bidResponse.nurl = bid.nurl; if (bid.burl) { bidResponse.burl = bid.burl; } - bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + if (bidResponse.mediaType !== NATIVE) { + bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + } if (bid.ext && bid.ext.dchain) { deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain); } @@ -197,35 +206,37 @@ const converter = ortbConverter({ }); export const spec = { - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, NATIVE], gvlid: GVLID, code: BIDDER_CODE, isBidRequestValid: (bidRequest) => { - return !!(bidRequest.sizes && - bidRequest.params && + const hasPublisherAndTag = !!(bidRequest.params && bidRequest.params.publisherId && bidRequest.params.tagId); + if (!hasPublisherAndTag) { + return false; + } + const { hasBanner, hasNative } = getMediaType(bidRequest); + return hasBanner || hasNative; }, buildRequests: (validBidRequests, bidderRequest) => { - const [bidRequest] = validBidRequests; - const auctionId = bidderRequest.auctionId || validBidRequests[0]?.auctionId; - const data = converter.toORTB({ - bidderRequest: bidderRequest, - bidRequests: validBidRequests, - context: { auctionId } + const bannerBids = []; + const nativeBids = []; + + validBidRequests.forEach(bid => { + const { hasBanner, hasNative } = getMediaType(bid); + if (hasBanner) bannerBids.push(bid); + if (hasNative) nativeBids.push(bid); }); - const { publisherId } = bidRequest.params; - const url = END_POINT_URL + '?publisher=' + publisherId; - return { - url, - method: 'POST', - data: data, - bids: validBidRequests, - options: { - withCredentials: false - }, - }; + const requests = []; + if (bannerBids.length) { + requests.push(createTaboolaRequest(bannerBids, bidderRequest, BANNER_ENDPOINT_URL, BANNER)); + } + if (nativeBids.length) { + requests.push(createTaboolaRequest(nativeBids, bidderRequest, NATIVE_ENDPOINT_URL, NATIVE)); + } + return requests; }, interpretResponse: (serverResponse, request) => { if (!request || !request.bids || !request.data) { @@ -346,6 +357,28 @@ export const spec = { }, }; +function createTaboolaRequest(bidRequests, bidderRequest, endpointUrl, mediaType) { + const [bidRequest] = bidRequests; + const auctionId = bidderRequest.auctionId || bidRequests[0]?.auctionId; + const data = converter.toORTB({ + bidderRequest: bidderRequest, + bidRequests: bidRequests, + context: { auctionId, mediaType } + }); + const { publisherId } = bidRequest.params; + const url = endpointUrl + '?publisher=' + publisherId; + + return { + url, + method: 'POST', + data: data, + bids: bidRequests, + options: { + withCredentials: false + }, + }; +} + function getSiteProperties({ publisherId }, refererInfo, ortb2) { const { getPageUrl, getReferrer } = internal; return { @@ -431,14 +464,16 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data, context) { } } -function fillTaboolaImpData(bid, imp) { +function fillTaboolaImpData(bid, imp, context) { const { tagId, position } = bid.params; - imp.banner = getBanners(bid, position); + if (imp.banner && position) { + imp.banner.pos = position; + } imp.tagid = tagId; - if (typeof bid.getFloor === 'function') { const floorInfo = bid.getFloor({ currency: CURRENCY, + mediaType: context.mediaType, size: '*' }); if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { @@ -476,22 +511,14 @@ function fillTaboolaImpData(bid, imp) { } } -function getBanners(bid, pos) { +function getMediaType(bidRequest) { + const hasBanner = !!bidRequest?.mediaTypes?.banner?.sizes; + const hasNative = !!bidRequest?.mediaTypes?.native; return { - ...getSizes(bid.sizes), - pos: pos - } -} - -function getSizes(sizes) { - return { - format: sizes.map(size => { - return { - w: size[0], - h: size[1] - } - }) - } + hasBanner, + hasNative, + mediaType: hasNative && !hasBanner ? NATIVE : BANNER + }; } registerBidder(spec); diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index a01eb42cdbe..9931831483c 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, EVENT_ENDPOINT, detectBot, getPageVisibility } from 'modules/taboolaBidAdapter.js'; +import { spec, internal, BANNER_ENDPOINT_URL, NATIVE_ENDPOINT_URL, userData, EVENT_ENDPOINT, detectBot, getPageVisibility } from 'modules/taboolaBidAdapter.js'; import { config } from '../../../src/config.js' import * as utils from '../../../src/utils.js' import { server } from '../../mocks/xhr.js' @@ -33,7 +33,11 @@ describe('Taboola Adapter', function () { }) const displayBidRequestParams = { - sizes: [[300, 250], [300, 600]] + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } } const createBidRequest = () => ({ @@ -266,22 +270,23 @@ describe('Taboola Adapter', function () { } it('should build display request', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); const expectedData = { 'imp': [{ 'id': res.data.imp[0].id, - 'secure': 1, 'banner': { + topframe: 0, format: [{ - w: displayBidRequestParams.sizes[0][0], - h: displayBidRequestParams.sizes[0][1] + w: displayBidRequestParams.mediaTypes.banner.sizes[0][0], + h: displayBidRequestParams.mediaTypes.banner.sizes[0][1] }, { - w: displayBidRequestParams.sizes[1][0], - h: displayBidRequestParams.sizes[1][1] + w: displayBidRequestParams.mediaTypes.banner.sizes[1][0], + h: displayBidRequestParams.mediaTypes.banner.sizes[1][1] } ] }, + 'secure': 1, 'tagid': commonBidRequest.params.tagId, 'bidfloor': null, 'bidfloorcur': 'USD', @@ -315,7 +320,7 @@ describe('Taboola Adapter', function () { 'ext': res.data.ext }; - expect(res.url).to.equal(`${END_POINT_URL}?publisher=${commonBidRequest.params.publisherId}`); + expect(res.url).to.equal(`${BANNER_ENDPOINT_URL}?publisher=${commonBidRequest.params.publisherId}`); expect(JSON.stringify(res.data)).to.deep.equal(JSON.stringify(expectedData)); expect(res.data.ext.prebid.version).to.equal('$prebid.version$'); }); @@ -331,7 +336,7 @@ describe('Taboola Adapter', function () { params: { ...commonBidRequest.params, ...optionalParams } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].bidfloor).to.deep.equal(0.25); expect(res.data.imp[0].bidfloorcur).to.deep.equal('EUR'); }); @@ -347,7 +352,7 @@ describe('Taboola Adapter', function () { } } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].bidfloor).to.deep.equal(2.7); expect(res.data.imp[0].bidfloorcur).to.deep.equal('USD'); }); @@ -368,7 +373,7 @@ describe('Taboola Adapter', function () { } } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].bidfloor).to.deep.equal(2.7); expect(res.data.imp[0].bidfloorcur).to.deep.equal('USD'); }); @@ -383,7 +388,7 @@ describe('Taboola Adapter', function () { params: { ...commonBidRequest.params, ...optionalParams } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].banner.pos).to.deep.equal(2); }); @@ -399,7 +404,7 @@ describe('Taboola Adapter', function () { params: { ...commonBidRequest.params } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.gpid).to.deep.equal('/homepage/#1'); }); @@ -415,7 +420,7 @@ describe('Taboola Adapter', function () { params: { ...commonBidRequest.params } }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.example).to.deep.equal('example'); }); @@ -424,7 +429,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.tmax).to.equal(500); }); @@ -433,7 +438,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: '500' } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.tmax).to.equal(500); }); @@ -442,7 +447,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: null } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.tmax).to.equal(undefined); }); @@ -471,7 +476,7 @@ describe('Taboola Adapter', function () { } } } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); 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) @@ -495,7 +500,7 @@ describe('Taboola Adapter', function () { } } } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.id).to.deep.equal(bidderRequest.ortb2.user.id) expect(res.data.user.buyeruid).to.deep.equal(bidderRequest.ortb2.user.buyeruid) expect(res.data.user.yob).to.deep.equal(bidderRequest.ortb2.user.yob) @@ -512,7 +517,7 @@ describe('Taboola Adapter', function () { } } } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.ext.pageType).to.deep.equal(bidderRequest.ortb2.ext.data.pageType); }); @@ -525,7 +530,7 @@ describe('Taboola Adapter', function () { } } } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.ext.example).to.deep.equal(bidderRequest.ortb2.ext.example); }); @@ -549,7 +554,7 @@ describe('Taboola Adapter', function () { } } } - const res = spec.buildRequests([defaultBidRequest], { ...ortb2 }) + const [res] = spec.buildRequests([defaultBidRequest], { ...ortb2 }) expect(res.data.user.data).to.deep.equal(ortb2.ortb2.user.data); }); }); @@ -566,7 +571,7 @@ describe('Taboola Adapter', function () { } }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest) + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest) expect(res.data.user.ext.consent).to.equal('consentString') expect(res.data.regs.ext.gdpr).to.equal(1) }); @@ -579,7 +584,7 @@ describe('Taboola Adapter', function () { } } - const res = spec.buildRequests([defaultBidRequest], { ...commonBidderRequest, ortb2 }) + const [res] = spec.buildRequests([defaultBidRequest], { ...commonBidderRequest, ortb2 }) expect(res.data.regs.ext.gpp).to.equal('testGpp') expect(res.data.regs.ext.gpp_sid).to.deep.equal([1, 2, 3]) }); @@ -591,14 +596,14 @@ describe('Taboola Adapter', function () { }, uspConsent: 'consentString' } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); 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 [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest) expect(res.data.regs.coppa).to.equal(1) config.resetConfig() @@ -615,7 +620,7 @@ describe('Taboola Adapter', function () { ...commonBidderRequest, timeout: 500 } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal(51525152); }); @@ -629,7 +634,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('12121212'); }); @@ -650,7 +655,7 @@ describe('Taboola Adapter', function () { } } }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.id).to.deep.equal('userid') expect(res.data.user.buyeruid).to.equal('12121212'); }); @@ -672,7 +677,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('user:12121212'); }); @@ -690,7 +695,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('user:12121212'); }); @@ -708,7 +713,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('user:tbla:12121212'); }); @@ -735,7 +740,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('cookie:1'); }); @@ -749,7 +754,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal('d966c5be-c49f-4f73-8cd1-37b6b5790653-tuct9f7bf10'); }); @@ -766,7 +771,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal(window.TRC.user_id); delete window.TRC; @@ -779,7 +784,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal(0); }); @@ -787,7 +792,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest } - const res = spec.buildRequests([defaultBidRequest], bidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequest); expect(res.data.user.buyeruid).to.equal(0); }); }); @@ -809,7 +814,7 @@ describe('Taboola Adapter', function () { const bidderRequest = { ...commonBidderRequest }; - const request = spec.buildRequests([defaultBidRequest], bidderRequest); + const [request] = spec.buildRequests([defaultBidRequest], bidderRequest); const serverResponse = { body: { @@ -1096,7 +1101,7 @@ describe('Taboola Adapter', function () { }); it('should interpret multi impression request', function () { - const multiRequest = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); + const [multiRequest] = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); const multiServerResponse = { body: { @@ -1438,7 +1443,7 @@ describe('Taboola Adapter', function () { }); it('should replace AUCTION_PRICE macro in adm', function () { - const multiRequest = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); + const [multiRequest] = spec.buildRequests([defaultBidRequest, defaultBidRequest], bidderRequest); const multiServerResponseWithMacro = { body: { 'id': '49ffg4d58ef9a163a69fhgfghd4fad03621b9e036f24f7_15', @@ -1661,7 +1666,11 @@ describe('Taboola Adapter', function () { }, bidId: 'test-bid-id', auctionId: 'test-auction-id', - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; const commonBidderRequest = { @@ -1674,13 +1683,13 @@ describe('Taboola Adapter', function () { }; it('should include bot detection in device.ext', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.device.ext.bot).to.exist; expect(res.data.device.ext.bot).to.have.property('detected'); }); it('should include visibility in device.ext', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.device.ext.visibility).to.exist; expect(res.data.device.ext.visibility).to.have.property('hidden'); expect(res.data.device.ext.visibility).to.have.property('state'); @@ -1688,7 +1697,7 @@ describe('Taboola Adapter', function () { }); it('should include scroll position in device.ext', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.device.ext.scroll).to.exist; expect(res.data.device.ext.scroll).to.have.property('top'); expect(res.data.device.ext.scroll).to.have.property('left'); @@ -1710,7 +1719,7 @@ describe('Taboola Adapter', function () { }; try { - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); // Viewability should be a number between 0-100 when element exists expect(res.data.imp[0].ext.viewability).to.be.a('number'); expect(res.data.imp[0].ext.viewability).to.be.at.least(0); @@ -1725,7 +1734,7 @@ describe('Taboola Adapter', function () { ...defaultBidRequest, adUnitCode: 'non-existent-element-id' }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.viewability).to.be.undefined; }); @@ -1746,7 +1755,7 @@ describe('Taboola Adapter', function () { }; try { - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.placement).to.exist; expect(res.data.imp[0].ext.placement).to.have.property('top'); expect(res.data.imp[0].ext.placement).to.have.property('left'); @@ -1771,7 +1780,7 @@ describe('Taboola Adapter', function () { }; try { - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.fold).to.exist; expect(res.data.imp[0].ext.fold).to.be.oneOf(['above', 'below']); } finally { @@ -1784,7 +1793,7 @@ describe('Taboola Adapter', function () { ...defaultBidRequest, adUnitCode: 'non-existent-placement-element' }; - const res = spec.buildRequests([bidRequest], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest], commonBidderRequest); expect(res.data.imp[0].ext.placement).to.be.undefined; expect(res.data.imp[0].ext.fold).to.be.undefined; }); @@ -1801,19 +1810,19 @@ describe('Taboola Adapter', function () { } } }; - const res = spec.buildRequests([defaultBidRequest], bidderRequestWithDeviceExt); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequestWithDeviceExt); expect(res.data.device.ext.existingProp).to.equal('existingValue'); expect(res.data.device.ext.bot).to.exist; expect(res.data.device.ext.visibility).to.exist; }); it('should include device.js = 1', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.device.js).to.equal(1); }); it('should include connectiontype when available', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); // connectiontype is optional - depends on navigator.connection availability if (res.data.device.connectiontype !== undefined) { expect(res.data.device.connectiontype).to.be.a('number'); @@ -1832,7 +1841,7 @@ describe('Taboola Adapter', function () { } } }; - const res = spec.buildRequests([defaultBidRequest], bidderRequestWithDevice); + const [res] = spec.buildRequests([defaultBidRequest], bidderRequestWithDevice); expect(res.data.device.ua).to.equal('custom-ua'); expect(res.data.device.w).to.equal(1920); expect(res.data.device.h).to.equal(1080); @@ -1850,7 +1859,11 @@ describe('Taboola Adapter', function () { bidId: 'test-bid-id-123', auctionId: 'test-auction-id-456', adUnitCode: 'test-ad-unit-code', - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; const commonBidderRequest = { @@ -1864,17 +1877,17 @@ describe('Taboola Adapter', function () { }; it('should include auctionId in ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.ext.prebid.auctionId).to.equal('auction-id-789'); }); it('should include bidId in imp.ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidId).to.equal('test-bid-id-123'); }); it('should include adUnitCode in imp.ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.adUnitCode).to.equal('test-ad-unit-code'); }); @@ -1883,12 +1896,12 @@ describe('Taboola Adapter', function () { ...defaultBidRequest, adUnitId: 'test-ad-unit-id' }; - const res = spec.buildRequests([bidRequestWithAdUnitId], commonBidderRequest); + const [res] = spec.buildRequests([bidRequestWithAdUnitId], commonBidderRequest); expect(res.data.imp[0].ext.prebid.adUnitId).to.equal('test-ad-unit-id'); }); it('should not include adUnitId when not available', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.adUnitId).to.be.undefined; }); @@ -1903,7 +1916,7 @@ describe('Taboola Adapter', function () { bidId: 'bid-id-2', adUnitCode: 'ad-unit-2' }; - const res = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidId).to.equal('bid-id-1'); expect(res.data.imp[0].ext.prebid.adUnitCode).to.equal('ad-unit-1'); expect(res.data.imp[1].ext.prebid.bidId).to.equal('bid-id-2'); @@ -1923,7 +1936,11 @@ describe('Taboola Adapter', function () { bidRequestsCount: 3, bidderRequestsCount: 2, bidderWinsCount: 1, - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; const commonBidderRequest = { @@ -1937,17 +1954,17 @@ describe('Taboola Adapter', function () { }; it('should include bidRequestsCount in imp.ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidRequestsCount).to.equal(3); }); it('should include bidderRequestsCount in imp.ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidderRequestsCount).to.equal(2); }); it('should include bidderWinsCount in imp.ext.prebid', function () { - const res = spec.buildRequests([defaultBidRequest], commonBidderRequest); + const [res] = spec.buildRequests([defaultBidRequest], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidderWinsCount).to.equal(1); }); @@ -1968,7 +1985,7 @@ describe('Taboola Adapter', function () { bidderRequestsCount: 1, bidderWinsCount: 0 }; - const res = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); + const [res] = spec.buildRequests([bidRequest1, bidRequest2], commonBidderRequest); expect(res.data.imp[0].ext.prebid.bidRequestsCount).to.equal(5); expect(res.data.imp[0].ext.prebid.bidderRequestsCount).to.equal(4); @@ -1980,4 +1997,330 @@ describe('Taboola Adapter', function () { }); }); }) + + describe('native', function () { + const commonBidderRequest = { + bidderRequestId: 'mock-uuid', + refererInfo: { + page: 'https://example.com/ref', + ref: 'https://ref', + domain: 'example.com', + }, + ortb2: { + device: { + ua: navigator.userAgent, + }, + } + }; + + const nativeBidRequestParams = { + mediaTypes: { + native: { + title: { required: true, len: 150 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + } + } + }; + + describe('isBidRequestValid', function () { + it('should return true for valid native bid without sizes', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'native-placement' + }, + ...nativeBidRequestParams + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false for native bid without publisherId', function () { + const bid = { + bidder: 'taboola', + params: { + tagId: 'native-placement' + }, + ...nativeBidRequestParams + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return false for native bid without tagId', function () { + const bid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId' + }, + ...nativeBidRequestParams + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + if (FEATURES.NATIVE) { + it('should build native request without banner imp', function () { + const nativeBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'native-placement' + }, + ...nativeBidRequestParams, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 1, required: 1, title: { len: 150 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 3, required: 1, data: { type: 1 } } + ] + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const [res] = spec.buildRequests([nativeBidRequest], commonBidderRequest); + + expect(res.data.imp[0]).to.not.have.property('banner'); + expect(res.data.imp[0]).to.have.property('native'); + expect(res.data.imp[0].tagid).to.equal('native-placement'); + }); + } + + it('should build banner request without native imp', function () { + const bannerBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'banner-placement' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const [res] = spec.buildRequests([bannerBidRequest], commonBidderRequest); + + expect(res.data.imp[0]).to.have.property('banner'); + expect(res.data.imp[0]).to.not.have.property('native'); + expect(res.data.imp[0]).to.not.have.property('native'); + }); + }); + + describe('interpretResponse', function () { + if (FEATURES.NATIVE) { + it('should interpret native response correctly', function () { + const nativeBidRequest = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'native-placement' + }, + ...nativeBidRequestParams, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 1, required: 1, title: { len: 150 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } } + ] + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const [request] = spec.buildRequests([nativeBidRequest], commonBidderRequest); + + const nativeAdm = { + ver: '1.2', + assets: [ + { id: 1, title: { text: 'Native Ad Title' } }, + { id: 2, img: { url: 'https://example.com/image.jpg', w: 300, h: 250 } } + ], + link: { + url: 'https://example.com/click' + } + }; + + const serverResponse = { + body: { + id: 'response-id', + seatbid: [{ + bid: [{ + id: 'bid-id', + impid: request.data.imp[0].id, + price: 1.5, + adm: JSON.stringify(nativeAdm), + adomain: ['example.com'], + crid: 'creative-id', + exp: 300, + nurl: 'https://example.com/win' + }], + seat: 'taboola' + }], + cur: 'USD' + } + }; + + const res = spec.interpretResponse(serverResponse, request); + + expect(res).to.be.an('array').with.lengthOf(1); + expect(res[0].mediaType).to.equal('native'); + expect(res[0].native).to.exist; + expect(res[0].native.ortb).to.deep.equal(nativeAdm); + expect(res[0]).to.not.have.property('ad'); + }); + } + }); + + if (FEATURES.NATIVE) { + describe('multiformat support', function () { + it('should split multiformat bid into separate banner and native requests', function () { + const multiformatBid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'multiformat-placement' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + }, + native: { + title: { required: true, len: 150 }, + image: { required: true, sizes: [300, 250] }, + sponsoredBy: { required: true } + } + }, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 1, required: 1, title: { len: 150 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } }, + { id: 3, required: 1, data: { type: 1 } } + ] + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const requests = spec.buildRequests([multiformatBid], commonBidderRequest); + + expect(requests).to.be.an('array').with.lengthOf(2); + + const bannerReq = requests.find(r => r.url.includes('display')); + const nativeReq = requests.find(r => r.url.includes('native')); + + expect(bannerReq).to.exist; + expect(nativeReq).to.exist; + + expect(bannerReq.url).to.include(BANNER_ENDPOINT_URL); + expect(nativeReq.url).to.include(NATIVE_ENDPOINT_URL); + + expect(bannerReq.data.imp[0]).to.have.property('banner'); + expect(bannerReq.data.imp[0]).to.not.have.property('native'); + + expect(nativeReq.data.imp[0]).to.have.property('native'); + expect(nativeReq.data.imp[0]).to.not.have.property('banner'); + }); + + it('should send banner-only bids to display endpoint only', function () { + const bannerBid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'banner-placement' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const requests = spec.buildRequests([bannerBid], commonBidderRequest); + + expect(requests).to.be.an('array').with.lengthOf(1); + expect(requests[0].url).to.include(BANNER_ENDPOINT_URL); + }); + + it('should send native-only bids to native endpoint only', function () { + const nativeBid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'native-placement' + }, + ...nativeBidRequestParams, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 1, required: 1, title: { len: 150 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } } + ] + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const requests = spec.buildRequests([nativeBid], commonBidderRequest); + + expect(requests).to.be.an('array').with.lengthOf(1); + expect(requests[0].url).to.include(NATIVE_ENDPOINT_URL); + }); + + it('should group mixed banner and native bids into separate requests', function () { + const bannerBid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'banner-placement' + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const nativeBid = { + bidder: 'taboola', + params: { + publisherId: 'publisherId', + tagId: 'native-placement' + }, + ...nativeBidRequestParams, + nativeOrtbRequest: { + ver: '1.2', + assets: [ + { id: 1, required: 1, title: { len: 150 } }, + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } } + ] + }, + bidId: utils.generateUUID(), + auctionId: utils.generateUUID(), + }; + + const requests = spec.buildRequests([bannerBid, nativeBid], commonBidderRequest); + + expect(requests).to.be.an('array').with.lengthOf(2); + + const bannerReq = requests.find(r => r.url.includes('display')); + const nativeReq = requests.find(r => r.url.includes('native')); + + expect(bannerReq.data.imp).to.have.lengthOf(1); + expect(nativeReq.data.imp).to.have.lengthOf(1); + }); + }); + } + }); }) From 13da5e0d05c6150db77c57d95b5eefa562db78c7 Mon Sep 17 00:00:00 2001 From: ronishefi <168074830+ronishefi9@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:38:41 +0200 Subject: [PATCH 21/50] Taboola multiformat test page (#14543) * add deferredBilling support using onBidBillable * update burl setting * support nurl firing logic * add extra signals to taboola request * add extra ad signals * fix missing semicolon * use Prebid's built-in counters * updated detectBot logic * Add html test page for the Taboola adapter testing multiformat ad support (banner, native) --------- Co-authored-by: Tal Avital Co-authored-by: tal avital --- .../gpt/taboola_multiformat.html | 492 ++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 integrationExamples/gpt/taboola_multiformat.html diff --git a/integrationExamples/gpt/taboola_multiformat.html b/integrationExamples/gpt/taboola_multiformat.html new file mode 100644 index 00000000000..e92d8158fd6 --- /dev/null +++ b/integrationExamples/gpt/taboola_multiformat.html @@ -0,0 +1,492 @@ + + + + + + + + + + + + + + + + + +

Taboola Multiformat Test

+

The multiformat ad unit should generate requests to both endpoints.

+ +

Bid Results

+
Waiting for bids...
+ +
+
Banner Only
+
Waiting for bids...
+
Waiting for bids...
+
Debug: div-banner
Loading...
+ +
+
Native Only
+
Waiting for bids...
+
Waiting for bids...
+
Debug: div-native
Loading...
+ +
+
Multiformat (Banner + Native)
+
Waiting for bids...
+
Waiting for bids...
+
Debug: div-multiformat
Loading...
+ + + From 7afcc7c8bfd6dbfc4605a9f6f9212c8507d37e31 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 12 Mar 2026 16:50:47 -0400 Subject: [PATCH 22/50] Fix formatting of bidderReq object in test --- test/spec/modules/deepintentBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index 2e33359dada..a1fb9f492eb 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -417,7 +417,7 @@ describe('Deepintent adapter', function () { expect(data.badv).to.deep.equal(['example.com']); }); it('should not add bcat or badv when bidderRequest.ortb2 does not have them', function() { - const bidderReq = {ortb2: {}}; + const bidderReq = { ortb2: {} }; const bRequest = spec.buildRequests(request, bidderReq); const data = JSON.parse(bRequest.data); expect(data.bcat).to.be.undefined; From 475215050a193261b3e39be4beea5ddcc3841e97 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 12 Mar 2026 17:09:21 -0400 Subject: [PATCH 23/50] fix version number for 10.29 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a34b76e84fd..c2907a51cef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.28.1-pre", + "version": "10.29.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.28.1-pre", + "version": "10.29.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index ab91b85026c..3a7e0d429ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.28.1-pre", + "version": "10.29.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From c16d7362512e8d979397c2e0ce5c85dcf1d2af2d Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Mar 2026 22:11:33 +0000 Subject: [PATCH 24/50] Prebid 10.29.0 release --- integrationExamples/gpt/x-domain/creative.html | 2 +- metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/aceexBidAdapter.json | 2 +- metadata/modules/acuityadsBidAdapter.json | 2 +- metadata/modules/adagioBidAdapter.json | 2 +- metadata/modules/adagioRtdProvider.json | 2 +- metadata/modules/adbroBidAdapter.json | 2 +- metadata/modules/addefendBidAdapter.json | 2 +- metadata/modules/adfBidAdapter.json | 2 +- metadata/modules/adfusionBidAdapter.json | 2 +- metadata/modules/adheseBidAdapter.json | 2 +- metadata/modules/adipoloBidAdapter.json | 2 +- metadata/modules/adkernelAdnBidAdapter.json | 2 +- metadata/modules/adkernelBidAdapter.json | 10 +++++----- metadata/modules/admaticBidAdapter.json | 4 ++-- metadata/modules/admixerBidAdapter.json | 2 +- metadata/modules/admixerIdSystem.json | 2 +- metadata/modules/adnowBidAdapter.json | 2 +- metadata/modules/adnuntiusBidAdapter.json | 2 +- metadata/modules/adnuntiusRtdProvider.json | 2 +- metadata/modules/adoceanBidAdapter.json | 2 +- metadata/modules/adotBidAdapter.json | 2 +- metadata/modules/adponeBidAdapter.json | 2 +- metadata/modules/adqueryBidAdapter.json | 2 +- metadata/modules/adqueryIdSystem.json | 2 +- metadata/modules/adrinoBidAdapter.json | 2 +- metadata/modules/ads_interactiveBidAdapter.json | 2 +- metadata/modules/adtargetBidAdapter.json | 2 +- metadata/modules/adtelligentBidAdapter.json | 4 ++-- metadata/modules/adtelligentIdSystem.json | 2 +- metadata/modules/aduptechBidAdapter.json | 2 +- metadata/modules/adyoulikeBidAdapter.json | 2 +- metadata/modules/airgridRtdProvider.json | 2 +- metadata/modules/alkimiBidAdapter.json | 2 +- metadata/modules/allegroBidAdapter.json | 2 +- metadata/modules/amxBidAdapter.json | 2 +- metadata/modules/amxIdSystem.json | 2 +- metadata/modules/aniviewBidAdapter.json | 2 +- metadata/modules/anonymisedRtdProvider.json | 2 +- metadata/modules/apesterBidAdapter.json | 2 +- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 8 ++++---- metadata/modules/appushBidAdapter.json | 2 +- metadata/modules/apsBidAdapter.json | 2 +- metadata/modules/apstreamBidAdapter.json | 2 +- metadata/modules/audiencerunBidAdapter.json | 2 +- metadata/modules/axisBidAdapter.json | 2 +- metadata/modules/azerionedgeRtdProvider.json | 2 +- metadata/modules/beachfrontBidAdapter.json | 2 +- metadata/modules/beopBidAdapter.json | 2 +- metadata/modules/betweenBidAdapter.json | 2 +- metadata/modules/bidmaticBidAdapter.json | 2 +- metadata/modules/bidtheatreBidAdapter.json | 2 +- metadata/modules/bliinkBidAdapter.json | 2 +- metadata/modules/blockthroughBidAdapter.json | 2 +- metadata/modules/blueBidAdapter.json | 2 +- metadata/modules/bmsBidAdapter.json | 2 +- metadata/modules/boldwinBidAdapter.json | 2 +- metadata/modules/bridBidAdapter.json | 2 +- metadata/modules/browsiBidAdapter.json | 2 +- metadata/modules/bucksenseBidAdapter.json | 2 +- metadata/modules/carodaBidAdapter.json | 2 +- metadata/modules/categoryTranslation.json | 2 +- metadata/modules/ceeIdSystem.json | 2 +- metadata/modules/chromeAiRtdProvider.json | 2 +- metadata/modules/clickioBidAdapter.json | 2 +- metadata/modules/compassBidAdapter.json | 2 +- metadata/modules/conceptxBidAdapter.json | 2 +- metadata/modules/connatixBidAdapter.json | 2 +- metadata/modules/connectIdSystem.json | 2 +- metadata/modules/connectadBidAdapter.json | 2 +- metadata/modules/contentexchangeBidAdapter.json | 2 +- metadata/modules/conversantBidAdapter.json | 2 +- metadata/modules/copper6sspBidAdapter.json | 2 +- metadata/modules/cpmstarBidAdapter.json | 2 +- metadata/modules/criteoBidAdapter.json | 2 +- metadata/modules/criteoIdSystem.json | 2 +- metadata/modules/cwireBidAdapter.json | 2 +- metadata/modules/czechAdIdSystem.json | 2 +- metadata/modules/dailymotionBidAdapter.json | 2 +- metadata/modules/debugging.json | 2 +- metadata/modules/deepintentBidAdapter.json | 2 +- metadata/modules/defineMediaBidAdapter.json | 2 +- metadata/modules/deltaprojectsBidAdapter.json | 2 +- metadata/modules/dianomiBidAdapter.json | 2 +- metadata/modules/digitalMatterBidAdapter.json | 2 +- metadata/modules/distroscaleBidAdapter.json | 2 +- metadata/modules/docereeAdManagerBidAdapter.json | 2 +- metadata/modules/docereeBidAdapter.json | 2 +- metadata/modules/dspxBidAdapter.json | 2 +- metadata/modules/e_volutionBidAdapter.json | 2 +- metadata/modules/edge226BidAdapter.json | 2 +- metadata/modules/empowerBidAdapter.json | 2 +- metadata/modules/equativBidAdapter.json | 2 +- metadata/modules/eskimiBidAdapter.json | 2 +- metadata/modules/etargetBidAdapter.json | 2 +- metadata/modules/euidIdSystem.json | 2 +- metadata/modules/exadsBidAdapter.json | 2 +- metadata/modules/feedadBidAdapter.json | 2 +- metadata/modules/fwsspBidAdapter.json | 2 +- metadata/modules/gamoshiBidAdapter.json | 2 +- metadata/modules/gemiusIdSystem.json | 2 +- metadata/modules/glomexBidAdapter.json | 2 +- metadata/modules/goldbachBidAdapter.json | 2 +- metadata/modules/gridBidAdapter.json | 2 +- metadata/modules/gumgumBidAdapter.json | 2 +- metadata/modules/hadronIdSystem.json | 2 +- metadata/modules/hadronRtdProvider.json | 2 +- metadata/modules/harionBidAdapter.json | 2 +- metadata/modules/holidBidAdapter.json | 2 +- metadata/modules/hybridBidAdapter.json | 2 +- metadata/modules/id5IdSystem.json | 2 +- metadata/modules/identityLinkIdSystem.json | 2 +- metadata/modules/illuminBidAdapter.json | 2 +- metadata/modules/impactifyBidAdapter.json | 2 +- metadata/modules/improvedigitalBidAdapter.json | 2 +- metadata/modules/inmobiBidAdapter.json | 2 +- metadata/modules/insticatorBidAdapter.json | 2 +- metadata/modules/insuradsBidAdapter.json | 2 +- metadata/modules/intentIqIdSystem.json | 2 +- metadata/modules/invibesBidAdapter.json | 2 +- metadata/modules/ipromBidAdapter.json | 2 +- metadata/modules/ixBidAdapter.json | 2 +- metadata/modules/justIdSystem.json | 2 +- metadata/modules/justpremiumBidAdapter.json | 2 +- metadata/modules/jwplayerBidAdapter.json | 2 +- metadata/modules/kargoBidAdapter.json | 2 +- metadata/modules/kueezRtbBidAdapter.json | 2 +- metadata/modules/limelightDigitalBidAdapter.json | 4 ++-- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 2 +- metadata/modules/magniteBidAdapter.json | 2 +- metadata/modules/marsmediaBidAdapter.json | 2 +- metadata/modules/mediaConsortiumBidAdapter.json | 2 +- metadata/modules/mediaforceBidAdapter.json | 2 +- metadata/modules/mediafuseBidAdapter.json | 2 +- metadata/modules/mediagoBidAdapter.json | 2 +- metadata/modules/mediakeysBidAdapter.json | 2 +- metadata/modules/medianetBidAdapter.json | 4 ++-- metadata/modules/mediasquareBidAdapter.json | 2 +- metadata/modules/mgidBidAdapter.json | 2 +- metadata/modules/mgidRtdProvider.json | 2 +- metadata/modules/mgidXBidAdapter.json | 2 +- metadata/modules/minutemediaBidAdapter.json | 2 +- metadata/modules/missenaBidAdapter.json | 2 +- metadata/modules/mobianRtdProvider.json | 2 +- metadata/modules/mobkoiBidAdapter.json | 2 +- metadata/modules/mobkoiIdSystem.json | 2 +- metadata/modules/msftBidAdapter.json | 2 +- metadata/modules/nativeryBidAdapter.json | 2 +- metadata/modules/nativoBidAdapter.json | 2 +- metadata/modules/newspassidBidAdapter.json | 2 +- metadata/modules/nextMillenniumBidAdapter.json | 2 +- metadata/modules/nextrollBidAdapter.json | 2 +- metadata/modules/nexx360BidAdapter.json | 12 ++++++------ metadata/modules/nobidBidAdapter.json | 2 +- metadata/modules/nodalsAiRtdProvider.json | 2 +- metadata/modules/novatiqIdSystem.json | 2 +- metadata/modules/oguryBidAdapter.json | 2 +- metadata/modules/omnidexBidAdapter.json | 2 +- metadata/modules/omsBidAdapter.json | 2 +- metadata/modules/onetagBidAdapter.json | 2 +- metadata/modules/openwebBidAdapter.json | 2 +- metadata/modules/openxBidAdapter.json | 2 +- metadata/modules/operaadsBidAdapter.json | 2 +- metadata/modules/optidigitalBidAdapter.json | 2 +- metadata/modules/optoutBidAdapter.json | 2 +- metadata/modules/orbidderBidAdapter.json | 2 +- metadata/modules/outbrainBidAdapter.json | 2 +- metadata/modules/ozoneBidAdapter.json | 2 +- metadata/modules/pairIdSystem.json | 2 +- metadata/modules/panxoBidAdapter.json | 2 +- metadata/modules/performaxBidAdapter.json | 2 +- .../modules/permutiveIdentityManagerIdSystem.json | 2 +- metadata/modules/permutiveRtdProvider.json | 2 +- metadata/modules/pixfutureBidAdapter.json | 2 +- metadata/modules/playdigoBidAdapter.json | 2 +- metadata/modules/prebid-core.json | 4 ++-- metadata/modules/precisoBidAdapter.json | 2 +- metadata/modules/prismaBidAdapter.json | 2 +- metadata/modules/programmaticXBidAdapter.json | 2 +- metadata/modules/proxistoreBidAdapter.json | 2 +- metadata/modules/publinkIdSystem.json | 2 +- metadata/modules/pubmaticBidAdapter.json | 2 +- metadata/modules/pubmaticIdSystem.json | 2 +- metadata/modules/pubstackBidAdapter.json | 2 +- metadata/modules/pulsepointBidAdapter.json | 2 +- metadata/modules/quantcastBidAdapter.json | 2 +- metadata/modules/quantcastIdSystem.json | 2 +- metadata/modules/r2b2BidAdapter.json | 2 +- metadata/modules/readpeakBidAdapter.json | 2 +- metadata/modules/relayBidAdapter.json | 2 +- metadata/modules/relevantdigitalBidAdapter.json | 2 +- metadata/modules/resetdigitalBidAdapter.json | 2 +- metadata/modules/responsiveAdsBidAdapter.json | 2 +- metadata/modules/revcontentBidAdapter.json | 2 +- metadata/modules/revnewBidAdapter.json | 2 +- metadata/modules/rhythmoneBidAdapter.json | 2 +- metadata/modules/richaudienceBidAdapter.json | 2 +- metadata/modules/riseBidAdapter.json | 4 ++-- metadata/modules/rixengineBidAdapter.json | 2 +- metadata/modules/rtbhouseBidAdapter.json | 2 +- metadata/modules/rubiconBidAdapter.json | 2 +- metadata/modules/scaliburBidAdapter.json | 2 +- metadata/modules/screencoreBidAdapter.json | 2 +- metadata/modules/seedingAllianceBidAdapter.json | 2 +- metadata/modules/seedtagBidAdapter.json | 2 +- metadata/modules/semantiqRtdProvider.json | 2 +- metadata/modules/sevioBidAdapter.json | 2 +- metadata/modules/sharedIdSystem.json | 2 +- metadata/modules/sharethroughBidAdapter.json | 2 +- metadata/modules/showheroes-bsBidAdapter.json | 2 +- metadata/modules/silvermobBidAdapter.json | 2 +- metadata/modules/sirdataRtdProvider.json | 2 +- metadata/modules/smaatoBidAdapter.json | 2 +- metadata/modules/smartadserverBidAdapter.json | 2 +- metadata/modules/smartxBidAdapter.json | 2 +- metadata/modules/smartyadsBidAdapter.json | 2 +- metadata/modules/smilewantedBidAdapter.json | 2 +- metadata/modules/snigelBidAdapter.json | 2 +- metadata/modules/sonaradsBidAdapter.json | 2 +- metadata/modules/sonobiBidAdapter.json | 2 +- metadata/modules/sovrnBidAdapter.json | 2 +- metadata/modules/sparteoBidAdapter.json | 2 +- metadata/modules/ssmasBidAdapter.json | 2 +- metadata/modules/sspBCBidAdapter.json | 2 +- metadata/modules/stackadaptBidAdapter.json | 2 +- metadata/modules/startioBidAdapter.json | 2 +- metadata/modules/stroeerCoreBidAdapter.json | 2 +- metadata/modules/stvBidAdapter.json | 2 +- metadata/modules/sublimeBidAdapter.json | 2 +- metadata/modules/taboolaBidAdapter.json | 2 +- metadata/modules/taboolaIdSystem.json | 2 +- metadata/modules/tadvertisingBidAdapter.json | 2 +- metadata/modules/tappxBidAdapter.json | 2 +- metadata/modules/targetVideoBidAdapter.json | 2 +- metadata/modules/teadsBidAdapter.json | 2 +- metadata/modules/teadsIdSystem.json | 2 +- metadata/modules/tealBidAdapter.json | 2 +- metadata/modules/tncIdSystem.json | 2 +- metadata/modules/topicsFpdModule.json | 2 +- metadata/modules/toponBidAdapter.json | 2 +- metadata/modules/tripleliftBidAdapter.json | 2 +- metadata/modules/ttdBidAdapter.json | 2 +- metadata/modules/twistDigitalBidAdapter.json | 2 +- metadata/modules/underdogmediaBidAdapter.json | 2 +- metadata/modules/undertoneBidAdapter.json | 2 +- metadata/modules/unifiedIdSystem.json | 2 +- metadata/modules/unrulyBidAdapter.json | 2 +- metadata/modules/userId.json | 2 +- metadata/modules/utiqIdSystem.json | 2 +- metadata/modules/utiqMtpIdSystem.json | 2 +- metadata/modules/validationFpdModule.json | 2 +- metadata/modules/valuadBidAdapter.json | 2 +- metadata/modules/vidazooBidAdapter.json | 2 +- metadata/modules/vidoomyBidAdapter.json | 2 +- metadata/modules/viouslyBidAdapter.json | 2 +- metadata/modules/visxBidAdapter.json | 2 +- metadata/modules/vlybyBidAdapter.json | 2 +- metadata/modules/voxBidAdapter.json | 2 +- metadata/modules/vrtcalBidAdapter.json | 2 +- metadata/modules/vuukleBidAdapter.json | 2 +- metadata/modules/weboramaRtdProvider.json | 2 +- metadata/modules/welectBidAdapter.json | 2 +- metadata/modules/yahooAdsBidAdapter.json | 2 +- metadata/modules/yaleoBidAdapter.json | 2 +- metadata/modules/yieldlabBidAdapter.json | 2 +- metadata/modules/yieldloveBidAdapter.json | 2 +- metadata/modules/yieldmoBidAdapter.json | 2 +- metadata/modules/zeotapIdPlusIdSystem.json | 2 +- metadata/modules/zeta_globalBidAdapter.json | 2 +- metadata/modules/zeta_global_sspBidAdapter.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 281 files changed, 300 insertions(+), 300 deletions(-) diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index ae8456c19e0..14deeb4f559 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' - + - - - - - - - - - - - - - - - - -

Prebid Freewheel Test Page

-

requireExactDuration = false

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - \ No newline at end of file diff --git a/integrationExamples/longform/basic_w_custom_adserver_translation.html b/integrationExamples/longform/basic_w_custom_adserver_translation.html deleted file mode 100644 index 2dbb89506b5..00000000000 --- a/integrationExamples/longform/basic_w_custom_adserver_translation.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Prebid Freewheel Integration Demo - - - - - - - - - - - - - - - - - - - -

Prebid Freewheel Integration Demo

-

custom adserver translation file

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - diff --git a/integrationExamples/longform/basic_w_priceGran.html b/integrationExamples/longform/basic_w_priceGran.html deleted file mode 100644 index 4ea9d5d19be..00000000000 --- a/integrationExamples/longform/basic_w_priceGran.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - Prebid Freewheel Integration Demo - - - - - - - - - - - - - - - - - - - -

Prebid Freewheel Test Page

-

requireExactDuration = false

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - \ No newline at end of file diff --git a/integrationExamples/longform/basic_w_requireExactDuration.html b/integrationExamples/longform/basic_w_requireExactDuration.html deleted file mode 100644 index 46b91887cfb..00000000000 --- a/integrationExamples/longform/basic_w_requireExactDuration.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - Prebid Freewheel Integration Demo - - - - - - - - - - - - - - - - - - - -

Prebid Freewheel Test Page

-

requireExactDuration = true

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - diff --git a/integrationExamples/longform/basic_wo_brandCategoryExclusion.html b/integrationExamples/longform/basic_wo_brandCategoryExclusion.html deleted file mode 100644 index 47ea4b7f47d..00000000000 --- a/integrationExamples/longform/basic_wo_brandCategoryExclusion.html +++ /dev/null @@ -1,133 +0,0 @@ - - - - - Prebid Freewheel Integration Demo - - - - - - - - - - - - - - - - - - - -

Prebid Freewheel Test Page

-

brandCategoryExclusion = false

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - diff --git a/integrationExamples/longform/basic_wo_requireExactDuration.html b/integrationExamples/longform/basic_wo_requireExactDuration.html deleted file mode 100644 index 6dbedbc6d39..00000000000 --- a/integrationExamples/longform/basic_wo_requireExactDuration.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - Prebid Freewheel Integration Demo - - - - - - - - - - - - - - - - - - - -

Prebid Freewheel Test Page

-

requireExactDuration = false

-
-
- -
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-

- -

-
-
-
- // bids -
-
-
-
-
-
-
- - - - diff --git a/libraries/advangUtils/index.js b/libraries/advangUtils/index.js index aab9c2b6e27..cf15c5025b5 100644 --- a/libraries/advangUtils/index.js +++ b/libraries/advangUtils/index.js @@ -1,6 +1,6 @@ import { generateUUID, isFn, parseSizesInput, parseUrl } from '../../src/utils.js'; -import { getDNT as getNavigatorDNT } from '../dnt/index.js'; import { config } from '../../src/config.js'; +import { getDNT } from '../dnt/index.js'; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; @@ -46,10 +46,6 @@ export function isConnectedTV() { return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); } -export function getDoNotTrack(win = typeof window !== 'undefined' ? window : undefined) { - return getNavigatorDNT(win); -} - export function findAndFillParam(o, key, value) { try { if (typeof value === 'function') { @@ -145,7 +141,7 @@ export function createRequestData(bid, bidderRequest, isVideo, getBidParam, getS const o = { 'device': { 'langauge': (global.navigator.language).split('-')[0], - 'dnt': getDoNotTrack(global) ? 1 : 0, + 'dnt': getDNT() ? 1 : 0, 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, 'js': 1, 'os': getOsVersion() @@ -170,7 +166,7 @@ export function createRequestData(bid, bidderRequest, isVideo, getBidParam, getS o.site['ref'] = topReferrer; o.site['mobile'] = isMobile() ? 1 : 0; const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - o.device['dnt'] = getDoNotTrack(global) ? 1 : 0; + o.device['dnt'] = getDNT() ? 1 : 0; findAndFillParam(o.site, 'name', function() { return global.top.document.title; diff --git a/libraries/bidViewabilityPixels/index.js b/libraries/bidViewabilityPixels/index.js new file mode 100644 index 00000000000..63417940d53 --- /dev/null +++ b/libraries/bidViewabilityPixels/index.js @@ -0,0 +1,60 @@ +import { EVENT_TYPE_VIEWABLE, parseEventTrackers, TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from '../../src/eventTrackers.js'; +import { filterEventTrackers, legacyPropertiesToOrtbNative } from '../../src/native.js'; +import { triggerPixel, insertHtmlIntoIframe } from '../../src/utils.js'; +import * as events from '../../src/events.js'; +import { EVENTS } from '../../src/constants.js'; +import adapterManager from '../../src/adapterManager.js'; + +/** + * Collects viewable tracking URLs from bid.eventtrackers for EVENT_TYPE_VIEWABLE (IMG and JS methods). + * @param {Object} bid - bid object that may have eventtrackers array + * @returns {{ img: string[], js: string[] }} img and js URLs to fire + */ +export function getViewabilityTrackersFromBid(bid) { + const eventTrackers = bid?.eventtrackers; + if (!eventTrackers || !Array.isArray(eventTrackers)) return { img: [], js: [] }; + const parsed = parseEventTrackers(eventTrackers); + const viewableTrackers = parsed[EVENT_TYPE_VIEWABLE]; + if (!viewableTrackers || typeof viewableTrackers !== 'object') return { img: [], js: [] }; + const img = viewableTrackers[TRACKER_METHOD_IMG]; + const js = viewableTrackers[TRACKER_METHOD_JS]; + return { + img: Array.isArray(img) ? img : [], + js: Array.isArray(js) ? js : [] + }; +} + +/** + * Fires viewability trackers for a bid. IMG URLs via triggerPixel, JS URLs via insertHtmlIntoIframe (script tag). + * Uses EVENT_TYPE_VIEWABLE trackers only (both TRACKER_METHOD_IMG and TRACKER_METHOD_JS). + * @param {Object} bid - bid with eventtrackers + */ +export function fireViewabilityPixels(bid) { + let { img, js } = getViewabilityTrackersFromBid(bid); + + const nativeResponse = bid.native && (bid?.native?.ortb || legacyPropertiesToOrtbNative(bid.native)); + if (nativeResponse && nativeResponse.eventtrackers) { + const filteredEventTrackers = filterEventTrackers(nativeResponse, bid); + const { [TRACKER_METHOD_IMG]: nativeImg = [], [TRACKER_METHOD_JS]: nativeJs = [] } = parseEventTrackers( + filteredEventTrackers || [] + )[EVENT_TYPE_VIEWABLE] || {}; + img = img.concat(Array.isArray(nativeImg) ? nativeImg : []); + js = js.concat(Array.isArray(nativeJs) ? nativeJs : []); + } + + img.forEach(triggerPixel); + if (js.length > 0) { + const markup = js.map(url => ``).join('\n'); + insertHtmlIntoIframe(markup); + } +} + +export function triggerBidViewable(bid) { + fireViewabilityPixels(bid); + // trigger respective bidder's onBidViewable handler + adapterManager.callBidViewableBidder(bid.adapterCode || bid.bidder, bid); + if (bid.deferBilling) { + adapterManager.triggerBilling(bid); + } + events.emit(EVENTS.BID_VIEWABLE, bid); +} diff --git a/libraries/categoryTranslationMapping/index.js b/libraries/categoryTranslationMapping/index.js deleted file mode 100644 index 13b10423450..00000000000 --- a/libraries/categoryTranslationMapping/index.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Provides mapping objects used by bidders for categoryTranslation type logic for Adpod feature - */ -export const APPNEXUS_CATEGORY_MAPPING = { - '1': 'IAB20-3', - '2': 'IAB18-5', - '3': 'IAB10-1', - '4': 'IAB2-3', - '5': 'IAB19-8', - '6': 'IAB22-1', - '7': 'IAB18-1', - '8': 'IAB12-3', - '9': 'IAB5-1', - '10': 'IAB4-5', - '11': 'IAB13-4', - '12': 'IAB8-7', - '13': 'IAB9-7', - '14': 'IAB7-1', - '15': 'IAB20-18', - '16': 'IAB10-7', - '17': 'IAB19-18', - '18': 'IAB13-6', - '19': 'IAB18-4', - '20': 'IAB1-5', - '21': 'IAB1-6', - '22': 'IAB3-4', - '23': 'IAB19-13', - '24': 'IAB22-2', - '25': 'IAB3-9', - '26': 'IAB17-18', - '27': 'IAB19-6', - '28': 'IAB1-7', - '29': 'IAB9-30', - '30': 'IAB20-7', - '31': 'IAB20-17', - '32': 'IAB7-32', - '33': 'IAB16-5', - '34': 'IAB19-34', - '35': 'IAB11-5', - '36': 'IAB12-3', - '37': 'IAB11-4', - '38': 'IAB12-3', - '39': 'IAB9-30', - '41': 'IAB7-44', - '42': 'IAB7-1', - '43': 'IAB7-30', - '50': 'IAB19-30', - '51': 'IAB17-12', - '52': 'IAB19-30', - '53': 'IAB3-1', - '55': 'IAB13-2', - '56': 'IAB19-30', - '57': 'IAB19-30', - '58': 'IAB7-39', - '59': 'IAB22-1', - '60': 'IAB7-39', - '61': 'IAB21-3', - '62': 'IAB5-1', - '63': 'IAB12-3', - '64': 'IAB20-18', - '65': 'IAB11-2', - '66': 'IAB17-18', - '67': 'IAB9-9', - '68': 'IAB9-5', - '69': 'IAB7-44', - '71': 'IAB22-3', - '73': 'IAB19-30', - '74': 'IAB8-5', - '78': 'IAB22-1', - '85': 'IAB12-2', - '86': 'IAB22-3', - '87': 'IAB11-3', - '112': 'IAB7-32', - '113': 'IAB7-32', - '114': 'IAB7-32', - '115': 'IAB7-32', - '118': 'IAB9-5', - '119': 'IAB9-5', - '120': 'IAB9-5', - '121': 'IAB9-5', - '122': 'IAB9-5', - '123': 'IAB9-5', - '124': 'IAB9-5', - '125': 'IAB9-5', - '126': 'IAB9-5', - '127': 'IAB22-1', - '132': 'IAB1-2', - '133': 'IAB19-30', - '137': 'IAB3-9', - '138': 'IAB19-3', - '140': 'IAB2-3', - '141': 'IAB2-1', - '142': 'IAB2-3', - '143': 'IAB17-13', - '166': 'IAB11-4', - '175': 'IAB3-1', - '176': 'IAB13-4', - '182': 'IAB8-9', - '183': 'IAB3-5' -}; diff --git a/libraries/dnt/index.js b/libraries/dnt/index.js index 1b03e848ca5..66aad467115 100644 --- a/libraries/dnt/index.js +++ b/libraries/dnt/index.js @@ -1,11 +1,7 @@ -function _getDNT(win) { - return win.navigator.doNotTrack === '1' || win.doNotTrack === '1' || win.navigator.msDoNotTrack === '1' || win.navigator.doNotTrack?.toLowerCase?.() === 'yes'; -} - -export function getDNT(win = window) { - try { - return _getDNT(win) || (win !== win.top && _getDNT(win.top)); - } catch (e) { - return false; - } +/** + * DNT was deprecated by W3C; Prebid no longer supports DNT signals. + * Keep this helper for backwards compatibility with adapters that still invoke getDNT(). + */ +export function getDNT() { + return false; } diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js index fef9aba37a4..b98b890d35a 100644 --- a/libraries/gptUtils/gptUtils.js +++ b/libraries/gptUtils/gptUtils.js @@ -1,5 +1,5 @@ import { CLIENT_SECTIONS } from '../../src/fpd/oneClient.js'; -import { compareCodeAndSlot, deepAccess, isGptPubadsDefined, uniques, isEmpty } from '../../src/utils.js'; +import { deepAccess, isGptPubadsDefined, uniques, isEmpty, isAdUnitCodeMatchingSlot } from '../../src/utils.js'; const slotInfoCache = new Map(); @@ -13,7 +13,10 @@ export function clearSlotInfoCache() { * @return {function} filter function */ export function isSlotMatchingAdUnitCode(adUnitCode) { - return (slot) => compareCodeAndSlot(slot, adUnitCode); + return (slot) => { + const match = isAdUnitCodeMatchingSlot(slot); + return match(adUnitCode); + } } /** @@ -35,7 +38,10 @@ export function getGptSlotForAdUnitCode(adUnitCode) { let matchingSlot; if (isGptPubadsDefined()) { // find the first matching gpt slot on the page - matchingSlot = window.googletag.pubads().getSlots().find(isSlotMatchingAdUnitCode(adUnitCode)); + matchingSlot = window.googletag.pubads().getSlots().find(slot => { + const match = isAdUnitCodeMatchingSlot(slot); + return match(adUnitCode); + }); } return matchingSlot; } diff --git a/libraries/magniteUtils/outstream.js b/libraries/magniteUtils/outstream.js index ab6f16cf89d..59daeacae0c 100644 --- a/libraries/magniteUtils/outstream.js +++ b/libraries/magniteUtils/outstream.js @@ -1,5 +1,6 @@ import { Renderer } from '../../src/Renderer.js'; import { logWarn } from '../../src/utils.js'; +import { getAdUnitElement } from '../../src/utils/adUnits.js'; export const DEFAULT_RENDERER_URL = 'https://video-outstream.rubiconproject.com/apex-2.3.7.js'; @@ -26,7 +27,7 @@ export function hideSmartAdServerIframe(adUnit) { export function renderBid(bid) { // hide existing ad units - let adUnitElement = document.getElementById(bid.adUnitCode); + let adUnitElement = getAdUnitElement(bid); if (!adUnitElement) { logWarn(`Magnite: unable to find ad unit element with id "${bid.adUnitCode}" for rendering.`); return; diff --git a/libraries/paapiTools/buyerOrigins.js b/libraries/paapiTools/buyerOrigins.js deleted file mode 100644 index ace9b7da073..00000000000 --- a/libraries/paapiTools/buyerOrigins.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - This list is several known buyer origins for PAAPI auctions. - Bidders should add anyone they like to it. - It is not intended to be comphensive nor maintained by the Core team. - Rather, Bid adapters should simply append additional constants whenever - the need arises in their adapter. - - The goal is to reduce expression of common constants over many - bid adapters attempting to define interestGroupBuyers - in advance of network traffic. - - Bidders should consider updating their interstGroupBuyer list - with server communication for auctions initiated after the first bid response. - - Known buyers without current importers are commented out. If you need one, uncomment it. -*/ - -export const BO_CSR_ONET = 'https://csr.onet.pl'; -// export const BO_DOUBLECLICK_GOOGLEADS = 'https://googleads.g.doubleclick.net'; -// export const BO_DOUBLECLICK_TD = 'https://td.doubleclick.net'; -// export const BO_RTBHOUSE = 'https://f.creativecdn.com'; -// export const BO_CRITEO_US = 'https://fledge.us.criteo.com'; -// export const BO_CRITEO_EU = 'https://fledge.eu.criteo.com'; -// export const BO_CRITEO_AS = 'https://fledge.as.criteo.com'; -// export const BO_CRITEO_GRID_MERCURY = 'https://grid-mercury.criteo.com'; -// export const BO_CRITEO_BIDSWITCH_TRADR = 'https://tradr.bsw-sb.criteo.com'; -// export const BO_CRITEO_BIDSWITCH_SANDBOX = 'https://dsp-paapi-sandbox.bsw-ig.criteo.com'; -// export const BO_APPSPOT = 'https://fledge-buyer-testing-1.uc.r.appspot.com'; -// export const BO_OPTABLE = 'https://ads.optable.co'; -// export const BO_ADROLL = 'https://x.adroll.com'; -// export const BO_ADFORM = 'https://a2.adform.net'; -// export const BO_RETARGETLY = 'https://cookieless-campaign.prd-00.retargetly.com'; -// export const BO_AUDIGENT = 'https://proton.ad.gt'; -// export const BO_YAHOO = 'https://pa.ybp.yahoo.com'; -// export const BO_DOTOMI = 'https://usadmm.dotomi.com'; diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js index 0de3a9c257d..21605c194a2 100644 --- a/libraries/percentInView/percentInView.js +++ b/libraries/percentInView/percentInView.js @@ -1,5 +1,8 @@ import { getWinDimensions, inIframe } from '../../src/utils.js'; import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js'; +import { defer, PbPromise, delay } from '../../src/utils/promise.js'; +import { startAuction } from '../../src/prebid.js'; +import { getAdUnitElement } from '../../src/utils/adUnits.js'; /** * return the offset between the given window's viewport and the top window's. @@ -24,8 +27,8 @@ export function getViewportOffset(win = window) { return { x, y }; } -export function getBoundingBox(element, { w, h } = {}) { - let { width, height, left, top, right, bottom, x, y } = getBoundingClientRect(element); +function applySize(bbox, { w, h }) { + let { width, height, left, top, right, bottom, x, y } = bbox; if ((width === 0 || height === 0) && w && h) { width = w; @@ -37,6 +40,10 @@ export function getBoundingBox(element, { w, h } = {}) { return { width, height, left, top, right, bottom, x, y }; } +export function getBoundingBox(element, { w, h } = {}) { + return applySize(getBoundingClientRect(element), { w, h }); +} + function getIntersectionOfRects(rects) { const bbox = { left: rects[0].left, right: rects[0].right, top: rects[0].top, bottom: rects[0].bottom @@ -64,7 +71,7 @@ function getIntersectionOfRects(rects) { return bbox; } -export const percentInView = (element, { w, h } = {}) => { +const percentInViewStatic = (element, { w, h } = {}) => { const elementBoundingBox = getBoundingBox(element, { w, h }); // when in an iframe, the bounding box is relative to the iframe's viewport @@ -102,6 +109,103 @@ export const percentInView = (element, { w, h } = {}) => { return 0; } +/** + * A wrapper around an IntersectionObserver that keeps track of the latest IntersectionEntry that was observed + * for each observed element. + * + * @param mkObserver + */ +export function intersections(mkObserver) { + const intersections = new WeakMap(); + let next = defer(); + function observerCallback(entries) { + entries.forEach(entry => { + if ((intersections.get(entry.target)?.time ?? -1) < entry.time) { + intersections.set(entry.target, entry) + next.resolve(); + next = defer(); + } + }) + } + + let obs = null; + try { + obs = mkObserver(observerCallback); + } catch (e) { + // IntersectionObserver not supported + } + + async function waitFor(element) { + const intersection = getIntersection(element); + if (intersection != null) { + return intersection; + } else { + return next.promise.then(() => waitFor(element)); + } + } + /** + * Observe the given element; returns a promise to the first available intersection observed for it. + */ + async function observe(element) { + if (obs != null && !intersections.has(element)) { + obs.observe(element); + intersections.set(element, null); + return waitFor(element); + } else { + return PbPromise.resolve(getIntersection(element)); + } + } + + /** + * Return the latest intersection that was observed for the given element. + */ + function getIntersection(element) { + return intersections.get(element); + } + + return { + observe, + getIntersection, + } +} + +export const viewportIntersections = intersections((callback) => new IntersectionObserver(callback, { + // update percentInView when visibility varies by 1% + threshold: Array.from({ length: 101 }, (e, i) => i / 100) +})); + +export function mkIntersectionHook(intersections = viewportIntersections) { + return function (next, request) { + PbPromise.race([ + PbPromise.allSettled((request.adUnits ?? []).map(adUnit => + intersections.observe(getAdUnitElement(adUnit)) + )), + // according to MDN, with threshold 0 "the callback will be run as soon as the target element intersects or touches the boundary of the root, even if no pixels are yet visible" + // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API + // However, browsers appear to run it even when the element is outside the DOM + // just to be sure, cap the amount of time we wait for intersections + delay(20) + ]).then(() => next.call(this, request)); + } +} + +startAuction.before(mkIntersectionHook()); + +export function percentInView(element, { w, h } = {}) { + const intersection = viewportIntersections.getIntersection(element); + if (intersection == null) { + viewportIntersections.observe(element); + return percentInViewStatic(element, { w, h }); + } else { + const adjusted = applySize(intersection.boundingClientRect, { w, h }); + if (adjusted.width !== intersection.boundingClientRect.width || adjusted.height !== intersection.boundingClientRect.height) { + // use w/h override + return percentInViewStatic(element, { w, h }); + } + return intersection.isIntersecting ? intersection.intersectionRatio * 100 : 0; + } +} + /** * Checks if viewability can be measured for an element * @param {HTMLElement} element - DOM element to check diff --git a/libraries/placementPositionInfo/placementPositionInfo.js b/libraries/placementPositionInfo/placementPositionInfo.js index 884f8e7c5f8..d0387c4bb0b 100644 --- a/libraries/placementPositionInfo/placementPositionInfo.js +++ b/libraries/placementPositionInfo/placementPositionInfo.js @@ -1,10 +1,10 @@ import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js'; import { canAccessWindowTop, cleanObj, getWinDimensions, getWindowSelf, getWindowTop } from '../../src/utils.js'; import { getViewability, getViewportOffset } from '../percentInView/percentInView.js'; +import { getAdUnitElement } from '../../src/utils/adUnits.js'; export function getPlacementPositionUtils() { const topWin = canAccessWindowTop() ? getWindowTop() : getWindowSelf(); - const selfWin = getWindowSelf(); const getViewportHeight = () => { const dim = getWinDimensions(); @@ -49,7 +49,7 @@ export function getPlacementPositionUtils() { }; function getPlacementInfo(bidReq) { - const element = selfWin.document.getElementById(bidReq.adUnitCode); + const element = getAdUnitElement(bidReq); const frameOffset = getViewportOffset(); const { distanceToView, elementHeight } = getViewableDistance(element, frameOffset); diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js index 5805f2c8264..c818dc8bc21 100644 --- a/libraries/riseUtils/index.js +++ b/libraries/riseUtils/index.js @@ -14,7 +14,6 @@ import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; import { config } from '../../src/config.js'; import { getDNT } from '../dnt/index.js'; import { ADAPTER_VERSION, DEFAULT_CURRENCY, DEFAULT_TTL, SUPPORTED_AD_TYPES } from './constants.js'; - import { getGlobalVarName } from '../../src/buildOptions.js'; export const makeBaseSpec = (baseUrl, modes) => { diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js index 331cf5bc4b1..f7a4bd3cc7a 100644 --- a/libraries/vidazooUtils/bidderUtils.js +++ b/libraries/vidazooUtils/bidderUtils.js @@ -339,13 +339,6 @@ export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidder data.gppSid = bidderRequest.ortb2.regs.gpp_sid; } - if (bidderRequest.paapi?.enabled) { - const fledge = bidderRequest?.ortb2Imp?.ext?.ae; - if (fledge) { - data.fledge = fledge; - } - } - const api = mediaTypes?.video?.api || []; if (api.includes(7)) { const sourceExt = bidderRequest?.ortb2?.source?.ext; diff --git a/metadata/disclosures/prebid/categoryTranslation.json b/metadata/disclosures/prebid/categoryTranslation.json deleted file mode 100644 index 82934ef440e..00000000000 --- a/metadata/disclosures/prebid/categoryTranslation.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "disclosures": [ - { - "identifier": "iabToFwMappingkey", - "type": "web", - "domains": ["*"], - "purposes": [ - 1 - ] - }, - { - "identifier": "iabToFwMappingkeyPub", - "type": "web", - "domains": ["*"], - "purposes": [ - 1 - ] - } - ], - "domains": [ - { - "domain": "*", - "use": "Category translation mappings are cached in localStorage" - } - ] -} diff --git a/metadata/modules.json b/metadata/modules.json index 1a7ea39c1ac..757b2215bd7 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -5943,13 +5943,6 @@ "disclosureURL": null, "aliasOf": null }, - { - "componentType": "userId", - "componentName": "quantcastId", - "gvlid": "11", - "disclosureURL": null, - "aliasOf": null - }, { "componentType": "userId", "componentName": "rewardedInterestId", @@ -6357,4 +6350,4 @@ "gvlid": null } ] -} \ No newline at end of file +} diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json deleted file mode 100644 index 985b23c6e2b..00000000000 --- a/metadata/modules/categoryTranslation.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": { - "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json": { - "timestamp": "2026-03-12T22:08:10.747Z", - "disclosures": [ - { - "identifier": "iabToFwMappingkey", - "type": "web", - "purposes": [ - 1 - ] - }, - { - "identifier": "iabToFwMappingkeyPub", - "type": "web", - "purposes": [ - 1 - ] - } - ] - } - }, - "components": [ - { - "componentType": "prebid", - "componentName": "categoryTranslation", - "disclosureURL": "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json" - } - ] -} \ No newline at end of file diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json index bbaf8d3b5c6..81f572ce634 100644 --- a/metadata/modules/quantcastBidAdapter.json +++ b/metadata/modules/quantcastBidAdapter.json @@ -48,4 +48,4 @@ "disclosureURL": "https://www.quantcast.com/.well-known/devicestorage.json" } ] -} \ No newline at end of file +} diff --git a/metadata/modules/ringieraxelspringerBidAdapter.json b/metadata/modules/ringieraxelspringerBidAdapter.json deleted file mode 100644 index 1ca1c72523f..00000000000 --- a/metadata/modules/ringieraxelspringerBidAdapter.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, - "components": [ - { - "componentType": "bidder", - "componentName": "das", - "aliasOf": null, - "gvlid": null, - "disclosureURL": null - }, - { - "componentType": "bidder", - "componentName": "ringieraxelspringer", - "aliasOf": "das", - "gvlid": null, - "disclosureURL": null - } - ] -} \ No newline at end of file diff --git a/metadata/overrides.mjs b/metadata/overrides.mjs index 214f778485c..869069f94d3 100644 --- a/metadata/overrides.mjs +++ b/metadata/overrides.mjs @@ -16,6 +16,5 @@ export default { operaadsIdSystem: 'operaId', relevadRtdProvider: 'RelevadRTDModule', sirdataRtdProvider: 'SirdataRTDModule', - fanBidAdapter: 'freedomadnetwork', - ringieraxelspringerBidAdapter: 'das' + fanBidAdapter: 'freedomadnetwork' } diff --git a/modules/.submodules.json b/modules/.submodules.json index c6b0db32e78..244104c93da 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -14,7 +14,6 @@ "czechAdIdSystem", "dacIdSystem", "deepintentDpesIdSystem", - "dmdIdSystem", "euidIdSystem", "fabrickIdSystem", "freepassIdSystem", @@ -50,7 +49,6 @@ "pubProvidedIdSystem", "publinkIdSystem", "pubmaticIdSystem", - "quantcastIdSystem", "rewardedInterestIdSystem", "sharedIdSystem", "taboolaIdSystem", @@ -65,10 +63,6 @@ "yandexIdSystem", "zeotapIdPlusIdSystem" ], - "adpod": [ - "freeWheelAdserverVideo", - "gamAdpod" - ], "rtdModule": [ "1plusXRtdProvider", "51DegreesRtdProvider", @@ -143,10 +137,6 @@ "jwplayerVideoProvider", "videojsVideoProvider", "adplayerproVideoProvider" - ], - "paapi": [ - "paapiForGpt", - "topLevelPaapi" ] } } diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index 06beb2bfa08..16a55eba026 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -401,8 +401,7 @@ function analyticEventHandler({ eventType, args }) { case EVENTS.BID_REJECTED: onBidRejected(args); break; - case EVENTS.NO_BID: - case EVENTS.SEAT_NON_BID: + case EVENTS.NO_BID: // todo: need to also consider pbsanalytics where nonbid is not null setCachedBidStatus(args.auctionId, args.bidId, BidStatus.NOBID); break; case EVENTS.BIDDER_ERROR: diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index d072cd7c1f7..5e50f6a8792 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -17,6 +17,7 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { percentInView } from '../libraries/percentInView/percentInView.js'; import { getMinSize } from '../libraries/sizeUtils/sizeUtils.js'; import { isIframe } from '../libraries/omsUtils/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; // **************************** UTILS ************************** // const BIDDER_CODE = '33across'; @@ -373,7 +374,7 @@ function _getProduct(bidRequest) { // BUILD REQUESTS: BANNER function _buildBannerORTB(bidRequest) { const bannerAdUnit = deepAccess(bidRequest, 'mediaTypes.banner', {}); - const element = _getAdSlotHTMLElement(bidRequest.adUnitCode); + const element = _getAdSlotHTMLElement(bidRequest); const sizes = _transformSizes(bannerAdUnit.sizes); @@ -478,6 +479,7 @@ function _getViewability(element, topWin, { w, h } = {}) { : 0; } +// TODO use utils/adUnits once that's unified in 11 function _mapAdUnitPathToElementId(adUnitCode) { if (isGptPubadsDefined()) { // eslint-disable-next-line no-undef @@ -500,9 +502,9 @@ function _mapAdUnitPathToElementId(adUnitCode) { return null; } -function _getAdSlotHTMLElement(adUnitCode) { - return document.getElementById(adUnitCode) || - document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +function _getAdSlotHTMLElement(bidRequest) { + return getAdUnitElement(bidRequest) || + document.getElementById(_mapAdUnitPathToElementId(bidRequest.adUnitCode)); } /** diff --git a/modules/AsteriobidPbmAnalyticsAdapter.js b/modules/AsteriobidPbmAnalyticsAdapter.js index fbd5a885b99..5dc953174c9 100644 --- a/modules/AsteriobidPbmAnalyticsAdapter.js +++ b/modules/AsteriobidPbmAnalyticsAdapter.js @@ -231,9 +231,6 @@ function handleEvent(eventType, eventArgs) { case EVENTS.REQUEST_BIDS: { break; } - case EVENTS.ADD_AD_UNITS: { - break; - } case EVENTS.AD_RENDER_FAILED: { pmEvent.bid = eventArgs.bid; pmEvent.message = eventArgs.message; diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 9d04e166600..3da9746128b 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -25,6 +25,7 @@ import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; import { validateOrtbFields } from '../src/prebid.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; @@ -466,7 +467,7 @@ const OUTSTREAM_RENDERER = { return; } - const el = document.getElementById(bid.adUnitCode); + const el = getAdUnitElement(bid); renderer.bootstrap(config, el, override); }, diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js index c3b1a881f98..ac1246fdc0a 100644 --- a/modules/adagioRtdProvider.js +++ b/modules/adagioRtdProvider.js @@ -495,6 +495,7 @@ function getElementFromTopWindow(element, currentWindow) { }; function getSlotPosition(divId) { + // TODO: this should use getAdUnitElement if (!isSafeFrameWindow() && !canAccessWindowTop()) { return ''; } diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index ed4b26da529..3e22acb9f08 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { _each, contains, @@ -416,9 +415,6 @@ function makeDevice(fpd) { 'js': 1, 'language': getLanguage() }, fpd.device || {}); - if (getDNT()) { - device.dnt = 1; - } return { device: device }; } diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index c991d776976..2a4e61f9f79 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -36,6 +36,8 @@ import { safeJSONParse } from '../src/utils.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { viewportIntersections } from '../libraries/percentInView/percentInView.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const MODULE_NAME = 'adloox'; const MODULE = `${MODULE_NAME}RtdProvider`; @@ -188,10 +190,9 @@ function getTargetingData(adUnitArray, config, userConsent, auction) { if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_${k}`] = v; }); - // ATF results shamelessly exfiltrated from intersectionRtdProvider - const bid = unit.bids.find(bid => !!bid.intersection); - if (bid) { - const v = val(config.params.thresholds.filter(t => t <= (bid.intersection.intersectionRatio * 100))); + const intersection = viewportIntersections.getIntersection(getAdUnitElement(unit)); + if (intersection) { + const v = val(config.params.thresholds.filter(t => t <= (intersection.intersectionRatio * 100))); if (v) targeting[unit.code][`${ADSERVER_TARGETING_PREFIX}_atf`] = v; } }); diff --git a/modules/adnuntiusAnalyticsAdapter.js b/modules/adnuntiusAnalyticsAdapter.js index a2df85ad3f1..a2fcc9018fd 100644 --- a/modules/adnuntiusAnalyticsAdapter.js +++ b/modules/adnuntiusAnalyticsAdapter.js @@ -3,6 +3,7 @@ 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 { getAdUnitElement } from '../src/utils/adUnits.js'; const URL = 'https://analytics.adnuntius.com/prebid'; const REQUEST_SENT = 1; @@ -36,7 +37,7 @@ const adnAnalyticsAdapter = Object.assign(adapter({ url: '', analyticsType: 'end cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; - const container = document.getElementById(bidReq.adUnitCode); + const container = getAdUnitElement(bidReq); const containerAttr = container ? container.getAttribute('data-adunitid') : undefined; const adUnitId = containerAttr || undefined; diff --git a/modules/adoceanBidAdapter.js b/modules/adoceanBidAdapter.js index 410b3c92338..e6b89272bc7 100644 --- a/modules/adoceanBidAdapter.js +++ b/modules/adoceanBidAdapter.js @@ -54,17 +54,6 @@ function buildRequest(bid, gdprConsent) { } payload.spots = 1; } - if (bid.mediaTypes.video.context === 'adpod') { - const durationRangeSec = bid.mediaTypes.video.durationRangeSec; - if (!bid.mediaTypes.video.adPodDurationSec || !isArray(durationRangeSec) || durationRangeSec.length === 0) { - return; - } - const spots = calculateAdPodSpotsNumber(bid.mediaTypes.video.adPodDurationSec, bid.mediaTypes.video.durationRangeSec); - const maxDuration = Math.max(...durationRangeSec); - payload.dur = bid.mediaTypes.video.adPodDurationSec; - payload.maxdur = maxDuration; - payload.spots = spots; - } } else if (bid.mediaTypes.banner) { payload.aosize = parseSizesInput(bid.mediaTypes.banner.sizes).join(','); } @@ -77,12 +66,6 @@ function buildRequest(bid, gdprConsent) { }; } -function calculateAdPodSpotsNumber(adPodDurationSec, durationRangeSec) { - const minAllowedDuration = Math.min(...durationRangeSec); - const numberOfSpots = Math.floor(adPodDurationSec / minAllowedDuration); - return numberOfSpots; -} - function interpretResponse(placementResponse, bidRequest, bids) { const requestId = bidRequest.bidIdMap[placementResponse.id]; if (!placementResponse.error && requestId) { @@ -134,9 +117,6 @@ export const spec = { if (bid.mediaTypes.video.context === 'instream') { return true; } - if (bid.mediaTypes.video.context === 'adpod') { - return !bid.mediaTypes.video.requireExactDuration; - } } return false; }, diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index 121c9960ade..6105463d0f3 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -7,6 +7,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { isArray, isBoolean, isFn, isPlainObject, isStr, logError, replaceAuctionPrice } from '../src/utils.js'; import { OUTSTREAM } from '../src/video.js'; import { NATIVE_ASSETS_IDS as NATIVE_ID_MAPPING, NATIVE_ASSETS as NATIVE_PLACEMENTS } from '../libraries/braveUtils/nativeAssets.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -495,7 +496,7 @@ function buildRenderer(bid, mediaType) { ad.renderer.push(() => { const domContainer = container ? document.querySelector(container) - : document.getElementById(adUnitCode); + : getAdUnitElement(ad) const player = new window.VASTPlayer(domContainer); diff --git a/modules/adpod.js b/modules/adpod.js deleted file mode 100644 index b123ac47e8d..00000000000 --- a/modules/adpod.js +++ /dev/null @@ -1,657 +0,0 @@ -/** - * This module houses the functionality to evaluate and process adpod adunits/bids. Specifically there are several hooked functions, - * that either supplement the base function (ie to check something additional or unique to adpod objects) or to replace the base function - * entirely when appropriate. - * - * Brief outline of each hook: - * - `callPrebidCacheHook` - for any adpod bids, this function will temporarily hold them in a queue in order to send the bids to Prebid Cache in bulk - * - `checkAdUnitSetupHook` - evaluates the adUnits to ensure that required fields for adpod adUnits are present. Invalid adpod adUntis are removed from the array. - * - `checkVideoBidSetupHook` - evaluates the adpod bid returned from an adaptor/bidder to ensure required fields are populated; also initializes duration bucket field. - * - * To initialize the module, there is an `initAdpodHooks()` function that should be imported and executed by a corresponding `...AdServerVideo` - * module that designed to support adpod video type ads. This import process allows this module to effectively act as a sub-module. - */ - -import { - deepAccess, - generateUUID, - groupBy, - isArray, - isArrayOfNums, - isNumber, - isPlainObject, - logError, - logInfo, - logWarn -} from '../src/utils.js'; -import { - addBidToAuction, - AUCTION_IN_PROGRESS, - getPriceByGranularity, - getPriceGranularity -} from '../src/auction.js'; -import { checkAdUnitSetup } from '../src/prebid.js'; -import { checkVideoBidSetup } from '../src/video.js'; -import { getHook, module, setupBeforeHookFnOnce } from '../src/hook.js'; -import { store } from '../src/videoCache.js'; -import { config } from '../src/config.js'; -import { ADPOD } from '../src/mediaTypes.js'; -import { auctionManager } from '../src/auctionManager.js'; -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'; - -let queueTimeDelay = 50; -let queueSizeLimit = 5; -const bidCacheRegistry = createBidCacheRegistry(); - -/** - * Create a registry object that stores/manages bids while be held in queue for Prebid Cache. - * @returns registry object with defined accessor functions - */ -function createBidCacheRegistry() { - const registry = {}; - - function setupRegistrySlot(auctionId) { - registry[auctionId] = {}; - registry[auctionId].bidStorage = new Set(); - registry[auctionId].queueDispatcher = createDispatcher(queueTimeDelay); - registry[auctionId].initialCacheKey = generateUUID(); - } - - return { - addBid: function (bid) { - // create parent level object based on auction ID (in case there are concurrent auctions running) to store objects for that auction - if (!registry[bid.auctionId]) { - setupRegistrySlot(bid.auctionId); - } - registry[bid.auctionId].bidStorage.add(bid); - }, - removeBid: function (bid) { - registry[bid.auctionId].bidStorage.delete(bid); - }, - getBids: function (bid) { - return registry[bid.auctionId] && registry[bid.auctionId].bidStorage.values(); - }, - getQueueDispatcher: function (bid) { - return registry[bid.auctionId] && registry[bid.auctionId].queueDispatcher; - }, - setupInitialCacheKey: function (bid) { - if (!registry[bid.auctionId]) { - registry[bid.auctionId] = {}; - registry[bid.auctionId].initialCacheKey = generateUUID(); - } - }, - getInitialCacheKey: function (bid) { - return registry[bid.auctionId] && registry[bid.auctionId].initialCacheKey; - } - } -} - -/** - * Creates a function that when called updates the bid queue and extends the running timer (when called subsequently). - * Once the time threshold for the queue (defined by queueSizeLimit) is reached, the queue will be flushed by calling the `firePrebidCacheCall` function. - * If there is a long enough time between calls (based on timeoutDration), the queue will automatically flush itself. - * @param {Number} timeoutDuration number of milliseconds to pass before timer expires and current bid queue is flushed - * @returns {Function} - */ -function createDispatcher(timeoutDuration) { - let timeout; - let counter = 1; - - return function (auctionInstance, bidListArr, afterBidAdded, killQueue) { - const context = this; - - var callbackFn = function () { - firePrebidCacheCall.call(context, auctionInstance, bidListArr, afterBidAdded); - }; - - clearTimeout(timeout); - - if (!killQueue) { - // want to fire off the queue if either: size limit is reached or time has passed since last call to dispatcher - if (counter === queueSizeLimit) { - counter = 1; - callbackFn(); - } else { - counter++; - timeout = setTimeout(callbackFn, timeoutDuration); - } - } else { - counter = 1; - } - }; -} - -function getPricePartForAdpodKey(bid) { - let pricePart - const prioritizeDeals = config.getConfig('adpod.prioritizeDeals'); - if (prioritizeDeals && deepAccess(bid, 'video.dealTier')) { - const adpodDealPrefix = config.getConfig(`adpod.dealTier.${bid.bidderCode}.prefix`); - pricePart = (adpodDealPrefix) ? adpodDealPrefix + deepAccess(bid, 'video.dealTier') : deepAccess(bid, 'video.dealTier'); - } else { - const granularity = getPriceGranularity(bid); - pricePart = getPriceByGranularity(granularity)(bid); - } - return pricePart -} - -/** - * This function reads certain fields from the bid to generate a specific key used for caching the bid in Prebid Cache - * @param {Object} bid bid object to update - * @param {Boolean} brandCategoryExclusion value read from setConfig; influences whether category is required or not - */ -function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { - const initialCacheKey = bidCacheRegistry.getInitialCacheKey(bid); - const duration = deepAccess(bid, 'video.durationBucket'); - const pricePart = getPricePartForAdpodKey(bid); - let pcd; - - if (brandCategoryExclusion) { - const category = deepAccess(bid, 'meta.adServerCatId'); - pcd = `${pricePart}_${category}_${duration}s`; - } else { - pcd = `${pricePart}_${duration}s`; - } - - if (!bid.adserverTargeting) { - bid.adserverTargeting = {}; - } - bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] = pcd; - bid.adserverTargeting[TARGETING_KEY_CACHE_ID] = initialCacheKey; - bid.videoCacheKey = initialCacheKey; - bid.customCacheKey = `${pcd}_${initialCacheKey}`; -} - -/** - * Updates the running queue for the associated auction. - * Does a check to ensure the auction is still running; if it's not - the previously running queue is killed. - * @param {*} auctionInstance running context of the auction - * @param {Object} bidResponse bid object being added to queue - * @param {Function} afterBidAdded callback function used when Prebid Cache responds - */ -function updateBidQueue(auctionInstance, bidResponse, afterBidAdded) { - const bidListIter = bidCacheRegistry.getBids(bidResponse); - - if (bidListIter) { - const bidListArr = Array.from(bidListIter); - const callDispatcher = bidCacheRegistry.getQueueDispatcher(bidResponse); - const killQueue = !!(auctionInstance.getAuctionStatus() !== AUCTION_IN_PROGRESS); - callDispatcher(auctionInstance, bidListArr, afterBidAdded, killQueue); - } else { - logWarn('Attempted to cache a bid from an unknown auction. Bid:', bidResponse); - } -} - -/** - * Small helper function to remove bids from internal storage; normally b/c they're about to sent to Prebid Cache for processing. - * @param {Array[Object]} bidResponses list of bids to remove - */ -function removeBidsFromStorage(bidResponses) { - for (let i = 0; i < bidResponses.length; i++) { - bidCacheRegistry.removeBid(bidResponses[i]); - } -} - -/** - * This function will send a list of bids to Prebid Cache. It also removes the same bids from the internal bidCacheRegistry - * to maintain which bids are in queue. - * If the bids are successfully cached, they will be added to the respective auction. - * @param {*} auctionInstance running context of the auction - * @param {Array[Object]} bidList list of bid objects that need to be sent to Prebid Cache - * @param {Function} afterBidAdded callback function used when Prebid Cache responds - */ -function firePrebidCacheCall(auctionInstance, bidList, afterBidAdded) { - // remove entries now so other incoming bids won't accidentally have a stale version of the list while PBC is processing the current submitted list - removeBidsFromStorage(bidList); - - store(bidList, function (error, cacheIds) { - if (error) { - logWarn(`Failed to save to the video cache: ${error}. Video bid(s) must be discarded.`); - } 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 - if (cacheIds[i].uuid !== '') { - addBidToAuction(auctionInstance, bidList[i]); - } else { - logInfo(`Detected a bid was not cached because the custom key was already registered. Attempted to use key: ${bidList[i].customCacheKey}. Bid was: `, bidList[i]); - } - afterBidAdded(); - } - } - }); -} - -/** - * This is the main hook function to handle adpod bids; maintains the logic to temporarily hold bids in a queue in order to send bulk requests to Prebid Cache. - * @param {Function} fn reference to original function (used by hook logic) - * @param {*} auctionInstance running context of the auction - * @param {Object} bidResponse incoming bid; if adpod, will be processed through hook function. If not adpod, returns to original function. - * @param {Function} afterBidAdded callback function used when Prebid Cache responds - * @param {Object} videoConfig mediaTypes.video from the bid's adUnit - */ -export function callPrebidCacheHook(fn, auctionInstance, bidResponse, afterBidAdded, videoConfig) { - if (videoConfig && videoConfig.context === ADPOD) { - const brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); - const adServerCatId = deepAccess(bidResponse, 'meta.adServerCatId'); - if (!adServerCatId && brandCategoryExclusion) { - logWarn('Detected a bid without meta.adServerCatId while setConfig({adpod.brandCategoryExclusion}) was enabled. This bid has been rejected:', bidResponse); - afterBidAdded(); - } else { - if (config.getConfig('adpod.deferCaching') === false) { - bidCacheRegistry.addBid(bidResponse); - attachPriceIndustryDurationKeyToBid(bidResponse, brandCategoryExclusion); - - updateBidQueue(auctionInstance, bidResponse, afterBidAdded); - } else { - // generate targeting keys for bid - bidCacheRegistry.setupInitialCacheKey(bidResponse); - attachPriceIndustryDurationKeyToBid(bidResponse, brandCategoryExclusion); - - // add bid to auction - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); - } - } - } else { - fn.call(this, auctionInstance, bidResponse, afterBidAdded, videoConfig); - } -} - -/** - * This hook function will review the adUnit setup and verify certain required values are present in any adpod adUnits. - * If the fields are missing or incorrectly setup, the adUnit is removed from the list. - * @param {Function} fn reference to original function (used by hook logic) - * @param {Array[Object]} adUnits list of adUnits to be evaluated - * @returns {Array[Object]} list of adUnits that passed the check - */ -export function checkAdUnitSetupHook(fn, adUnits) { - const goodAdUnits = adUnits.filter(adUnit => { - const mediaTypes = deepAccess(adUnit, 'mediaTypes'); - const videoConfig = deepAccess(mediaTypes, 'video'); - if (videoConfig && videoConfig.context === ADPOD) { - // run check to see if other mediaTypes are defined (ie multi-format); reject adUnit if so - if (Object.keys(mediaTypes).length > 1) { - logWarn(`Detected more than one mediaType in adUnitCode: ${adUnit.code} while attempting to define an 'adpod' video adUnit. 'adpod' adUnits cannot be mixed with other mediaTypes. This adUnit will be removed from the auction.`); - return false; - } - - let errMsg = `Detected missing or incorrectly setup fields for an adpod adUnit. Please review the following fields of adUnitCode: ${adUnit.code}. This adUnit will be removed from the auction.`; - - const playerSize = !!( - ( - videoConfig.playerSize && ( - isArrayOfNums(videoConfig.playerSize, 2) || ( - isArray(videoConfig.playerSize) && videoConfig.playerSize.every(sz => isArrayOfNums(sz, 2)) - ) - ) - ) || (videoConfig.sizeConfig) - ); - const adPodDurationSec = !!(videoConfig.adPodDurationSec && isNumber(videoConfig.adPodDurationSec) && videoConfig.adPodDurationSec > 0); - const durationRangeSec = !!(videoConfig.durationRangeSec && isArrayOfNums(videoConfig.durationRangeSec) && videoConfig.durationRangeSec.every(range => range > 0)); - - if (!playerSize || !adPodDurationSec || !durationRangeSec) { - errMsg += (!playerSize) ? '\nmediaTypes.video.playerSize' : ''; - errMsg += (!adPodDurationSec) ? '\nmediaTypes.video.adPodDurationSec' : ''; - errMsg += (!durationRangeSec) ? '\nmediaTypes.video.durationRangeSec' : ''; - logWarn(errMsg); - return false; - } - } - return true; - }); - adUnits = goodAdUnits; - fn.call(this, adUnits); -} - -/** - * This check evaluates the incoming bid's `video.durationSeconds` field and tests it against specific logic depending on adUnit config. Summary of logic below: - * when adUnit.mediaTypes.video.requireExactDuration is true - * - only bids that exactly match those listed values are accepted (don't round at all). - * - populate the `bid.video.durationBucket` field with the matching duration value - * when adUnit.mediaTypes.video.requireExactDuration is false - * - round the duration to the next highest specified duration value based on adunit. If the duration is above a range within a set buffer, that bid falls down into that bucket. - * (eg if range was [5, 15, 30] -> 2s is rounded to 5s; 17s is rounded back to 15s; 18s is rounded up to 30s) - * - if the bid is above the range of the listed durations (and outside the buffer), reject the bid - * - set the rounded duration value in the `bid.video.durationBucket` field for accepted bids - * @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; - const bidDuration = deepAccess(bidResponse, 'video.durationSeconds'); - const adUnitRanges = videoMediaType.durationRangeSec; - adUnitRanges.sort((a, b) => a - b); // ensure the ranges are sorted in numeric order - - if (!videoMediaType.requireExactDuration) { - const max = Math.max(...adUnitRanges); - if (bidDuration <= (max + buffer)) { - const nextHighestRange = ((adUnitRanges) || []).find(range => (range + buffer) >= bidDuration); - bidResponse.video.durationBucket = nextHighestRange; - } else { - logWarn(`Detected a bid with a duration value outside the accepted ranges specified in adUnit.mediaTypes.video.durationRangeSec. Rejecting bid: `, bidResponse); - return false; - } - } else { - if (((adUnitRanges) || []).find(range => range === bidDuration)) { - bidResponse.video.durationBucket = bidDuration; - } else { - logWarn(`Detected a bid with a duration value not part of the list of accepted ranges specified in adUnit.mediaTypes.video.durationRangeSec. Exact match durations must be used for this adUnit. Rejecting bid: `, bidResponse); - return false; - } - } - return true; -} - -/** - * This hooked function evaluates an adpod bid and determines if the required fields are present. - * If it's found to not be an adpod bid, it will return to original function via hook logic - * @param {Function} fn reference to original function (used by hook logic) - * @param {Object} bid incoming bid object - * @param {Object} adUnit adUnit object of associated bid - * @param {Object} videoMediaType copy of the `bidRequest.mediaTypes.video` object; used in original function - * @param {String} context value of the `bidRequest.mediaTypes.video.context` field; used in original function - * @returns {boolean} this return is only used for adpod bids - */ -export function checkVideoBidSetupHook(fn, bid, adUnit, videoMediaType, context) { - if (context === ADPOD) { - let result = true; - const brandCategoryExclusion = config.getConfig('adpod.brandCategoryExclusion'); - if (brandCategoryExclusion && !deepAccess(bid, 'meta.primaryCatId')) { - result = false; - } - - if (deepAccess(bid, 'video')) { - if (!deepAccess(bid, 'video.context') || bid.video.context !== ADPOD) { - result = false; - } - - if (!deepAccess(bid, 'video.durationSeconds') || bid.video.durationSeconds <= 0) { - result = false; - } else { - const isBidGood = checkBidDuration(videoMediaType, bid); - if (!isBidGood) result = false; - } - } - - if (!config.getConfig('cache.url') && bid.vastXml && !bid.vastUrl) { - logError(` - This bid contains only vastXml and will not work when a prebid cache url is not specified. - Try enabling prebid cache with pbjs.setConfig({ cache: {url: "..."} }); - `); - result = false; - }; - - fn.bail(result); - } else { - fn.call(this, bid, adUnit, videoMediaType, context); - } -} - -/** - * This function reads the (optional) settings for the adpod as set from the setConfig() - * @param {Object} config contains the config settings for adpod module - */ -export function adpodSetConfig(config) { - if (config.bidQueueTimeDelay !== undefined) { - if (typeof config.bidQueueTimeDelay === 'number' && config.bidQueueTimeDelay > 0) { - queueTimeDelay = config.bidQueueTimeDelay; - } else { - logWarn(`Detected invalid value for adpod.bidQueueTimeDelay in setConfig; must be a positive number. Using default: ${queueTimeDelay}`) - } - } - - if (config.bidQueueSizeLimit !== undefined) { - if (typeof config.bidQueueSizeLimit === 'number' && config.bidQueueSizeLimit > 0) { - queueSizeLimit = config.bidQueueSizeLimit; - } else { - logWarn(`Detected invalid value for adpod.bidQueueSizeLimit in setConfig; must be a positive number. Using default: ${queueSizeLimit}`) - } - } -} -config.getConfig('adpod', config => adpodSetConfig(config.adpod)); - -/** - * This function initializes the adpod module's hooks. This is called by the corresponding adserver video module. - * PBJS 10: Adding a deprecation warning - */ -function initAdpodHooks() { - logWarn('DEPRECATION NOTICE: Prebid.js is not aware of any transactions requiring the ADPOD video mediatype context. Please open a github issue if you are relying on it as support for it may be removed in a future version.'); - - setupBeforeHookFnOnce(getHook('callPrebidCache'), callPrebidCacheHook); - setupBeforeHookFnOnce(checkAdUnitSetup, checkAdUnitSetupHook); - setupBeforeHookFnOnce(checkVideoBidSetup, checkVideoBidSetupHook); -} - -initAdpodHooks() - -/** - * - * @param {Array[Object]} bids list of 'winning' bids that need to be cached - * @param {Function} callback send the cached bids (or error) back to adserverVideoModule for further processing - }} - */ -export function callPrebidCacheAfterAuction(bids, callback) { - // will call PBC here and execute cb param to initialize player code - store(bids, function (error, cacheIds) { - if (error) { - callback(error, null); - } else { - const successfulCachedBids = []; - for (let i = 0; i < cacheIds.length; i++) { - if (cacheIds[i] !== '') { - successfulCachedBids.push(bids[i]); - } - } - callback(null, successfulCachedBids); - } - }) -} - -/** - * Compare function to be used in sorting long-form bids. This will compare bids on price per second. - */ -export function sortByPricePerSecond(a, b) { - if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { - return 1; - } - if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket > b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { - return -1; - } - return 0; -} - -/** - * This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form - * @param {Object} options - Options for targeting. - * @param {Array} options.codes - Array of ad unit codes. - * @param {function} options.callback - Callback function to handle the targeting key-value pairs. - * @returns {Object} Targeting key-value pairs for ad unit codes. - */ -export function getTargeting({ codes, callback } = {}) { - if (!callback) { - logError('No callback function was defined in the getTargeting call. Aborting getTargeting().'); - return; - } - codes = codes || []; - const adPodAdUnits = getAdPodAdUnits(codes); - const bidsReceived = auctionManager.getBidsReceived(); - const competiveExclusionEnabled = config.getConfig('adpod.brandCategoryExclusion'); - const deferCachingSetting = config.getConfig('adpod.deferCaching'); - const deferCachingEnabled = (typeof deferCachingSetting === 'boolean') ? deferCachingSetting : true; - - let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); - bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - - const prioritizeDeals = config.getConfig('adpod.prioritizeDeals'); - if (prioritizeDeals) { - const [otherBids, highPriorityDealBids] = bids.reduce((partitions, bid) => { - const bidDealTier = deepAccess(bid, 'video.dealTier'); - const minDealTier = config.getConfig(`adpod.dealTier.${bid.bidderCode}.minDealTier`); - if (minDealTier && bidDealTier) { - if (bidDealTier >= minDealTier) { - partitions[1].push(bid) - } else { - partitions[0].push(bid) - } - } else if (bidDealTier) { - partitions[1].push(bid) - } else { - partitions[0].push(bid); - } - return partitions; - }, [[], []]); - highPriorityDealBids.sort(sortByPricePerSecond); - otherBids.sort(sortByPricePerSecond); - bids = highPriorityDealBids.concat(otherBids); - } else { - bids.sort(sortByPricePerSecond); - } - - const targeting = {}; - if (deferCachingEnabled === false) { - adPodAdUnits.forEach((adUnit) => { - const adPodTargeting = []; - let adPodDurationSeconds = deepAccess(adUnit, 'mediaTypes.video.adPodDurationSec'); - - bids - .filter((bid) => bid.adUnitCode === adUnit.code) - .forEach((bid, index, arr) => { - if (bid.video.durationBucket <= adPodDurationSeconds) { - adPodTargeting.push({ - [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] - }); - adPodDurationSeconds -= bid.video.durationBucket; - } - if (index === arr.length - 1 && adPodTargeting.length > 0) { - adPodTargeting.push({ - [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] - }); - } - }); - targeting[adUnit.code] = adPodTargeting; - }); - - callback(null, targeting); - } else { - const bidsToCache = []; - adPodAdUnits.forEach((adUnit) => { - let adPodDurationSeconds = deepAccess(adUnit, 'mediaTypes.video.adPodDurationSec'); - - bids - .filter((bid) => bid.adUnitCode === adUnit.code) - .forEach((bid) => { - if (bid.video.durationBucket <= adPodDurationSeconds) { - bidsToCache.push(bid); - adPodDurationSeconds -= bid.video.durationBucket; - } - }); - }); - - callPrebidCacheAfterAuction(bidsToCache, function (error, bidsSuccessfullyCached) { - if (error) { - callback(error, null); - } else { - const groupedBids = groupBy(bidsSuccessfullyCached, 'adUnitCode'); - Object.keys(groupedBids).forEach((adUnitCode) => { - const adPodTargeting = []; - - groupedBids[adUnitCode].forEach((bid, index, arr) => { - adPodTargeting.push({ - [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] - }); - - if (index === arr.length - 1 && adPodTargeting.length > 0) { - adPodTargeting.push({ - [TARGETING_KEY_CACHE_ID]: bid.adserverTargeting[TARGETING_KEY_CACHE_ID] - }); - } - }); - targeting[adUnitCode] = adPodTargeting; - }); - - callback(null, targeting); - } - }); - } - return targeting; -} - -/** - * This function returns the adunit of mediaType adpod - * @param {Array} codes adUnitCodes - * @returns {Array[Object]} adunits of mediaType adpod - */ -function getAdPodAdUnits(codes) { - return auctionManager.getAdUnits() - .filter((adUnit) => deepAccess(adUnit, 'mediaTypes.video.context') === ADPOD) - .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 - * @returns {Array[Object]} unique category bids - */ -function getExclusiveBids(bidsReceived) { - let bids = bidsReceived - .map((bid) => Object.assign({}, bid, { [TARGETING_KEY_PB_CAT_DUR]: bid.adserverTargeting[TARGETING_KEY_PB_CAT_DUR] })); - bids = groupBy(bids, TARGETING_KEY_PB_CAT_DUR); - const filteredBids = []; - Object.keys(bids).forEach((targetingKey) => { - bids[targetingKey].sort(compareOn('responseTimestamp')); - filteredBids.push(bids[targetingKey][0]); - }); - return filteredBids; -} - -/** - * This function returns bids for adpod adunits - * @param {Array[Object]} bidsReceived - * @param {Array[Object]} adPodAdUnits - * @returns {Array[Object]} bids of mediaType adpod - */ -function getBidsForAdpod(bidsReceived, adPodAdUnits) { - const adUnitCodes = adPodAdUnits.map((adUnit) => adUnit.code); - return bidsReceived - .filter((bid) => adUnitCodes.indexOf(bid.adUnitCode) !== -1 && (bid.video && bid.video.context === ADPOD)) -} - -const sharedMethods = { - TARGETING_KEY_PB_CAT_DUR: TARGETING_KEY_PB_CAT_DUR, - TARGETING_KEY_CACHE_ID: TARGETING_KEY_CACHE_ID, - 'getTargeting': getTargeting -} -Object.freeze(sharedMethods); - -module('adpod', function shareAdpodUtilities(...args) { - if (!isPlainObject(args[0])) { - logError('Adpod module needs plain object to share methods with submodule'); - return; - } - function addMethods(object, func) { - for (const name in func) { - object[name] = func[name]; - } - } - addMethods(args[0], sharedMethods); -}); diff --git a/modules/adrelevantisBidAdapter.js b/modules/adrelevantisBidAdapter.js index 5b28d15a9f3..32b408f73b0 100644 --- a/modules/adrelevantisBidAdapter.js +++ b/modules/adrelevantisBidAdapter.js @@ -21,6 +21,7 @@ import { getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; import { chunk } from '../libraries/chunk/chunk.js'; import { transformSizes } from '../libraries/sizeUtils/tranformSize.js'; import { hasUserInfo, hasAppDeviceInfo, hasAppId } from '../libraries/adrelevantisUtils/bidderUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -250,10 +251,9 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { /** * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id */ -function hidedfpContainer(elementId) { - var el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); +function hidedfpContainer(bid) { + var el = getAdUnitElement(bid).querySelectorAll("div[id^='google_ads']"); if (el[0]) { el[0].style.setProperty('display', 'none'); } @@ -261,7 +261,7 @@ function hidedfpContainer(elementId) { function outstreamRender(bid) { // push to render queue because ANOutstreamVideo may not be loaded yet - hidedfpContainer(bid.adUnitCode); + hidedfpContainer(bid); bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 589a251a333..00c7bbf72e6 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -1,6 +1,6 @@ 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 { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { chunk } from '../libraries/chunk/chunk.js'; @@ -188,14 +188,6 @@ function prepareBidRequests(bidReq) { bidReqParams.GPID = gpid; } - if (mediaType === VIDEO) { - const context = deepAccess(bidReq, 'mediaTypes.video.context'); - - if (context === ADPOD) { - bidReqParams.Adpod = deepAccess(bidReq, 'mediaTypes.video'); - } - } - return bidReqParams; } @@ -238,18 +230,6 @@ function createBid(bidResponse, bidRequest) { adUrl: bidResponse.adUrl, }); } - if (context === ADPOD) { - Object.assign(bid, { - meta: { - primaryCatId: bidResponse.primaryCatId, - }, - video: { - context: ADPOD, - durationSeconds: bidResponse.durationSeconds - } - }); - } - Object.assign(bid, { vastUrl: bidResponse.vastUrl }); diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js index dc15dd2dc9f..a4badd11a4c 100644 --- a/modules/adtrgtmeBidAdapter.js +++ b/modules/adtrgtmeBidAdapter.js @@ -11,6 +11,7 @@ import { } from '../src/utils.js'; import { config } from '../src/config.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'adtrgtme'; const BIDDER_VERSION = '1.0.7'; @@ -71,7 +72,7 @@ function createORTB(bR, bid) { ...site, }, device: { - dnt: bid?.params?.dnt ? 1 : 0, + dnt: getDNT() ? 1 : 0, ua: bid?.params?.ua || navigator.userAgent, ip, }, diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index b81dd579329..9fdb1c051c4 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { logWarn, isArray, inIframe, isNumber, isStr, deepClone, deepSetValue, logError, deepAccess, isBoolean } 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 { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'adtrue'; const storage = getStorageManager({ bidderCode: BIDDER_CODE }); diff --git a/modules/alkimiBidAdapter.js b/modules/alkimiBidAdapter.js index 62beb22c521..9611cd93521 100644 --- a/modules/alkimiBidAdapter.js +++ b/modules/alkimiBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { deepAccess, deepClone, 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'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'alkimi'; const GVLID = 1169; diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index a4ec0d833cd..e4d19055ce0 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError, deepClone } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; import { parseDomain } from '../src/refererDetection.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'apacdex'; const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs' const USERSYNC = 'https://sync.quantumdex.io/usersync/pbjs' diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 74d24afecf9..cad4bd0c948 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -21,21 +21,21 @@ import { 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 { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { INSTREAM, OUTSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; import { bidderSettings } from '../src/bidderSettings.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { APPNEXUS_CATEGORY_MAPPING } from '../libraries/categoryTranslationMapping/index.js'; import { convertKeywordStringToANMap, getANKewyordParamFromMaps, getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; -import { convertCamelToUnderscore, fill, appnexusAliases } from '../libraries/appnexusUtils/anUtils.js'; +import { convertCamelToUnderscore, appnexusAliases } from '../libraries/appnexusUtils/anUtils.js'; import { convertTypes } from '../libraries/transformParamsUtils/convertTypes.js'; import { chunk } from '../libraries/chunk/chunk.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -295,10 +295,6 @@ export const spec = { } } - if (config.getConfig('adpod.brandCategoryExclusion')) { - payload.brand_category_uniqueness = true; - } - if (debugObjParams.enabled) { payload.debug = debugObjParams; logInfo('AppNexus Debug Auction Settings:\n\n' + JSON.stringify(debugObjParams, null, 4)); @@ -350,18 +346,6 @@ export const spec = { payload.referrer_detection = refererinfo; } - if (FEATURES.VIDEO) { - const hasAdPodBid = ((bidRequests) || []).find(hasAdPod); - if (hasAdPodBid) { - bidRequests.filter(hasAdPod).forEach(adPodBid => { - const adPodTags = createAdPodRequest(tags, adPodBid); - // don't need the original adpod placement because it's in adPodTags - const nonPodTags = payload.tags.filter(tag => tag.uuid !== adPodBid.bidId); - payload.tags = [...nonPodTags, ...adPodTags]; - }); - } - } - if (bidRequests[0].userIdAsEids?.length > 0) { const eids = []; bidRequests[0].userIdAsEids.forEach(eid => { @@ -613,7 +597,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { } if (FEATURES.VIDEO && rtbBid.rtb.video) { - // shared video properties used for all 3 contexts + // shared video properties used for both stream contexts Object.assign(bid, { width: rtbBid.rtb.video.player_width, height: rtbBid.rtb.video.player_height, @@ -623,17 +607,6 @@ function newBid(serverBid, rtbBid, bidderRequest) { const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); switch (videoContext) { - case ADPOD: - const primaryCatId = (APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id]) ? APPNEXUS_CATEGORY_MAPPING[rtbBid.brand_category_id] : null; - bid.meta = Object.assign({}, bid.meta, { primaryCatId }); - const dealTier = rtbBid.deal_priority; - bid.video = { - context: ADPOD, - durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), - dealTier - }; - bid.vastUrl = rtbBid.rtb.video.asset_url; - break; case OUTSTREAM: bid.adResponse = serverBid; bid.adResponse.ad = bid.adResponse.ads[0]; @@ -935,11 +908,7 @@ function bidToTag(bid) { const videoMediaType = deepAccess(bid, `mediaTypes.${VIDEO}`); const context = deepAccess(bid, 'mediaTypes.video.context'); - if (videoMediaType && context === 'adpod') { - tag.hb_source = 7; - } else { - tag.hb_source = 1; - } + tag.hb_source = 1; if (bid.mediaType === VIDEO || videoMediaType) { tag.ad_types.push(VIDEO); } @@ -1155,14 +1124,6 @@ function hasDebug(bid) { return !!bid.debug } -function hasAdPod(bid) { - return ( - bid.mediaTypes && - bid.mediaTypes.video && - bid.mediaTypes.video.context === ADPOD - ); -} - function hasOmidSupport(bid) { let hasOmid = false; const bidderParams = bid.params; @@ -1176,54 +1137,6 @@ function hasOmidSupport(bid) { return hasOmid; } -/** - * Expand an adpod placement into a set of request objects according to the - * total adpod duration and the range of duration seconds. Sets minduration/ - * maxduration video property according to requireExactDuration configuration - */ -function createAdPodRequest(tags, adPodBid) { - const { durationRangeSec, requireExactDuration } = adPodBid.mediaTypes.video; - - const numberOfPlacements = getAdPodPlacementNumber(adPodBid.mediaTypes.video); - const maxDuration = Math.max(...durationRangeSec); - - const tagToDuplicate = tags.filter(tag => tag.uuid === adPodBid.bidId); - const request = fill(...tagToDuplicate, numberOfPlacements); - - if (requireExactDuration) { - const divider = Math.ceil(numberOfPlacements / durationRangeSec.length); - const chunked = chunk(request, divider); - - // each configured duration is set as min/maxduration for a subset of requests - durationRangeSec.forEach((duration, index) => { - chunked[index].forEach(tag => { - setVideoProperty(tag, 'minduration', duration); - setVideoProperty(tag, 'maxduration', duration); - }); - }); - } else { - // all maxdurations should be the same - request.forEach(tag => setVideoProperty(tag, 'maxduration', maxDuration)); - } - - return request; -} - -function getAdPodPlacementNumber(videoParams) { - const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoParams; - const minAllowedDuration = Math.min(...durationRangeSec); - const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration); - - return requireExactDuration - ? Math.max(numberOfPlacements, durationRangeSec.length) - : numberOfPlacements; -} - -function setVideoProperty(tag, key, value) { - if (isEmpty(tag.video)) { tag.video = {}; } - tag.video[key] = value; -} - function getRtbBid(tag) { return tag && tag.ads && tag.ads.length && ((tag.ads) || []).find(ad => ad.rtb); } @@ -1266,11 +1179,10 @@ function buildNativeRequest(params) { /** * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id */ -function hidedfpContainer(elementId) { +function hidedfpContainer(container) { try { - const el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + const el = container.querySelectorAll("div[id^='google_ads']"); if (el[0]) { el[0].style.setProperty('display', 'none'); } @@ -1279,10 +1191,10 @@ function hidedfpContainer(elementId) { } } -function hideSASIframe(elementId) { +function hideSASIframe(container) { try { // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. - const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + const el = container.querySelectorAll("script[id^='sas_script']"); if (el[0].nextSibling && el[0].nextSibling.localName === 'iframe') { el[0].nextSibling.style.setProperty('display', 'none'); } @@ -1292,8 +1204,9 @@ function hideSASIframe(elementId) { } function outstreamRender(bid, doc) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); + const container = getAdUnitElement(bid); + hidedfpContainer(container); + hideSASIframe(container); // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { const win = doc?.defaultView || window; diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 68f391e106d..32c5f8242ec 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { generateUUID, deepAccess, createTrackPixelHtml } from '../src/utils.js'; import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getDNT } from '../libraries/dnt/index.js'; const CONSTANTS = { DSU_KEY: 'apr_dsu', diff --git a/modules/asteriobidAnalyticsAdapter.js b/modules/asteriobidAnalyticsAdapter.js index 58ca7cec914..43c0643e394 100644 --- a/modules/asteriobidAnalyticsAdapter.js +++ b/modules/asteriobidAnalyticsAdapter.js @@ -229,9 +229,6 @@ function handleEvent(eventType, eventArgs) { case EVENTS.REQUEST_BIDS: { break } - case EVENTS.ADD_AD_UNITS: { - break - } case EVENTS.AD_RENDER_FAILED: { pmEvent.bid = eventArgs.bid pmEvent.message = eventArgs.message diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index a621eedeabc..5c51167b63e 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, isArray, isEmpty, logError, replaceAuctionPrice, triggerPixel } 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 { ajax } from '../src/ajax.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'axonix'; const BIDDER_VERSION = '1.0.2'; diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index c1160567d80..f85c233e644 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -10,8 +10,9 @@ import { import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { getFirstSize, getOsVersion, getVideoSizes, getBannerSizes, isConnectedTV, getDoNotTrack, isMobile, isBannerBid, isVideoBid, getBannerBidFloor, getVideoBidFloor, getVideoTargetingParams, getTopWindowLocation } from '../libraries/advangUtils/index.js'; +import { getFirstSize, getOsVersion, getVideoSizes, getBannerSizes, isConnectedTV, isMobile, isBannerBid, isVideoBid, getBannerBidFloor, getVideoBidFloor, getVideoTargetingParams, getTopWindowLocation } from '../libraries/advangUtils/index.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; const ADAPTER_VERSION = '1.21'; const GVLID = 157; @@ -305,7 +306,7 @@ function createVideoRequestData(bid, bidderRequest) { ua: navigator.userAgent, language: navigator.language, devicetype: isMobile() ? 1 : isConnectedTV() ? 3 : 2, - dnt: getDoNotTrack() ? 1 : 0, + dnt: getDNT() ? 1 : 0, js: 1, geo: {} }, @@ -371,7 +372,7 @@ function createBannerRequestData(bids, bidderRequest) { ua: navigator.userAgent, deviceOs: getOsVersion(), isMobile: isMobile() ? 1 : 0, - dnt: getDoNotTrack() ? 1 : 0, + dnt: getDNT() ? 1 : 0, adapterVersion: ADAPTER_VERSION, adapterName: ADAPTER_NAME }; diff --git a/modules/bidViewability.js b/modules/bidViewability.js index 2670f601a24..191df92f8f6 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -3,82 +3,34 @@ // Does not work with other than GPT integration import { config } from '../src/config.js'; -import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; -import { isFn, logWarn, triggerPixel } from '../src/utils.js'; +import { isAdUnitCodeMatchingSlot, logWarn } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; -import adapterManager, { gppDataHandler, uspDataHandler } from '../src/adapterManager.js'; -import { gdprParams } from '../libraries/dfpUtils/dfpUtils.js'; +import { triggerBidViewable } from '../libraries/bidViewabilityPixels/index.js'; const MODULE_NAME = 'bidViewability'; const CONFIG_ENABLED = 'enabled'; -const CONFIG_FIRE_PIXELS = 'firePixels'; -const CONFIG_CUSTOM_MATCH = 'customMatchFunction'; -const BID_VURL_ARRAY = 'vurls'; const GPT_IMPRESSION_VIEWABLE_EVENT = 'impressionViewable'; -export const isBidAdUnitCodeMatchingSlot = (bid, slot) => { - return (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode); -} - -export const getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { +export const getMatchingWinningBidForGPTSlot = (slot) => { + const match = isAdUnitCodeMatchingSlot(slot); return getGlobal().getAllWinningBids().find( // supports custom match function from config - bid => isFn(globalModuleConfig[CONFIG_CUSTOM_MATCH]) - ? globalModuleConfig[CONFIG_CUSTOM_MATCH](bid, slot) - : isBidAdUnitCodeMatchingSlot(bid, slot) + ({ adUnitCode }) => match(adUnitCode) ) || null; }; -export const fireViewabilityPixels = (globalModuleConfig, bid) => { - if (globalModuleConfig[CONFIG_FIRE_PIXELS] === true && bid.hasOwnProperty(BID_VURL_ARRAY)) { - const queryParams = gdprParams(); - - const uspConsent = uspDataHandler.getConsentData(); - if (uspConsent) { queryParams.us_privacy = uspConsent; } - - const gppConsent = gppDataHandler.getConsentData(); - if (gppConsent) { - // TODO - need to know what to set here for queryParams... - } - - bid[BID_VURL_ARRAY].forEach(url => { - // add '?' if not present in URL - if (Object.keys(queryParams).length > 0 && url.indexOf('?') === -1) { - url += '?'; - } - // append all query params, `&key=urlEncoded(value)` - url += Object.keys(queryParams).reduce((prev, key) => { - prev += `&${key}=${encodeURIComponent(queryParams[key])}`; - return prev; - }, ''); - triggerPixel(url) - }); - } -}; - export const logWinningBidNotFound = (slot) => { logWarn(`bid details could not be found for ${slot.getSlotElementId()}, probable reasons: a non-prebid bid is served OR check the prebid.AdUnit.code to GPT.AdSlot relation.`); }; export const impressionViewableHandler = (globalModuleConfig, event) => { const slot = event.slot; - const respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); + const respectiveBid = getMatchingWinningBidForGPTSlot(slot); if (respectiveBid === null) { logWinningBidNotFound(slot); } else { - // if config is enabled AND VURL array is present then execute each pixel - fireViewabilityPixels(globalModuleConfig, respectiveBid); - // trigger respective bidder's onBidViewable handler - adapterManager.callBidViewableBidder(respectiveBid.adapterCode || respectiveBid.bidder, respectiveBid); - - if (respectiveBid.deferBilling) { - adapterManager.triggerBilling(respectiveBid); - } - - // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels - events.emit(EVENTS.BID_VIEWABLE, respectiveBid); + triggerBidViewable(respectiveBid); } }; @@ -90,7 +42,6 @@ const handleSetConfig = (config) => { // do nothing if module-config.enabled is not set to true // this way we are adding a way for bidders to know (using pbjs.getConfig('bidViewability').enabled === true) whether this module is added in build and is enabled const impressionViewableHandlerWrapper = (event) => { - window.googletag.pubads().removeEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, impressionViewableHandlerWrapper); impressionViewableHandler(globalModuleConfig, event); }; @@ -102,6 +53,7 @@ const handleSetConfig = (config) => { } // add the GPT event listener window.googletag.cmd.push(() => { + window.googletag.pubads().removeEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, impressionViewableHandlerWrapper); window.googletag.pubads().addEventListener(GPT_IMPRESSION_VIEWABLE_EVENT, impressionViewableHandlerWrapper); }); } diff --git a/modules/bidViewability.md b/modules/bidViewability.md index 922a4a9def4..24f7dcd019e 100644 --- a/modules/bidViewability.md +++ b/modules/bidViewability.md @@ -12,7 +12,7 @@ Maintainer: harshad.mane@pubmatic.com - GPT API is used to find when a bid is viewable, https://developers.google.com/publisher-tag/reference#googletag.events.impressionviewableevent . This event is fired when an impression becomes viewable, according to the Active View criteria. Refer: https://support.google.com/admanager/answer/4524488 - This module does not work with any adserver's other than GAM with GPT integration -- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using param ```customMatchFunction``` +- Logic used to find a matching pbjs-bid for a GPT slot is ``` (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ``` this logic can be changed by using config param ```customGptSlotMatching``` - When a rendered PBJS bid is viewable the module will trigger a BID_VIEWABLE event, which can be consumed by bidders and analytics adapters - If the viewable bid contains a ```vurls``` param containing URL's and the Bid Viewability module is configured with ``` firePixels: true ``` then the URLs mentioned in bid.vurls will be called. Please note that GDPR and USP related parameters will be added to the given URLs - This module is also compatible with Prebid core's billing deferral logic, this means that bids linked to an ad unit marked with `deferBilling: true` will trigger a bid adapter's `onBidBillable` function (if present) indicating an ad slot was viewed and also billing ready (if it were deferred). @@ -20,7 +20,6 @@ Refer: https://support.google.com/admanager/answer/4524488 # Params - enabled [required] [type: boolean, default: false], when set to true, the module will emit BID_VIEWABLE when applicable - firePixels [optional] [type: boolean], when set to true, will fire the urls mentioned in bid.vurls which should be array of urls -- customMatchFunction [optional] [type: function(bid, slot)], when passed this function will be used to `find` the matching winning bid for the GPT slot. Default value is ` (bid, slot) => (slot.getAdUnitPath() === bid.adUnitCode || slot.getSlotElementId() === bid.adUnitCode) ` # Example of consuming BID_VIEWABLE event ``` diff --git a/modules/bidViewabilityIO.js b/modules/bidViewabilityIO.js index 0e54d969b81..34dc03c168f 100644 --- a/modules/bidViewabilityIO.js +++ b/modules/bidViewabilityIO.js @@ -2,6 +2,8 @@ import { logMessage } from '../src/utils.js'; import { config } from '../src/config.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { triggerBidViewable } from '../libraries/bidViewabilityPixels/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const MODULE_NAME = 'bidViewabilityIO'; const CONFIG_ENABLED = 'enabled'; @@ -42,7 +44,7 @@ export const getViewableOptions = (bid) => { export const markViewed = (bid, entry, observer) => { return () => { observer.unobserve(entry.target); - events.emit(EVENTS.BID_VIEWABLE, bid); + triggerBidViewable(bid); _logMessage(`id: ${entry.target.getAttribute('id')} code: ${bid.adUnitCode} was viewed`); } } @@ -80,7 +82,7 @@ export const init = () => { events.on(EVENTS.AD_RENDER_SUCCEEDED, ({ doc, bid, id }) => { if (isSupportedMediaType(bid)) { const viewable = new IntersectionObserver(viewCallbackFactory(bid), getViewableOptions(bid)); - const element = document.getElementById(bid.adUnitCode); + const element = getAdUnitElement(bid); viewable.observe(element); } }); diff --git a/modules/bmtmBidAdapter.js b/modules/bmtmBidAdapter.js index 36c5c3c89ca..a5663d35299 100644 --- a/modules/bmtmBidAdapter.js +++ b/modules/bmtmBidAdapter.js @@ -1,8 +1,8 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { generateUUID, deepAccess, logWarn, deepSetValue, isPlainObject } 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 { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'bmtm'; const AD_URL = 'https://one.elitebidder.com/api/hb?sid='; diff --git a/modules/cadent_aperture_mxBidAdapter.js b/modules/cadent_aperture_mxBidAdapter.js index 741bc0db16b..fa8a283b924 100644 --- a/modules/cadent_aperture_mxBidAdapter.js +++ b/modules/cadent_aperture_mxBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { _each, deepAccess, getBidIdParameter, @@ -13,6 +12,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { parseDomain } from '../src/refererDetection.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'cadent_aperture_mx'; const ENDPOINT = 'hb.emxdgt.com'; diff --git a/modules/categoryTranslation.js b/modules/categoryTranslation.js deleted file mode 100644 index a1925921e44..00000000000 --- a/modules/categoryTranslation.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * This module translates iab category to freewheel industry using translation mapping file - * Publisher can set translation file by using setConfig method - * - * Example: - * config.setConfig({ - * 'brandCategoryTranslation': { - * 'translationFile': 'http://sample.com' - * } - * }); - * If publisher has not defined translation file than prebid will use default prebid translation file provided here //cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json - */ - -import { config } from '../src/config.js'; -import { hook, setupBeforeHookFnOnce, ready } from '../src/hook.js'; -import { ajax } from '../src/ajax.js'; -import { logError, timestamp } from '../src/utils.js'; -import { addBidResponse } from '../src/auction.js'; -import { getCoreStorageManager } from '../src/storageManager.js'; -import { timedBidResponseHook } from '../src/utils/perfMetrics.js'; - -export const storage = getCoreStorageManager('categoryTranslation'); -const DEFAULT_TRANSLATION_FILE_URL = 'https://cdn.jsdelivr.net/gh/prebid/category-mapping-file@1/freewheel-mapping.json'; -const DEFAULT_IAB_TO_FW_MAPPING_KEY = 'iabToFwMappingkey'; -const DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB = 'iabToFwMappingkeyPub'; -const refreshInDays = 1; - -export const registerAdserver = hook('async', function(adServer) { - let url; - if (adServer === 'freewheel') { - url = DEFAULT_TRANSLATION_FILE_URL; - initTranslation(url, DEFAULT_IAB_TO_FW_MAPPING_KEY); - } -}, 'registerAdserver'); - -ready.then(() => registerAdserver()); - -export const getAdserverCategoryHook = timedBidResponseHook('categoryTranslation', function getAdserverCategoryHook(fn, adUnitCode, bid, reject) { - if (!bid) { - return fn.call(this, adUnitCode, bid, reject); // if no bid, call original and let it display warnings - } - - if (!config.getConfig('adpod.brandCategoryExclusion')) { - return fn.call(this, adUnitCode, bid, reject); - } - - const localStorageKey = (config.getConfig('brandCategoryTranslation.translationFile')) ? DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB : DEFAULT_IAB_TO_FW_MAPPING_KEY; - - if (bid.meta && !bid.meta.adServerCatId) { - let mapping = storage.getDataFromLocalStorage(localStorageKey); - if (mapping) { - try { - mapping = JSON.parse(mapping); - } catch (error) { - logError('Failed to parse translation mapping file'); - } - if (bid.meta.primaryCatId && mapping['mapping'] && mapping['mapping'][bid.meta.primaryCatId]) { - bid.meta.adServerCatId = mapping['mapping'][bid.meta.primaryCatId]['id']; - } else { - // This bid will be automatically ignored by adpod module as adServerCatId was not found - bid.meta.adServerCatId = undefined; - } - } else { - logError('Translation mapping data not found in local storage'); - } - } - fn.call(this, adUnitCode, bid, reject); -}); - -export function initTranslation(url, localStorageKey) { - setupBeforeHookFnOnce(addBidResponse, getAdserverCategoryHook, 50); - let mappingData = storage.getDataFromLocalStorage(localStorageKey); - try { - mappingData = mappingData ? JSON.parse(mappingData) : undefined; - if (!mappingData || timestamp() > mappingData.lastUpdated + refreshInDays * 24 * 60 * 60 * 1000) { - ajax(url, - { - success: (response) => { - try { - response = JSON.parse(response); - response['lastUpdated'] = timestamp(); - storage.setDataInLocalStorage(localStorageKey, JSON.stringify(response)); - } catch (error) { - logError('Failed to parse translation mapping file'); - } - }, - error: () => { - logError('Failed to load brand category translation file.') - } - }, - ); - } - } catch (error) { - logError('Failed to parse translation mapping file'); - } -} - -function setConfig(config) { - if (config.translationFile) { - // if publisher has defined the translation file, preload that file here - initTranslation(config.translationFile, DEFAULT_IAB_TO_FW_MAPPING_KEY_PUB); - } -} - -config.getConfig('brandCategoryTranslation', config => setConfig(config.brandCategoryTranslation)); diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index 3bb60cef907..68e30854916 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -104,10 +104,6 @@ function _buildBid (bid, bidderRequest) { placement.ext = { 'pid': bid.params.placementId } - if (bidderRequest.paapi?.enabled) { - placement.ext.ae = bid?.ortb2Imp?.ext?.ae - } - return placement } diff --git a/modules/chtnwBidAdapter.js b/modules/chtnwBidAdapter.js index 31c2c7bf342..920c4670b84 100644 --- a/modules/chtnwBidAdapter.js +++ b/modules/chtnwBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { generateUUID, _each, @@ -10,6 +9,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +import { getDNT } from '../libraries/dnt/index.js'; const ENDPOINT_URL = 'https://prebid.cht.hinet.net/api/v1'; const BIDDER_CODE = 'chtnw'; const COOKIE_NAME = '__htid'; diff --git a/modules/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js index d9394253497..93c031dcdf7 100644 --- a/modules/cointrafficBidAdapter.js +++ b/modules/cointrafficBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import { config } from '../src/config.js' import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { getViewportSize } from '../libraries/viewport/viewport.js' -import { getDNT } from '../libraries/dnt/index.js' +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index a83c078ccef..e9d463ca937 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -4,6 +4,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getViewportCoordinates } from '../libraries/viewport/viewport.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -70,7 +71,7 @@ export const spec = { payload.slots = validBidRequests.map((bidRequest) => { eids.push(...(bidRequest.userIdAsEids || [])); - const adUnitElement = document.getElementById(bidRequest.adUnitCode); + const adUnitElement = getAdUnitElement(bidRequest); const coordinates = getOffset(adUnitElement); const slot = { diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 3d61234444c..c8364b97daa 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -25,6 +25,7 @@ import { BANNER, VIDEO, } from '../src/mediaTypes.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'connatix'; @@ -124,7 +125,7 @@ function getDomElement(elementId) { } export function detectViewability(bid) { - const { params, adUnitCode } = bid; + const { params } = bid; const viewabilityContainerIdentifier = params.viewabilityContainerIdentifier; @@ -151,7 +152,7 @@ export function detectViewability(bid) { bidParamSizes = typeof bidParamSizes === 'undefined' && bid.mediaType && bid.mediaType.video && bid.mediaType.video.playerSize ? bid.mediaType.video.playerSize : bidParamSizes; bidParamSizes = typeof bidParamSizes === 'undefined' && bid.mediaType && bid.mediaType.video && isNumber(bid.mediaType.video.w) && isNumber(bid.mediaType.h) ? [bid.mediaType.video.w, bid.mediaType.video.h] : bidParamSizes; minSize = _getMinSize(bidParamSizes ?? []) - element = document.getElementById(adUnitCode); + element = getAdUnitElement(bid); } if (isViewabilityMeasurable(element)) { diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index 71f8986e604..d1fec831b50 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, mergeDeep, logWarn, generateUUID } 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 { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 4c319c9b48a..f711c300b8b 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -292,6 +292,7 @@ function getDivIdPosition(divId) { let domElement; + // TODO: this should use getAdUnitElement if (inIframe() === true) { const ws = getWindowSelf(); const currentElement = ws.document.getElementById(divId); @@ -335,6 +336,7 @@ function tryGetDivIdPosition(divIdMethod) { return undefined; } +// TODO unified adUnit/element association in 11 function tryMultipleDivIdPositions(adUnit) { const divMethods = [ // ortb2\ diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index a6ef6ba26e6..56fc19ef7be 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,4 +1,4 @@ -import { deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel, deepAccess, logInfo } from '../src/utils.js'; +import { deepAccess, deepSetValue, logError, logInfo, logWarn, parseUrl, triggerPixel } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -76,10 +76,6 @@ function imp(buildImp, bidRequest, context) { delete imp.rwdd // oRTB 2.6 field moved to ext - if (!context.fledgeEnabled && imp.ext.igs?.ae) { - delete imp.ext.igs.ae; - } - if (hasVideoMediaType(bidRequest)) { const paramsVideo = bidRequest.params.video; if (paramsVideo !== undefined) { @@ -392,7 +388,7 @@ export const spec = { /** * @param {*} response * @param {ServerRequest} request - * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} + * @return {Bid[] | {bids: Bid[]}} */ interpretResponse: (response, request) => { if (typeof response?.body === 'undefined') { @@ -400,18 +396,7 @@ export const spec = { } const interpretedResponse = CONVERTER.fromORTB({ response: response.body, request: request.data }); - const bids = interpretedResponse.bids || []; - - const fledgeAuctionConfigs = response.body?.ext?.igi?.filter(igi => isArray(igi?.igs)) - .flatMap(igi => igi.igs); - if (fledgeAuctionConfigs?.length) { - return { - bids, - paapi: fledgeAuctionConfigs, - }; - } - - return bids; + return interpretedResponse.bids || []; }, /** @@ -503,7 +488,6 @@ function buildContext(bidRequests, bidderRequest) { url: bidderRequest?.refererInfo?.page || '', debug: queryString['pbt_debug'] === '1', noLog: queryString['pbt_nolog'] === '1', - fledgeEnabled: bidderRequest.paapi?.enabled, amp: bidRequests.some(bidRequest => bidRequest.params.integrationMode === 'amp'), networkId: bidRequests.find(bidRequest => bidRequest.params?.networkId)?.params.networkId, publisherId: bidRequests.find(bidRequest => bidRequest.params?.pubid)?.params.pubid, diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index 875dfdedf67..46647b31c58 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -11,6 +11,7 @@ import { getBoundingClientRect } from "../libraries/boundingClientRect/boundingC import { hasPurpose1Consent } from "../src/utils/gdpr.js"; import { sendBeacon } from "../src/ajax.js"; import { isAutoplayEnabled } from "../libraries/autoplayDetection/autoplay.js"; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -35,7 +36,7 @@ export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); */ function slotDimensions(bid) { const adUnitCode = bid.adUnitCode; - const slotEl = document.getElementById(adUnitCode); + const slotEl = getAdUnitElement(bid); if (slotEl) { logInfo(`Slot element found: ${adUnitCode}`); diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index a6b452d2d91..afd644684d7 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -53,7 +53,6 @@ export function makebidInterceptor({ utils, BANNER, NATIVE, VIDEO, Renderer }) { match: this.matcher(ruleDef.when, ruleNo), replace: this.replacer(ruleDef.then, ruleNo), options: Object.assign({}, this.DEFAULT_RULE_OPTIONS, ruleDef.options), - paapi: this.paapiReplacer(ruleDef.paapi || [], ruleNo) } }, /** @@ -144,24 +143,6 @@ export function makebidInterceptor({ utils, BANNER, NATIVE, VIDEO, Renderer }) { return response; } }, - - paapiReplacer(paapiDef, ruleNo) { - function wrap(configs = []) { - return configs.map(config => { - return Object.keys(config).some(k => !['config', 'igb'].includes(k)) - ? { config } - : config - }); - } - if (Array.isArray(paapiDef)) { - return () => wrap(paapiDef); - } else if (typeof paapiDef === 'function') { - return (...args) => wrap(paapiDef(...args)) - } else { - this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`); - } - }, - responseDefaults(bid) { const response = { requestId: bid.bidId, @@ -224,12 +205,11 @@ export function makebidInterceptor({ utils, BANNER, NATIVE, VIDEO, Renderer }) { * {{}[]} bids? * {*} bidRequest * {function(*)} addBid called once for each mock response - * addPaapiConfig called once for each mock PAAPI config * {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, addPaapiConfig, done }) { + intercept({ bids, bidRequest, addBid, done }) { if (bids == null) { bids = bidRequest.bids; } @@ -238,12 +218,10 @@ export function makebidInterceptor({ utils, BANNER, NATIVE, VIDEO, Renderer }) { 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, PAAPI configs:`, match.bid, mockResponse, mockPaapi) + this.logger.logMessage(`Intercepted bid request (matching rule #${match.rule.no}), mocking response in ${delay}ms. Request, response:`, match.bid, mockResponse) this.setTimeout(() => { 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 5413ca37082..022d901264d 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -108,7 +108,6 @@ export function makeBidderBidInterceptor({ utils }) { bids, bidRequest, addBid: wrapCallback(cbs.onBid), - addPaapiConfig: wrapCallback((config, bidRequest) => cbs.onPaapi({ bidId: bidRequest.bidId, ...config })), done })); if (bids.length === 0) { diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 484e99dcd5f..da246f30511 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -4,7 +4,6 @@ export function makePbsInterceptor({ createBid, utils }) { onResponse, onError, onBid, - onFledge, }) { let responseArgs; const done = delayExecution(() => onResponse(...responseArgs), bidRequests.length + 1) @@ -22,14 +21,6 @@ export function makePbsInterceptor({ createBid, utils }) { .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) diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 0d5d2bb6435..47602bd218f 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { generateUUID, deepSetValue, deepAccess, isArray, isFn, isPlainObject, logError, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { COMMON_ORTB_VIDEO_PARAMS, formatResponse } from '../libraries/deepintentUtils/index.js'; import { addDealCustomTargetings, addPMPDeals } from '../libraries/dealUtils/dealUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; const LOG_WARN_PREFIX = 'DeepIntent: '; const BIDDER_CODE = 'deepintent'; diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js deleted file mode 100644 index d9e038d58d2..00000000000 --- a/modules/dfpAdServerVideo.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint prebid/validate-imports: "off" */ -import { registerVideoSupport } from '../src/adServerManager.js'; -import { buildGamVideoUrl, getVastXml, notifyTranslationModule, dep, VAST_TAG_URI_TAGNAME, getBase64BlobContent } from './gamAdServerVideo.js'; - -export const buildDfpVideoUrl = buildGamVideoUrl; -export { getVastXml, notifyTranslationModule, dep, VAST_TAG_URI_TAGNAME, getBase64BlobContent }; - -registerVideoSupport('dfp', { - buildVideoUrl: buildDfpVideoUrl, - getVastXml -}); diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js deleted file mode 100644 index 4f547ececfa..00000000000 --- a/modules/dfpAdpod.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint prebid/validate-imports: "off" */ -import { registerVideoSupport } from '../src/adServerManager.js'; -import { buildAdpodVideoUrl, adpodUtils } from './gamAdpod.js'; - -export { buildAdpodVideoUrl, adpodUtils }; - -registerVideoSupport('dfp', { - buildAdpodVideoUrl, - getAdpodTargeting: (args) => adpodUtils.getTargeting(args) -}); diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js index 704069783bc..0c56118dc71 100644 --- a/modules/digitalMatterBidAdapter.js +++ b/modules/digitalMatterBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, getWinDimensions, inIframe, logWarn, parseSizesInput } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'digitalMatter'; const GVLID = 1345; diff --git a/modules/distroscaleBidAdapter.js b/modules/distroscaleBidAdapter.js index 02982b27a82..a2eea9e9130 100644 --- a/modules/distroscaleBidAdapter.js +++ b/modules/distroscaleBidAdapter.js @@ -1,8 +1,8 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { logWarn, isPlainObject, isStr, isArray, isFn, inIframe, mergeDeep, deepSetValue, logError, deepClone } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'distroscale'; const SHORT_CODE = 'ds'; const LOG_WARN_PREFIX = 'DistroScale: '; diff --git a/modules/dmdIdSystem.js b/modules/dmdIdSystem.js deleted file mode 100644 index 4fc986bd1fc..00000000000 --- a/modules/dmdIdSystem.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * This module adds dmdId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/dmdIdSystem - * @requires module:modules/userId - */ - -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} */ -export const dmdIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - - /** - * decode the stored id value for passing to bid requests - * @function decode - * @param {(Object|string)} value - * @returns {(Object|undefined)} - */ - decode(value) { - return value && typeof value === 'string' - ? { 'dmdId': value } - : undefined; - }, - - /** - * performs action to obtain id and return a value in the callback's response argument - * @function getId - * @param {SubmoduleConfig} [config] - * @param {ConsentData} consentData - * @param {Object} cacheIdObj - existing id, if any - * @returns {IdResponse|undefined} - */ - getId(config, consentData, cacheIdObj) { - const configParams = (config && config.params) || {}; - if ( - !configParams || - !configParams.api_key || - typeof configParams.api_key !== 'string' - ) { - logError('dmd submodule requires an api_key.'); - return; - } - // If cahceIdObj is null or undefined - calling AIX-API - if (cacheIdObj) { - return cacheIdObj; - } else { - const url = configParams && configParams.api_url - ? configParams.api_url - : `https://aix.hcn.health/api/v1/auths`; - // Setting headers - const headers = {}; - headers['x-api-key'] = configParams.api_key; - headers['x-domain'] = getWindowLocation(); - // Response callbacks - const resp = function (callback) { - const callbacks = { - success: response => { - let responseObj; - let responseId; - try { - responseObj = JSON.parse(response); - if (responseObj && responseObj.dgid) { - responseId = responseObj.dgid; - } - } catch (error) { - logError(error); - } - callback(responseId); - }, - error: error => { - logError(`${MODULE_NAME}: ID fetch encountered an error`, error); - callback(); - } - }; - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true, customHeaders: headers }); - }; - return { callback: resp }; - } - }, - eids: { - 'dmdId': { - source: 'hcn.health', - atype: 3 - }, - } -}; - -submodule('userId', dmdIdSubmodule); diff --git a/modules/dmdIdSystem.md b/modules/dmdIdSystem.md deleted file mode 100644 index f2a5b76ade7..00000000000 --- a/modules/dmdIdSystem.md +++ /dev/null @@ -1,26 +0,0 @@ -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'dmdId', - storage: { - name: 'dmd-dgid', - type: 'cookie', - expires: 30 - }, - params: { - api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by DMD - } - }] - } -}); - -#### DMD ID Configuration - -{: .table .table-bordered .table-striped } -| Param under userSync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | The name of Module | `"dmdId"` | -| storage | Required | Object | | -| storage.name | Required | String | `dmd-dgid` | -| params | Required | Object | Container of all module params. | | -| params.api_key | Required | String | This is your `api_key` as provided by DMD Marketing Corp. | `3fdbe297-3690-4f5c-9e11-ee9186a6d77c` | \ No newline at end of file diff --git a/modules/empowerBidAdapter.js b/modules/empowerBidAdapter.js index 14e8be2a82d..919305f5519 100644 --- a/modules/empowerBidAdapter.js +++ b/modules/empowerBidAdapter.js @@ -1,262 +1,258 @@ -import { - deepAccess, - mergeDeep, - logError, - replaceMacros, - triggerPixel, - deepSetValue, - isStr, - isArray, - getWinDimensions, -} from "../src/utils.js"; -import { registerBidder } from "../src/adapters/bidderFactory.js"; -import { config } from "../src/config.js"; -import { VIDEO, BANNER } from "../src/mediaTypes.js"; -import { getConnectionType } from "../libraries/connectionInfo/connectionUtils.js"; - -export const ENDPOINT = "https://bid.virgul.com/prebid"; - -const BIDDER_CODE = "empower"; -const GVLID = 1248; - -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - supportedMediaTypes: [VIDEO, BANNER], - - isBidRequestValid: (bid) => - !!(bid && bid.params && bid.params.zone && bid.bidder === BIDDER_CODE), - - buildRequests: (bidRequests, bidderRequest) => { - const currencyObj = config.getConfig("currency"); - const currency = (currencyObj && currencyObj.adServerCurrency) || "USD"; - - const request = { - id: bidRequests[0].bidderRequestId, - at: 1, - imp: bidRequests.map((slot) => impression(slot, currency)), - site: { - page: bidderRequest.refererInfo.page, - domain: bidderRequest.refererInfo.domain, - ref: bidderRequest.refererInfo.ref, - publisher: { domain: bidderRequest.refererInfo.domain }, - }, - device: { - ua: navigator.userAgent, - js: 1, - dnt: - navigator.doNotTrack === "yes" || - navigator.doNotTrack === "1" || - navigator.msDoNotTrack === "1" - ? 1 - : 0, - h: screen.height, - w: screen.width, - language: navigator.language, - connectiontype: getConnectionType(), - }, - cur: [currency], - source: { - fd: 1, - tid: bidderRequest.ortb2?.source?.tid, - ext: { - prebid: "$prebid.version$", - }, - }, - user: {}, - regs: {}, - ext: {}, - }; - - if (bidderRequest.gdprConsent) { - request.user = { - ext: { - consent: bidderRequest.gdprConsent.consentString || "", - }, - }; - request.regs = { - ext: { - gdpr: - bidderRequest.gdprConsent.gdprApplies !== undefined - ? bidderRequest.gdprConsent.gdprApplies - : true, - }, - }; - } - - if (bidderRequest.ortb2?.source?.ext?.schain) { - request.schain = bidderRequest.ortb2.source.ext.schain; - } - - let bidUserIdAsEids = deepAccess(bidRequests, "0.userIdAsEids"); - if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { - deepSetValue(request, "user.eids", bidUserIdAsEids); - } - - const commonFpd = bidderRequest.ortb2 || {}; - const { user, device, site, bcat, badv } = commonFpd; - if (site) { - mergeDeep(request, { site: site }); - } - if (user) { - mergeDeep(request, { user: user }); - } - if (badv) { - mergeDeep(request, { badv: badv }); - } - if (bcat) { - mergeDeep(request, { bcat: bcat }); - } - - if (user?.geo && device?.geo) { - request.device.geo = { ...request.device.geo, ...device.geo }; - request.user.geo = { ...request.user.geo, ...user.geo }; - } else { - if (user?.geo || device?.geo) { - request.user.geo = request.device.geo = user?.geo - ? { ...request.user.geo, ...user.geo } - : { ...request.user.geo, ...device.geo }; - } - } - - if (bidderRequest.ortb2?.device) { - mergeDeep(request.device, bidderRequest.ortb2.device); - } - - return { - method: "POST", - url: ENDPOINT, - data: JSON.stringify(request), - }; - }, - - interpretResponse: (bidResponse, bidRequest) => { - const idToImpMap = {}; - const idToBidMap = {}; - - if (!bidResponse["body"]) { - return []; - } - if (!bidRequest.data) { - return []; - } - const requestImps = parse(bidRequest.data); - if (!requestImps) { - return []; - } - requestImps.imp.forEach((imp) => { - idToImpMap[imp.id] = imp; - }); - bidResponse = bidResponse.body; - if (bidResponse) { - bidResponse.seatbid.forEach((seatBid) => - seatBid.bid.forEach((bid) => { - idToBidMap[bid.impid] = bid; - }) - ); - } - const bids = []; - Object.keys(idToImpMap).forEach((id) => { - const imp = idToImpMap[id]; - const result = idToBidMap[id]; - - if (result) { - const bid = { - requestId: id, - cpm: result.price, - creativeId: result.crid, - ttl: 300, - netRevenue: true, - mediaType: imp.video ? VIDEO : BANNER, - currency: bidResponse.cur, - }; - if (imp.video) { - bid.vastXml = result.adm; - } else if (imp.banner) { - bid.ad = result.adm; - } - bid.width = result.w; - bid.height = result.h; - if (result.burl) bid.burl = result.burl; - if (result.nurl) bid.nurl = result.nurl; - if (result.adomain) { - bid.meta = { - advertiserDomains: result.adomain, - }; - } - bids.push(bid); - } - }); - return bids; - }, - - onBidWon: (bid) => { - if (bid.nurl && isStr(bid.nurl)) { - bid.nurl = replaceMacros(bid.nurl, { - AUCTION_PRICE: bid.cpm, - AUCTION_CURRENCY: bid.cur, - }); - triggerPixel(bid.nurl); - } - }, -}; - -function impression(slot, currency) { - let bidFloorFromModule; - if (typeof slot.getFloor === "function") { - const floorInfo = slot.getFloor({ - currency: "USD", - mediaType: "*", - size: "*", - }); - bidFloorFromModule = - floorInfo?.currency === "USD" ? floorInfo?.floor : undefined; - } - const imp = { - id: slot.bidId, - bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, - bidfloorcur: - (bidFloorFromModule && "USD") || - slot.params.bidfloorcur || - currency || - "USD", - tagid: "" + (slot.params.zone || ""), - }; - - if (slot.mediaTypes.banner) { - imp.banner = bannerImpression(slot); - } else if (slot.mediaTypes.video) { - imp.video = deepAccess(slot, "mediaTypes.video"); - } - imp.ext = slot.params || {}; - const { innerWidth, innerHeight } = getWinDimensions(); - imp.ext.ww = innerWidth || ""; - imp.ext.wh = innerHeight || ""; - return imp; -} - -function bannerImpression(slot) { - const sizes = slot.mediaTypes.banner.sizes || slot.sizes; - return { - format: sizes.map((s) => ({ w: s[0], h: s[1] })), - w: sizes[0][0], - h: sizes[0][1], - }; -} - -function parse(rawResponse) { - try { - if (rawResponse) { - if (typeof rawResponse === "object") { - return rawResponse; - } else { - return JSON.parse(rawResponse); - } - } - } catch (ex) { - logError("empowerBidAdapter", "ERROR", ex); - } - return null; -} - -registerBidder(spec); +import { + deepAccess, + mergeDeep, + logError, + replaceMacros, + triggerPixel, + deepSetValue, + isStr, + isArray, + getWinDimensions, +} from "../src/utils.js"; +import { registerBidder } from "../src/adapters/bidderFactory.js"; +import { config } from "../src/config.js"; +import { VIDEO, BANNER } from "../src/mediaTypes.js"; +import { getConnectionType } from "../libraries/connectionInfo/connectionUtils.js"; +import { getDNT } from "../libraries/dnt/index.js"; + +export const ENDPOINT = "https://bid.virgul.com/prebid"; + +const BIDDER_CODE = "empower"; +const GVLID = 1248; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [VIDEO, BANNER], + + isBidRequestValid: (bid) => + !!(bid && bid.params && bid.params.zone && bid.bidder === BIDDER_CODE), + + buildRequests: (bidRequests, bidderRequest) => { + const currencyObj = config.getConfig("currency"); + const currency = (currencyObj && currencyObj.adServerCurrency) || "USD"; + + const request = { + id: bidRequests[0].bidderRequestId, + at: 1, + imp: bidRequests.map((slot) => impression(slot, currency)), + site: { + page: bidderRequest.refererInfo.page, + domain: bidderRequest.refererInfo.domain, + ref: bidderRequest.refererInfo.ref, + publisher: { domain: bidderRequest.refererInfo.domain }, + }, + device: { + ua: navigator.userAgent, + js: 1, + dnt: getDNT() ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language, + connectiontype: getConnectionType(), + }, + cur: [currency], + source: { + fd: 1, + tid: bidderRequest.ortb2?.source?.tid, + ext: { + prebid: "$prebid.version$", + }, + }, + user: {}, + regs: {}, + ext: {}, + }; + + if (bidderRequest.gdprConsent) { + request.user = { + ext: { + consent: bidderRequest.gdprConsent.consentString || "", + }, + }; + request.regs = { + ext: { + gdpr: + bidderRequest.gdprConsent.gdprApplies !== undefined + ? bidderRequest.gdprConsent.gdprApplies + : true, + }, + }; + } + + if (bidderRequest.ortb2?.source?.ext?.schain) { + request.schain = bidderRequest.ortb2.source.ext.schain; + } + + let bidUserIdAsEids = deepAccess(bidRequests, "0.userIdAsEids"); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(request, "user.eids", bidUserIdAsEids); + } + + const commonFpd = bidderRequest.ortb2 || {}; + const { user, device, site, bcat, badv } = commonFpd; + if (site) { + mergeDeep(request, { site: site }); + } + if (user) { + mergeDeep(request, { user: user }); + } + if (badv) { + mergeDeep(request, { badv: badv }); + } + if (bcat) { + mergeDeep(request, { bcat: bcat }); + } + + if (user?.geo && device?.geo) { + request.device.geo = { ...request.device.geo, ...device.geo }; + request.user.geo = { ...request.user.geo, ...user.geo }; + } else { + if (user?.geo || device?.geo) { + request.user.geo = request.device.geo = user?.geo + ? { ...request.user.geo, ...user.geo } + : { ...request.user.geo, ...device.geo }; + } + } + + if (bidderRequest.ortb2?.device) { + mergeDeep(request.device, bidderRequest.ortb2.device); + } + + return { + method: "POST", + url: ENDPOINT, + data: JSON.stringify(request), + }; + }, + + interpretResponse: (bidResponse, bidRequest) => { + const idToImpMap = {}; + const idToBidMap = {}; + + if (!bidResponse["body"]) { + return []; + } + if (!bidRequest.data) { + return []; + } + const requestImps = parse(bidRequest.data); + if (!requestImps) { + return []; + } + requestImps.imp.forEach((imp) => { + idToImpMap[imp.id] = imp; + }); + bidResponse = bidResponse.body; + if (bidResponse) { + bidResponse.seatbid.forEach((seatBid) => + seatBid.bid.forEach((bid) => { + idToBidMap[bid.impid] = bid; + }) + ); + } + const bids = []; + Object.keys(idToImpMap).forEach((id) => { + const imp = idToImpMap[id]; + const result = idToBidMap[id]; + + if (result) { + const bid = { + requestId: id, + cpm: result.price, + creativeId: result.crid, + ttl: 300, + netRevenue: true, + mediaType: imp.video ? VIDEO : BANNER, + currency: bidResponse.cur, + }; + if (imp.video) { + bid.vastXml = result.adm; + } else if (imp.banner) { + bid.ad = result.adm; + } + bid.width = result.w; + bid.height = result.h; + if (result.burl) bid.burl = result.burl; + if (result.nurl) bid.nurl = result.nurl; + if (result.adomain) { + bid.meta = { + advertiserDomains: result.adomain, + }; + } + bids.push(bid); + } + }); + return bids; + }, + + onBidWon: (bid) => { + if (bid.nurl && isStr(bid.nurl)) { + bid.nurl = replaceMacros(bid.nurl, { + AUCTION_PRICE: bid.cpm, + AUCTION_CURRENCY: bid.cur, + }); + triggerPixel(bid.nurl); + } + }, +}; + +function impression(slot, currency) { + let bidFloorFromModule; + if (typeof slot.getFloor === "function") { + const floorInfo = slot.getFloor({ + currency: "USD", + mediaType: "*", + size: "*", + }); + bidFloorFromModule = + floorInfo?.currency === "USD" ? floorInfo?.floor : undefined; + } + const imp = { + id: slot.bidId, + bidfloor: bidFloorFromModule || slot.params.bidfloor || 0, + bidfloorcur: + (bidFloorFromModule && "USD") || + slot.params.bidfloorcur || + currency || + "USD", + tagid: "" + (slot.params.zone || ""), + }; + + if (slot.mediaTypes.banner) { + imp.banner = bannerImpression(slot); + } else if (slot.mediaTypes.video) { + imp.video = deepAccess(slot, "mediaTypes.video"); + } + imp.ext = slot.params || {}; + const { innerWidth, innerHeight } = getWinDimensions(); + imp.ext.ww = innerWidth || ""; + imp.ext.wh = innerHeight || ""; + return imp; +} + +function bannerImpression(slot) { + const sizes = slot.mediaTypes.banner.sizes || slot.sizes; + return { + format: sizes.map((s) => ({ w: s[0], h: s[1] })), + w: sizes[0][0], + h: sizes[0][1], + }; +} + +function parse(rawResponse) { + try { + if (rawResponse) { + if (typeof rawResponse === "object") { + return rawResponse; + } else { + return JSON.parse(rawResponse); + } + } + } catch (ex) { + logError("empowerBidAdapter", "ERROR", ex); + } + return null; +} + +registerBidder(spec); diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 12e643402d6..27f5171e724 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -420,6 +420,7 @@ function _mapAdUnitPathToElementId(adUnitCode) { } function _getAdSlotHTMLElement(adUnitCode) { + // TODO: this should use getAdUnitElement return document.getElementById(adUnitCode) || document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); } diff --git a/modules/express.js b/modules/express.js deleted file mode 100644 index 760a6f578e5..00000000000 --- a/modules/express.js +++ /dev/null @@ -1,210 +0,0 @@ -import { logMessage, logWarn, logError, logInfo } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; - -const MODULE_NAME = 'express'; -const pbjsInstance = getGlobal(); - -/** - * Express Module - * - * The express module allows the initiation of Prebid.js auctions automatically based on calls such as gpt.defineSlot. - * It works by monkey-patching the gpt methods and overloading their functionality. In order for this module to be - * used gpt must be included in the page, this module must be included in the Prebid.js bundle, and a call to - * pbjs.express() must be made. - * - * @param {Object[]} [adUnits = pbjs.adUnits] - an array of adUnits for express to operate on. - */ -pbjsInstance.express = function(adUnits = pbjsInstance.adUnits) { - logMessage('loading ' + MODULE_NAME); - - if (adUnits.length === 0) { - logWarn('no valid adUnits found, not loading ' + MODULE_NAME); - } - - // store gpt slots in a more performant hash lookup by elementId (adUnit code) - var gptSlotCache = {}; - // put adUnits in a more performant hash lookup by code. - var adUnitsCache = adUnits.reduce(function (cache, adUnit) { - if (adUnit.code && adUnit.bids) { - cache[adUnit.code] = adUnit; - } else { - logError('misconfigured adUnit', null, adUnit); - } - return cache; - }, {}); - - window.googletag = window.googletag || {}; - window.googletag.cmd = window.googletag.cmd || []; - window.googletag.cmd.push(function () { - // verify all necessary gpt functions exist - var gpt = window.googletag; - var pads = gpt.pubads; - if (!gpt.display || !gpt.enableServices || typeof pads !== 'function' || !pads().refresh || !pads().disableInitialLoad || !pads().getSlots || !pads().enableSingleRequest) { - logError('could not bind to gpt googletag api'); - return; - } - logMessage('running'); - - // function to convert google tag slot sizes to [[w,h],...] - function mapGptSlotSizes(aGPTSlotSizes) { - var aSlotSizes = []; - for (var i = 0; i < aGPTSlotSizes.length; i++) { - try { - aSlotSizes.push([aGPTSlotSizes[i].getWidth(), aGPTSlotSizes[i].getHeight()]); - } catch (e) { - logWarn('slot size ' + aGPTSlotSizes[i].toString() + ' not supported by' + MODULE_NAME); - } - } - return aSlotSizes; - } - - // a helper function to verify slots or get slots if not present - function defaultSlots(slots) { - return Array.isArray(slots) - ? slots.slice() - // eslint-disable-next-line no-undef - : googletag.pubads().getSlots().slice(); - } - - // maps gpt slots to adUnits, matches are copied to new array and removed from passed array. - function pickAdUnits(gptSlots) { - var adUnits = []; - // traverse backwards (since gptSlots is mutated) to find adUnits in cache and remove non-mapped slots - for (var i = gptSlots.length - 1; i > -1; i--) { - const gptSlot = gptSlots[i]; - const elemId = gptSlot.getSlotElementId(); - const adUnit = adUnitsCache[elemId]; - - if (adUnit) { - gptSlotCache[elemId] = gptSlot; // store by elementId - adUnit.sizes = adUnit.sizes || mapGptSlotSizes(gptSlot.getSizes()); - adUnits.push(adUnit); - gptSlots.splice(i, 1); - } - } - - return adUnits; - } - - // store original gpt functions that will be overridden - var fGptDisplay = gpt.display; - var fGptEnableServices = gpt.enableServices; - var fGptRefresh = pads().refresh; - var fGptDisableInitialLoad = pads().disableInitialLoad; - var fGptEnableSingleRequest = pads().enableSingleRequest; - - // override googletag.enableServices() - // - make sure fGptDisableInitialLoad() has been called so we can - // better control when slots are displayed, then call original - // fGptEnableServices() - gpt.enableServices = function () { - if (!bInitialLoadDisabled) { - fGptDisableInitialLoad.apply(pads()); - } - return fGptEnableServices.apply(gpt, arguments); - }; - - // override googletag.display() - // - call the real fGptDisplay(). this won't initiate auctions because we've disabled initial load - // - define all corresponding rubicon slots - // - if disableInitialLoad() has been called by the pub, done - // - else run an auction and call the real fGptRefresh() to - // initiate the DFP request - gpt.display = function (sElementId) { - logInfo('display:', sElementId); - // call original gpt display() function - fGptDisplay.apply(gpt, arguments); - - // if not SRA mode, get only the gpt slot corresponding to sEementId - var aGptSlots; - if (!bEnabledSRA) { - // eslint-disable-next-line no-undef - aGptSlots = googletag.pubads().getSlots().filter(function (oGptSlot) { - return oGptSlot.getSlotElementId() === sElementId; - }); - } - - aGptSlots = defaultSlots(aGptSlots).filter(function (gptSlot) { - return !gptSlot._displayed; - }); - - aGptSlots.forEach(function (gptSlot) { - gptSlot._displayed = true; - }); - - var adUnits = pickAdUnits(/* mutated: */ aGptSlots); - - if (!bInitialLoadDisabled) { - if (aGptSlots.length) { - fGptRefresh.apply(pads(), [aGptSlots]); - } - - if (adUnits.length) { - pbjsInstance.requestBids({ - adUnits: adUnits, - bidsBackHandler: function () { - pbjsInstance.setTargetingForGPTAsync(); - fGptRefresh.apply(pads(), [ - adUnits.map(function (adUnit) { - return gptSlotCache[adUnit.code]; - }) - ]); - } - }); - } - } - }; - - // override gpt refresh() function - // - run auctions for provided gpt slots, then initiate ad-server call - pads().refresh = function (aGptSlots, options) { - logInfo('refresh:', aGptSlots); - // get already displayed adUnits from aGptSlots if provided, else all defined gptSlots - aGptSlots = defaultSlots(aGptSlots); - var adUnits = pickAdUnits(/* mutated: */ aGptSlots).filter(function (adUnit) { - return gptSlotCache[adUnit.code]._displayed; - }); - - if (aGptSlots.length) { - fGptRefresh.apply(pads(), [aGptSlots, options]); - } - - if (adUnits.length) { - pbjsInstance.requestBids({ - adUnits: adUnits, - bidsBackHandler: function () { - pbjsInstance.setTargetingForGPTAsync(); - fGptRefresh.apply(pads(), [ - adUnits.map(function (adUnit) { - return gptSlotCache[adUnit.code]; - }), - options - ]); - } - }); - } - }; - - // override gpt disableInitialLoad function - // Register that initial load was called, meaning calls to display() - // should not initiate an ad-server request. Instead a call to - // refresh() will be needed to iniate the request. - // We will assume the pub is using this the correct way, calling it - // before enableServices() - var bInitialLoadDisabled = false; - pads().disableInitialLoad = function () { - bInitialLoadDisabled = true; - return fGptDisableInitialLoad.apply(window.googletag.pubads(), arguments); - }; - - // override gpt useSingleRequest function - // Register that SRA has been turned on - // We will assume the pub is using this the correct way, calling it - // before enableServices() - var bEnabledSRA = false; - pads().enableSingleRequest = function () { - bEnabledSRA = true; - return fGptEnableSingleRequest.apply(window.googletag.pubads(), arguments); - }; - }); -}; diff --git a/modules/fanBidAdapter.js b/modules/fanBidAdapter.js index d4b2954d9d2..a3690cd6098 100644 --- a/modules/fanBidAdapter.js +++ b/modules/fanBidAdapter.js @@ -6,6 +6,7 @@ import { getBidFloor } from '../libraries/currencyUtils/floor.js'; import { getStorageManager } from '../src/storageManager.js'; import { Renderer } from '../src/Renderer.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'freedomadnetwork'; const BIDDER_VERSION = '0.2.0'; @@ -349,8 +350,7 @@ function createRenderer(bid, videoPlayerUrl) { try { renderer.setRender(function (bidResponse) { - const divId = document.getElementById(bid.adUnitCode) ? bid.adUnitCode : getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId; - const adUnit = document.getElementById(divId); + const adUnit = getAdUnitElement(bidResponse) ?? document.getElementById(getGptSlotInfoForAdUnitCode(bid.adUnitCode).divId) if (!window.createOutstreamPlayer) { logWarn('Renderer error: outstream player is not available'); diff --git a/modules/freeWheelAdserverVideo.js b/modules/freeWheelAdserverVideo.js deleted file mode 100644 index cb4bd938373..00000000000 --- a/modules/freeWheelAdserverVideo.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This module adds Freewheel support for Video to Prebid. - */ - -import { registerVideoSupport } from '../src/adServerManager.js'; -import { getHook, submodule } from '../src/hook.js'; - -export const adpodUtils = {}; -export function notifyTranslationModule(fn) { - fn.call(this, 'freewheel'); -} - -getHook('registerAdserver').before(notifyTranslationModule); - -registerVideoSupport('freewheel', { - getTargeting: (args) => adpodUtils.getTargeting(args) -}); - -submodule('adpod', adpodUtils); diff --git a/modules/gamAdServerVideo.js b/modules/gamAdServerVideo.js index 5f52c935554..3fc5dddd678 100644 --- a/modules/gamAdServerVideo.js +++ b/modules/gamAdServerVideo.js @@ -9,7 +9,6 @@ import { auctionManager } from '../src/auctionManager.js'; import { config } from '../src/config.js'; import { EVENTS } from '../src/constants.js'; import * as events from '../src/events.js'; -import { getHook } from '../src/hook.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { targeting } from '../src/targeting.js'; import { @@ -199,12 +198,6 @@ export function buildGamVideoUrl(options) { return buildUrl(Object.assign({}, GAM_ENDPOINT, urlComponents, { search: queryParams })); } -export function notifyTranslationModule(fn) { - fn.call(this, 'dfp'); -} - -if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('registerAdserver').before(notifyTranslationModule); } - /** * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. diff --git a/modules/gamAdpod.js b/modules/gamAdpod.js deleted file mode 100644 index 8aebb860c48..00000000000 --- a/modules/gamAdpod.js +++ /dev/null @@ -1,95 +0,0 @@ -import { submodule } from '../src/hook.js'; -import { buildUrl, deepAccess, formatQS, logError, parseSizesInput } from '../src/utils.js'; -import { auctionManager } from '../src/auctionManager.js'; -import { DEFAULT_GAM_PARAMS, GAM_ENDPOINT, gdprParams } from '../libraries/gamUtils/gamUtils.js'; -import { registerVideoSupport } from '../src/adServerManager.js'; - -export const adpodUtils = {}; - -/** - * @typedef {Object} DfpAdpodOptions - * - * @param {string} code Ad Unit code - * @param {Object} params Query params which should be set on the DFP request. - * These will override this module's defaults whenever they conflict. - * @param {function} callback Callback function to execute when master tag is ready - */ - -/** - * Creates master tag url for long-form - * @param {DfpAdpodOptions} options - * @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.gam.buildAdpodVideoUrl.html - if (!params || !callback) { - logError(`A params object and a callback is required to use pbjs.adServers.gam.buildAdpodVideoUrl`); - return; - } - - const derivedParams = { - correlator: Date.now(), - sz: getSizeForAdUnit(code), - url: encodeURIComponent(location.href), - }; - - function getSizeForAdUnit(code) { - const adUnit = auctionManager.getAdUnits() - .filter((adUnit) => adUnit.code === code) - const sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); - return parseSizesInput(sizes).join('|'); - } - - adpodUtils.getTargeting({ - 'codes': [code], - 'callback': createMasterTag - }); - - function createMasterTag(err, targeting) { - if (err) { - callback(err, null); - return; - } - - const initialValue = { - [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, - [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined - }; - let customParams = {}; - if (targeting[code]) { - customParams = targeting[code].reduce((acc, curValue) => { - if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { - acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; - } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { - acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] - } - return acc; - }, initialValue); - } - - const encodedCustomParams = encodeURIComponent(formatQS(customParams)); - - const queryParams = Object.assign({}, - DEFAULT_GAM_PARAMS, - derivedParams, - params, - { cust_params: encodedCustomParams }, - gdprParams(), - ); - - const masterTag = buildUrl({ - ...GAM_ENDPOINT, - search: queryParams - }); - - callback(null, masterTag); - } -} - -registerVideoSupport('gam', { - buildAdpodVideoUrl: buildAdpodVideoUrl, - getAdpodTargeting: (args) => adpodUtils.getTargeting(args) -}); - -submodule('adpod', adpodUtils); diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index a8611176e10..f1ae1d1aab4 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -16,6 +16,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const ENDPOINTS = { 'gamoshi': 'https://rtb.gamoshi.io', 'cleanmedianet': 'https://bidder.cleanmediaads.com' @@ -250,7 +251,7 @@ function renderOutstream(bid) { window['GamoshiPlayer'].renderAd({ id: unitId, debug: window.location.href.indexOf('pbjsDebug') >= 0, - placement: document.getElementById(bid.adUnitCode), + placement: getAdUnitElement(bid), width: bid.width, height: bid.height, events: { diff --git a/modules/gmosspBidAdapter.js b/modules/gmosspBidAdapter.js index c043e852831..28b2d7cb7b8 100644 --- a/modules/gmosspBidAdapter.js +++ b/modules/gmosspBidAdapter.js @@ -1,6 +1,5 @@ import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; -import { getDNT } from '../libraries/dnt/index.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { @@ -51,7 +50,6 @@ export const spec = { const urlInfo = getUrlInfo(bidderRequest.refererInfo); const cur = getCurrencyType(bidderRequest); - const dnt = getDNT() ? '1' : '0'; for (let i = 0; i < validBidRequests.length; i++) { let queryString = ''; @@ -76,7 +74,6 @@ export const spec = { queryString = tryAppendQueryString(queryString, 'meta_url', urlInfo.canonicalLink); queryString = tryAppendQueryString(queryString, 'ref', urlInfo.ref); queryString = tryAppendQueryString(queryString, 'cur', cur); - queryString = tryAppendQueryString(queryString, 'dnt', dnt); bidRequests.push({ method: 'GET', diff --git a/modules/gptPreAuction.ts b/modules/gptPreAuction.ts index 3c9b071098e..5bfb9d243a0 100644 --- a/modules/gptPreAuction.ts +++ b/modules/gptPreAuction.ts @@ -13,8 +13,6 @@ import { pick, uniques } from '../src/utils.js'; -import type { SlotMatchingFn } from '../src/targeting.ts'; -import type { AdUnitCode } from '../src/types/common.d.ts'; import type { AdUnit } from '../src/adUnits.ts'; const MODULE_NAME = 'GPT Pre-Auction'; @@ -66,8 +64,6 @@ export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) { } export const appendGptSlots = adUnits => { - const { customGptSlotMatching } = _currentConfig; - if (!isGptPubadsDefined()) { return; } @@ -81,9 +77,7 @@ export const appendGptSlots = adUnits => { const adUnitPaths = {}; window.googletag.pubads().getSlots().forEach((slot: googletag.Slot) => { - const matchingAdUnitCode = Object.keys(adUnitMap).find(customGptSlotMatching - ? customGptSlotMatching(slot) - : isAdUnitCodeMatchingSlot(slot)); + const matchingAdUnitCode = Object.keys(adUnitMap).find(isAdUnitCodeMatchingSlot(slot)); if (matchingAdUnitCode) { const path = adUnitPaths[matchingAdUnitCode] = slot.getAdUnitPath(); @@ -174,16 +168,9 @@ type GPTPreAuctionConfig = { */ enabled?: boolean; /** - * If true, use default behavior for determining GPID and PbAdSlot. Defaults to false. + * If true, use default behavior for determining GPID. Defaults to false. */ useDefaultPreAuction?: boolean; - customGptSlotMatching?: SlotMatchingFn; - /** - * @param adUnitCode Ad unit code - * @param adServerAdSlot The value of that ad unit's `ortb2Imp.ext.data.adserver.adslot` - * @returns pbadslot for the ad unit - */ - customPbAdSlot?: (adUnitCode: AdUnitCode, adServerAdSlot: string) => string; /** * @param adUnit An ad unit object * @param adServerAdSlot The value of that ad unit's `ortb2Imp.ext.data.adserver.adslot` @@ -206,8 +193,6 @@ declare module '../src/config' { const handleSetGptConfig = moduleConfig => { _currentConfig = pick(moduleConfig, [ 'enabled', enabled => enabled !== false, - 'customGptSlotMatching', customGptSlotMatching => - typeof customGptSlotMatching === 'function' && customGptSlotMatching, 'customPreAuction', customPreAuction => typeof customPreAuction === 'function' && customPreAuction, 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction ?? true, ]); diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index 93d9a2b10dd..e49c23bbf27 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -93,11 +93,6 @@ const growthCodeAnalyticsAdapter = Object.assign(adapter({ url: url, analyticsTy break; } - case EVENTS.ADD_AD_UNITS: { - data = eventData; - break; - } - case EVENTS.NO_BID: { data = eventData break; diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 316a5401da9..6aca8a36728 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -1,11 +1,13 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { _each, deepAccess, getWinDimensions, logError, logWarn, parseSizesInput } from '../src/utils.js'; +import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { config } from '../src/config.js'; -import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; -import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRatio.js'; import { getStorageManager } from '../src/storageManager.js'; + import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -279,7 +281,7 @@ function _getDeviceData(ortb2Data) { ipv6: _device.ipv6, ua: _device.ua, sua: _device.sua ? JSON.stringify(_device.sua) : undefined, - dnt: _device.dnt, + dnt: getDNT() ? 1 : 0, os: _device.os, osv: _device.osv, dt: _device.devicetype, @@ -303,78 +305,6 @@ function _getDeviceData(ortb2Data) { }, {}); } -/** - * Retrieves content metadata from the ORTB2 object - * Supports both site.content and app.content (site takes priority) - * @param {Object} ortb2Data ORTB2 object - * @returns {Object} Content parameters - */ -function _getContentParams(ortb2Data) { - // Check site.content first, then app.content - const siteContent = deepAccess(ortb2Data, 'site.content'); - const appContent = deepAccess(ortb2Data, 'app.content'); - const content = siteContent || appContent; - - if (!content) { - return {}; - } - - const contentParams = {}; - contentParams.itype = siteContent ? 'site' : 'app'; - - // Basic content fields - if (content.id) contentParams.cid = content.id; - if (content.episode !== undefined && content.episode !== null) contentParams.cepisode = content.episode; - if (content.title) contentParams.ctitle = content.title; - if (content.series) contentParams.cseries = content.series; - if (content.season) contentParams.cseason = content.season; - if (content.genre) contentParams.cgenre = content.genre; - if (content.contentrating) contentParams.crating = content.contentrating; - if (content.userrating) contentParams.cur = content.userrating; - if (content.context !== undefined && content.context !== null) contentParams.cctx = content.context; - if (content.livestream !== undefined && content.livestream !== null) contentParams.clive = content.livestream; - if (content.len !== undefined && content.len !== null) contentParams.clen = content.len; - if (content.language) contentParams.clang = content.language; - if (content.url) contentParams.curl = content.url; - if (content.cattax !== undefined && content.cattax !== null) contentParams.cattax = content.cattax; - if (content.prodq !== undefined && content.prodq !== null) contentParams.cprodq = content.prodq; - if (content.qagmediarating !== undefined && content.qagmediarating !== null) contentParams.cqag = content.qagmediarating; - - // Handle keywords - can be string or array - if (content.keywords) { - if (Array.isArray(content.keywords)) { - contentParams.ckw = content.keywords.join(','); - } else if (typeof content.keywords === 'string') { - contentParams.ckw = content.keywords; - } - } - - // Handle cat array - if (content.cat && Array.isArray(content.cat) && content.cat.length > 0) { - contentParams.ccat = content.cat.join(','); - } - - // Handle producer fields - if (content.producer) { - if (content.producer.id) contentParams.cpid = content.producer.id; - if (content.producer.name) contentParams.cpname = content.producer.name; - } - - // Channel fields - if (content.channel) { - if (content.channel.id) contentParams.cchannelid = content.channel.id; - if (content.channel.name) contentParams.cchannel = content.channel.name; - if (content.channel.domain) contentParams.cchanneldomain = content.channel.domain; - } - - // Network fields - if (content.network) { - if (content.network.name) contentParams.cnetwork = content.network.name; - } - - return contentParams; -} - /** * loops through bannerSizes array to get greatest slot dimensions * @param {number[][]} sizes @@ -397,53 +327,28 @@ function getGreatestDimensions(sizes) { return [maxw, maxh]; } -function getFirstUid(eid) { - if (!eid || !Array.isArray(eid.uids)) return null; - return eid.uids.find(uid => uid && uid.id); -} - -function getUserEids(bidRequest, bidderRequest) { - const bidderRequestEids = deepAccess(bidderRequest, 'ortb2.user.ext.eids'); - if (Array.isArray(bidderRequestEids) && bidderRequestEids.length) { - return bidderRequestEids; - } - const bidEids = deepAccess(bidRequest, 'userIdAsEids'); - if (Array.isArray(bidEids) && bidEids.length) { - return bidEids; - } - const bidUserEids = deepAccess(bidRequest, 'user.ext.eids'); - if (Array.isArray(bidUserEids) && bidUserEids.length) { - return bidUserEids; - } - return []; -} - -function isPubProvidedIdEid(eid) { - const source = (eid && eid.source) ? eid.source.toLowerCase() : ''; - if (!source || !pubProvidedIdSources.includes(source) || !Array.isArray(eid.uids)) return false; - return eid.uids.some(uid => uid && uid.ext && uid.ext.stype); -} - -function getEidsFromEidsArray(eids) { - return (Array.isArray(eids) ? eids : []).reduce((ids, eid) => { - const source = (eid.source || '').toLowerCase(); - if (source === 'uidapi.com') { - const uid = getFirstUid(eid); - if (uid) { - ids.uid2 = uid.id; - } - } else if (source === 'liveramp.com') { - const uid = getFirstUid(eid); - if (uid) { - ids.idl_env = uid.id; - } - } else if (source === 'adserver.org' && Array.isArray(eid.uids)) { - const tdidUid = eid.uids.find(uid => uid && uid.id && uid.ext && uid.ext.rtiPartner === 'TDID'); - if (tdidUid) { - ids.tdid = tdidUid.id; - } +function getEids(userId) { + const idProperties = [ + 'uid', + 'eid', + 'lipbid', + 'envelope', + 'id' + ]; + + return Object.keys(userId).reduce(function (eids, provider) { + const eid = userId[provider]; + switch (typeof eid) { + case 'string': + eids[provider] = eid; + break; + + case 'object': + const idProp = idProperties.filter(prop => eid.hasOwnProperty(prop)); + idProp.length && (eids[provider] = eid[idProp[0]]); + break; } - return ids; + return eids; }, {}); } @@ -467,14 +372,13 @@ function buildRequests(validBidRequests, bidderRequest) { bidId, mediaTypes = {}, params = {}, + userId = {}, ortb2Imp, adUnitCode = '' } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); - const userEids = getUserEids(bidRequest, bidderRequest); - const eids = getEidsFromEidsArray(userEids); + const eids = getEids(userId); const gpid = deepAccess(ortb2Imp, 'ext.gpid'); - const paapiEligible = deepAccess(ortb2Imp, 'ext.ae') === 1 let sizes = [1, 1]; let data = {}; data.displaymanager = 'Prebid.js - gumgum'; @@ -497,20 +401,16 @@ function buildRequests(validBidRequests, bidderRequest) { } } // Send filtered pubProvidedId's - if (userEids.length) { - const filteredData = userEids.filter(isPubProvidedIdEid); + if (userId && userId.pubProvidedId) { + const filteredData = userId.pubProvidedId.filter(item => pubProvidedIdSources.includes(item.source)); const maxLength = 1800; // replace this with your desired maximum length const truncatedJsonString = jsoStringifynWithMaxLength(filteredData, maxLength); - if (filteredData.length) { - data.pubProvidedId = truncatedJsonString - } + data.pubProvidedId = truncatedJsonString } // ADJS-1286 Read id5 id linktype field - const id5Eid = userEids.find(eid => (eid.source || '').toLowerCase() === 'id5-sync.com'); - const id5Uid = getFirstUid(id5Eid); - if (id5Uid && id5Uid.ext) { - data.id5Id = id5Uid.id || null - data.id5IdLinkType = id5Uid.ext.linkType || null + if (userId && userId.id5id && userId.id5id.uid && userId.id5id.ext) { + data.id5Id = userId.id5id.uid || null + data.id5IdLinkType = userId.id5id.ext.linkType || null } // ADTS-169 add adUnitCode to requests if (adUnitCode) data.aun = adUnitCode; @@ -538,10 +438,9 @@ function buildRequests(validBidRequests, bidderRequest) { } if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site) { setIrisId(data, bidderRequest.ortb2.site, params); + const curl = bidderRequest.ortb2.site.content?.url; + if (curl) data.curl = curl; } - // Extract content metadata from ortb2 - const contentParams = _getContentParams(bidderRequest?.ortb2); - Object.assign(data, contentParams); if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; } @@ -568,9 +467,6 @@ function buildRequests(validBidRequests, bidderRequest) { } else { // legacy params data = { ...data, ...handleLegacyParams(params, sizes) }; } - if (paapiEligible) { - data.ae = paapiEligible - } if (gdprConsent) { data.gdprApplies = gdprConsent.gdprApplies ? 1 : 0; } diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js index 63e00622176..90b284a485c 100644 --- a/modules/h12mediaBidAdapter.js +++ b/modules/h12mediaBidAdapter.js @@ -2,6 +2,7 @@ import { inIframe, logError, logMessage, deepAccess, getWinDimensions } from '.. import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getViewportSize } from '../libraries/viewport/viewport.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'h12media'; const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/'; const DEFAULT_CURRENCY = 'USD'; @@ -30,7 +31,7 @@ export const spec = { pubsubid = ''; } const pubcontainerid = bidderParams.pubcontainerid; - const adUnitElement = document.getElementById(pubcontainerid || bidRequest.adUnitCode); + const adUnitElement = pubcontainerid ? document.getElementById(pubcontainerid) : getAdUnitElement(bidRequest); const ishidden = !isVisible(adUnitElement); const framePos = getFramePos(); const coords = isiframe ? { diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 37c1478fcb8..dae64cd0e5b 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -111,11 +111,6 @@ const hadronAnalyticsAdapter = Object.assign(adapter({ url: HADRON_ANALYTICS_URL break; } - case EVENTS.ADD_AD_UNITS: { - data = args; - break; - } - case EVENTS.AD_RENDER_FAILED: { data = args; break; diff --git a/modules/hypelabBidAdapter.js b/modules/hypelabBidAdapter.js index bc562b84cb3..2112d93b9e5 100644 --- a/modules/hypelabBidAdapter.js +++ b/modules/hypelabBidAdapter.js @@ -5,6 +5,7 @@ import { getDevicePixelRatio } from '../libraries/devicePixelRatio/devicePixelRa import { ajax } from '../src/ajax.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getWalletPresence, getWalletProviderFlags } from '../libraries/hypelabUtils/hypelabUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; export const BIDDER_CODE = 'hypelab'; export const ENDPOINT_URL = 'https://api.hypelab.com'; @@ -55,7 +56,7 @@ function buildRequests(validBidRequests, bidderRequest) { winDimensions?.innerHeight || 0 ), ]; - const pp = getPosition(request.adUnitCode); + const pp = getPosition(request); const payload = { property_slug: request.params.property_slug, @@ -121,8 +122,8 @@ function getBidFloor(bid, sizes) { return floor; } -function getPosition(id) { - const element = document.getElementById(id); +function getPosition(bidRequest) { + const element = getAdUnitElement(bidRequest); if (!element) return null; const rect = getBoundingClientRect(element); return [rect.left, rect.top]; diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index 8ecdca972d3..14a35d318f5 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,11 +1,11 @@ 'use strict'; -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, generateUUID, getWinDimensions, isPlainObject } 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'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/intersectionRtdProvider.js b/modules/intersectionRtdProvider.js deleted file mode 100644 index b57fd919d16..00000000000 --- a/modules/intersectionRtdProvider.js +++ /dev/null @@ -1,118 +0,0 @@ -import { submodule } from '../src/hook.js'; -import { isFn, logError } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; - -import '../src/adapterManager.js'; - -let observerAvailable = true; -function getIntersectionData(requestBidsObject, onDone, providerConfig, userConsent) { - const intersectionMap = {}; - const placeholdersMap = {}; - let done = false; - if (!observerAvailable) return complete(); - const observer = new IntersectionObserver(observerCallback, { threshold: 0.5 }); - const adUnitCodes = requestBidsObject.adUnitCodes || []; - const auctionDelay = config.getConfig('realTimeData.auctionDelay') || 0; - const waitForIt = providerConfig.waitForIt; - let adUnits = requestBidsObject.adUnits || getGlobal().adUnits || []; - if (adUnitCodes.length) { - adUnits = adUnits.filter(unit => adUnitCodes.includes(unit.code)); - } - let checkTimeoutId; - findAndObservePlaceholders(); - if (auctionDelay > 0) { - setTimeout(complete, auctionDelay); - } - function findAndObservePlaceholders() { - const observed = adUnits.filter((unit) => { - const code = unit.code; - if (placeholdersMap[code]) return true; - const ph = document.getElementById(code); - if (ph) { - placeholdersMap[code] = ph; - observer.observe(ph); - return true; - } - return false; - }); - if ( - observed.length === adUnits.length || - !waitForIt || - auctionDelay <= 0 - ) { - return; - } - checkTimeoutId = setTimeout(findAndObservePlaceholders); - } - function observerCallback(entries) { - let entry = entries.pop(); - while (entry) { - const target = entry.target; - const id = target.getAttribute('id'); - if (id) { - const intersection = intersectionMap[id]; - if (!intersection || intersection.time < entry.time) { - intersectionMap[id] = { - 'boundingClientRect': cloneRect(entry.boundingClientRect), - 'intersectionRect': cloneRect(entry.intersectionRect), - 'rootRect': cloneRect(entry.rootRect), - 'intersectionRatio': entry.intersectionRatio, - 'isIntersecting': entry.isIntersecting, - 'time': entry.time - }; - if (adUnits.every(unit => !!intersectionMap[unit.code])) { - complete(); - } - } - } - entry = entries.pop(); - } - } - function complete() { - if (done) return; - if (checkTimeoutId) clearTimeout(checkTimeoutId); - done = true; - checkTimeoutId = null; - observer && observer.disconnect(); - adUnits && adUnits.forEach((unit) => { - const intersection = intersectionMap[unit.code]; - if (intersection && unit.bids) { - unit.bids.forEach(bid => { - bid.intersection = intersection; - }); - } - }); - onDone(); - } -} -function init(moduleConfig) { - if (!isFn(window.IntersectionObserver)) { - logError('IntersectionObserver is not defined'); - observerAvailable = false; - } else { - observerAvailable = true; - } - return observerAvailable; -} -function cloneRect(rect) { - return rect ? { - 'left': rect.left, - 'top': rect.top, - 'right': rect.right, - 'bottom': rect.bottom, - 'width': rect.width, - 'height': rect.height, - 'x': rect.x, - 'y': rect.y, - } : rect; -} -export const intersectionSubmodule = { - name: 'intersection', - getBidRequestData: getIntersectionData, - init: init, -}; -function registerSubModule() { - submodule('realTimeData', intersectionSubmodule); -} -registerSubModule(); diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index d6b5fc3efef..e835df6ce47 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -27,7 +27,6 @@ const { BIDDER_DONE, SET_TARGETING, REQUEST_BIDS, - ADD_AD_UNITS, AD_RENDER_FAILED, } = EVENTS; @@ -187,10 +186,6 @@ function handleEvent(eventType, eventArgs) { invisiblyEvent = eventArgs; break; } - case ADD_AD_UNITS: { - invisiblyEvent = eventArgs; - break; - } case AD_RENDER_FAILED: { invisiblyEvent = eventArgs; break; diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 18413ad5d77..b2da2f3c077 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -23,14 +23,16 @@ 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 { getAdUnitElement } from '../src/utils/adUnits.js'; const divIdCache = {}; -export function getDivIdFromAdUnitCode(adUnitCode) { +export function getDivIdFromAdUnit(adUnitCode, target) { if (divIdCache[adUnitCode]) { return divIdCache[adUnitCode]; } - const divId = document.getElementById(adUnitCode) ? adUnitCode : getGptSlotInfoForAdUnitCode(adUnitCode).divId; + const element = getAdUnitElement(target); + const divId = element?.id ? element.id : getGptSlotInfoForAdUnitCode(adUnitCode).divId; divIdCache[adUnitCode] = divId; return divId; } @@ -686,8 +688,7 @@ 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 - const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') - const ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); + const ixdiag = buildIXDiag(validBidRequests); for (const key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; } @@ -954,8 +955,6 @@ 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 paapi = impressions[impKeys[adUnitIndex]].paapi; const bannerImpressions = impressionObjects.filter(impression => BANNER in impression); const otherImpressions = impressionObjects.filter(impression => !(BANNER in impression)); @@ -1006,7 +1005,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID || paapi) { + if (dfpAdUnitCode || gpid || tid || sid || externalID) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; @@ -1014,12 +1013,6 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.ext.tid = tid; _bannerImpression.ext.sid = sid; _bannerImpression.ext.externalID = externalID; - - // enable fledge auction - if (Number(auctionEnvironment) === 1) { - _bannerImpression.ext.ae = 1; - _bannerImpression.ext.paapi = paapi; - } } if ('bidfloor' in bannerImps[0]) { @@ -1273,10 +1266,9 @@ function addIdentifiersInfo(impressions, r, impKeys, adUnitIndex, payload, baseU * Calculates IX diagnostics values and packages them into an object * * @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) { +function buildIXDiag(validBidRequests) { var adUnitMap = validBidRequests .map(bidRequest => bidRequest.adUnitCode) .filter((value, index, arr) => arr.indexOf(value) === index); @@ -1293,7 +1285,6 @@ function buildIXDiag(validBidRequests, fledgeEnabled) { version: '$prebid.version$', url: window.location.href.split('?')[0], vpd: defaultVideoPlacement, - ae: fledgeEnabled, eidLength: allEids.length }; @@ -1369,7 +1360,7 @@ function createNativeImps(validBidRequest, nativeImps) { nativeImps[validBidRequest.adUnitCode].tagId = deepAccess(validBidRequest, 'params.tagId'); const adUnitCode = validBidRequest.adUnitCode; - const divId = getDivIdFromAdUnitCode(adUnitCode); + const divId = getDivIdFromAdUnit(adUnitCode, validBidRequest); nativeImps[validBidRequest.adUnitCode].adUnitCode = adUnitCode; nativeImps[validBidRequest.adUnitCode].divId = divId; } @@ -1391,7 +1382,7 @@ function createVideoImps(validBidRequest, videoImps) { videoImps[validBidRequest.adUnitCode].tagId = deepAccess(validBidRequest, 'params.tagId'); const adUnitCode = validBidRequest.adUnitCode; - const divId = getDivIdFromAdUnitCode(adUnitCode); + const divId = getDivIdFromAdUnit(adUnitCode, validBidRequest); videoImps[validBidRequest.adUnitCode].adUnitCode = adUnitCode; videoImps[validBidRequest.adUnitCode].divId = divId; } @@ -1418,23 +1409,6 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde 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, 'paapi.enabled') - if (fledgeEnabled) { - const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') - const paapi = deepAccess(validBidRequest, 'ortb2Imp.ext.paapi') - if (paapi) { - bannerImps[validBidRequest.adUnitCode].paapi = paapi - } - if (auctionEnvironment) { - if (isInteger(auctionEnvironment)) { - bannerImps[validBidRequest.adUnitCode].ae = auctionEnvironment; - } else { - logWarn('error setting auction environment flag - must be an integer') - } - } - } - // AdUnit-Specific First Party Data const adUnitFPD = deepAccess(validBidRequest, 'ortb2Imp.ext.data'); if (adUnitFPD) { @@ -1447,7 +1421,7 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde } const adUnitCode = validBidRequest.adUnitCode; - const divId = getDivIdFromAdUnitCode(adUnitCode); + const divId = getDivIdFromAdUnit(adUnitCode, validBidRequest); bannerImps[validBidRequest.adUnitCode].adUnitCode = adUnitCode; bannerImps[validBidRequest.adUnitCode].divId = divId; @@ -1515,7 +1489,7 @@ function createMissingBannerImp(bid, imp, newSize) { function outstreamRenderer(bid) { bid.renderer.push(function () { const adUnitCode = bid.adUnitCode; - const divId = getDivIdFromAdUnitCode(adUnitCode); + const divId = getDivIdFromAdUnit(adUnitCode, bid); if (!divId) { logWarn(`IX Bid Adapter: adUnitCode: ${divId} not found on page.`); return; @@ -1767,9 +1741,6 @@ export const spec = { const bids = []; let bid = null; - // Extract the FLEDGE auction configuration list from the response - let fledgeAuctionConfigs = deepAccess(serverResponse, 'body.ext.protectedAudienceAuctionConfigs') || []; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); if (!serverResponse.hasOwnProperty('body')) { @@ -1813,29 +1784,7 @@ export const spec = { } } } - - 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, - paapi: fledgeAuctionConfigs, - }; - } catch (error) { - logWarn('Error attaching AuctionConfigs', error); - return bids; - } - } else { - return bids; - } + return bids; }, /** @@ -2045,15 +1994,6 @@ 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 e6f6f8dc320..14a013fd6be 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -468,11 +468,6 @@ 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 [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module to build and enable Fledge. - Additional Information ====================== diff --git a/modules/jixieBidAdapter.js b/modules/jixieBidAdapter.js index 418fbeb7a63..67895347e73 100644 --- a/modules/jixieBidAdapter.js +++ b/modules/jixieBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, isArray, logWarn, isFn, isPlainObject, logError, logInfo, getWinDimensions } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -7,6 +6,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { Renderer } from '../src/Renderer.js'; +import { getDNT } from '../libraries/dnt/index.js'; const ADAPTER_VERSION = '2.1.0'; const PREBID_VERSION = '$prebid.version$'; diff --git a/modules/jwplayerBidAdapter.js b/modules/jwplayerBidAdapter.js index 8a6b2909f60..3ff51fce8c1 100644 --- a/modules/jwplayerBidAdapter.js +++ b/modules/jwplayerBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { isArray, isFn, deepAccess, deepSetValue, logError, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; import { hasPurpose1Consent } from '../src/utils/gdpr.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'jwplayer'; const BASE_URL = 'https://vpb-server.jwplayer.com/'; diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index cba29908229..33355868d19 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -199,7 +199,6 @@ function buildRequests(validBidRequests, bidderRequest) { function interpretResponse(response, bidRequest) { const bids = response.body; - const fledgeAuctionConfigs = []; const bidResponses = []; if (isEmpty(bids) || typeof bids !== 'object') { @@ -241,23 +240,9 @@ function interpretResponse(response, bidRequest) { } bidResponses.push(bidResponse); - - if (adUnit.auctionConfig) { - fledgeAuctionConfigs.push({ - bidId: bidID, - config: adUnit.auctionConfig - }) - } } - if (fledgeAuctionConfigs.length > 0) { - return { - bids: bidResponses, - paapi: fledgeAuctionConfigs - } - } else { - return bidResponses; - } + return bidResponses; } function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index 7447d893217..8c9eaeb925c 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -1,8 +1,8 @@ import * as utils from '../src/utils.js'; -import { getDNT } from '../libraries/dnt/index.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index abeb2095ff7..686d90b7cc7 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -4,6 +4,7 @@ import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const ANALYTICSTYPE = 'endpoint'; const URL = 'https://lwadm.com/analytics/10'; @@ -41,7 +42,7 @@ const livewrappedAnalyticsAdapter = Object.assign(adapter({ EMPTYURL, ANALYTICST cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined; cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined; let lwFloor; - const container = document.getElementById(bidRequest.adUnitCode); + const container = getAdUnitElement(bidRequest); let adUnitId = container ? container.getAttribute('data-adunitid') : undefined; adUnitId = adUnitId != null ? adUnitId : undefined; diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index 535dbbf759b..a017b3c2eb3 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -46,7 +46,6 @@ export const spec = { const DOMAIN = bid.params.pageurl || REFERER; const GDPR = BIDDER_GDPR || bid.params.gdpr || null; const GDPRS = BIDDER_GDPRS || bid.params.gdprs || null; - const DNT = bid.params.dnt || null; const BID_FLOOR = 0; const VIDEO_BID = bid.video ? bid.video : {}; @@ -76,10 +75,6 @@ export const spec = { } }; - if (isSet(DNT)) { - requestData.device.dnt = DNT; - } - if (isSet(config.getConfig('coppa'))) { requestData.regs.coppa = config.getConfig('coppa') === true ? 1 : 0; } diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index 18aac8faa0d..aeff148880f 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -42,14 +42,6 @@ export const spec = { bids.push(seatbid.bid); }) - const fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.fledgeAuctionConfigs') || []; - if (fledgeAuctionConfigs.length) { - return { - bids, - paapi: fledgeAuctionConfigs, - }; - } - return bids; }, getUserSyncs: function (syncOptions, serverResponses) { @@ -74,14 +66,6 @@ function newBidRequest(bidRequest, bidderRequest) { mediaTypes: bidRequest.mediaTypes, } - const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') - 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: bidRequest.auctionId, diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js index 78016f9fa56..52f7718a7ab 100755 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -106,34 +106,7 @@ function interpretResponse(serverResponse, bidRequest) { })); logInfo('interpretResponse', { serverResponse, bidRequest, bidRequestData, bids }); - - if (response?.enable_pa === false) { return bids; } - - const fledgeAuctionConfigs = (response.bids || []).map(bid => ({ - bidId: bid?.bid_id, - config: { - seller: baseUrl, - decisionLogicUrl: `${baseUrl}/js/ssp.js`, - interestGroupBuyers: [baseUrl], - requestedSize: bid?.size, - auctionSignals: { - size: bid?.size, - }, - perBuyerSignals: { - [baseUrl]: { - prebid_paapi: true, - prebid_bid_id: bid?.bid_id, - prebid_request_id: bidRequestData.request_id, - placement_id: bid.placement_id, - // floor, - is_sra: true, - endpoint_url: endpointUrl, - }, - } - } - })); - - return { bids, paapi: fledgeAuctionConfigs }; + return { bids }; } function report(type, data) { diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 633f5c380ff..074a96b6268 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -1,11 +1,12 @@ 'use strict'; -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, parseSizesInput, isArray, getWindowTop, deepSetValue, triggerPixel, getWindowSelf, isPlainObject } 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 { percentInView } from '../libraries/percentInView/percentInView.js'; import { getMinSize } from '../libraries/sizeUtils/sizeUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; +import { getDNT } from '../libraries/dnt/index.js'; function MarsmediaAdapter() { this.code = 'marsmedia'; @@ -167,7 +168,7 @@ function MarsmediaAdapter() { 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 element = getAdUnitElement(bid); const minSize = getMinSize(processedSizes); const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), minSize) diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js index fa728d01944..2fda15971ea 100644 --- a/modules/mediaforceBidAdapter.js +++ b/modules/mediaforceBidAdapter.js @@ -1,9 +1,9 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, isStr, replaceAuctionPrice, triggerPixel, parseGPTSingleSizeArrayToRtbSize } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { buildNativeRequest, parseNativeResponse } from '../libraries/nativeAssetsUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 135ee52a880..7fe6538c7ad 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -28,6 +28,8 @@ import { } from '../libraries/appnexusUtils/anKeywords.js'; import { convertCamelToUnderscore } from '../libraries/appnexusUtils/anUtils.js'; import { chunk } from '../libraries/chunk/chunk.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; + const BIDDER_CODE = 'mediafuse'; const GVLID = 32; const ENDPOINT_URL_NORMAL = 'https://ib.adnxs.com/openrtb2/prebidjs'; @@ -491,7 +493,7 @@ const converter = ortbConverter({ tag_id: extANData.tag_id, uuid: bidResponse.requestId }; - bidResponse.renderer = newRenderer(bidRequest.adUnitCode, { + bidResponse.renderer = newRenderer(bidRequest, { renderer_url: extANData.renderer_url, renderer_id: extANData.renderer_id, }, rendererOptions); @@ -676,13 +678,13 @@ function getBidFloor(bid) { return null; } -function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { +function newRenderer(bidRequest, rtbBid, rendererOptions = {}) { const renderer = Renderer.install({ id: rtbBid.renderer_id, url: rtbBid.renderer_url, config: rendererOptions, loaded: false, - adUnitCode, + adUnitCode: bidRequest.adUnitCode, }); try { @@ -696,7 +698,7 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { loaded: () => logMessage('Mediafuse outstream video loaded event'), ended: () => { logMessage('Mediafuse outstream renderer video event'); - const el = document.getElementById(adUnitCode); + const el = getAdUnitElement(bidRequest); if (el) { el.style.display = 'none'; } @@ -705,9 +707,9 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { return renderer; } -function hidedfpContainer(elementId) { +function hidedfpContainer(container) { try { - const el = document.getElementById(elementId).querySelectorAll("div[id^='google_ads']"); + const el = container.querySelectorAll("div[id^='google_ads']"); if (el[0]) { el[0].style.setProperty('display', 'none'); } @@ -716,9 +718,9 @@ function hidedfpContainer(elementId) { } } -function hideSASIframe(elementId) { +function hideSASIframe(container) { try { - const el = document.getElementById(elementId).querySelectorAll("script[id^='sas_script']"); + const el = container.querySelectorAll("script[id^='sas_script']"); if (el[0]?.nextSibling?.localName === 'iframe') { el[0].nextSibling.style.setProperty('display', 'none'); } @@ -739,8 +741,9 @@ function handleOutstreamRendererEvents(bid, id, eventName) { } function outstreamRender(bid, doc) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); + const container = getAdUnitElement(bid); + hidedfpContainer(container); + hideSASIframe(container); bid.renderer.push(() => { const win = doc?.defaultView || window; if (win.ANOutstreamVideo) { diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index 4aed102c6dc..c7065a95f2b 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { cleanObj, deepAccess, @@ -21,6 +20,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getDNT } from '../libraries/dnt/index.js'; const AUCTION_TYPE = 1; const BIDDER_CODE = 'mediakeys'; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 3a98dcf57b9..f8ff33ddc81 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -23,6 +23,7 @@ import { GLOBAL_VENDOR_ID, MEDIANET } from '../libraries/medianetUtils/constants import { getGlobal } from '../src/prebidGlobal.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getMinSize } from '../libraries/sizeUtils/sizeUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -133,11 +134,11 @@ function getWindowSize() { } } -function getCoordinates(adUnitCode) { - let element = document.getElementById(adUnitCode); - if (!element && adUnitCode.indexOf('/') !== -1) { +function getCoordinates(bidRequest) { + let element = getAdUnitElement(bidRequest); + if (!element && bidRequest.adUnitCode.indexOf('/') !== -1) { // now it means that adUnitCode is GAM AdUnitPath - const { divId } = getGptSlotInfoForAdUnitCode(adUnitCode); + const { divId } = getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode); if (isStr(divId)) { element = document.getElementById(divId); } @@ -239,7 +240,7 @@ function slotParams(bidRequest, bidderRequests) { if (bidFloor) { params.bidfloor = bidFloor; } - const coordinates = getCoordinates(bidRequest.adUnitCode); + const coordinates = getCoordinates(bidRequest); if (coordinates && params.banner && params.banner.length !== 0) { const normCoordinates = normalizeCoordinates(coordinates); params.ext.coordinates = normCoordinates; @@ -256,9 +257,6 @@ function slotParams(bidRequest, bidderRequests) { if (floorInfo && floorInfo.length > 0) { params.bidfloors = floorInfo; } - if (bidderRequests.paapi?.enabled) { - params.ext.ae = bidRequest?.ortb2Imp?.ext?.ae; - } return params; } @@ -487,7 +485,7 @@ export const spec = { * Unpack the response from the server into a list of bids. * * @param {*} serverResponse A successful response from the server. - * @returns {{bids: *[], fledgeAuctionConfigs: *[]} | *[]} An object containing bids and fledgeAuctionConfigs if present, otherwise an array of bids. + * @returns {*[]} An array of bids. */ interpretResponse: function(serverResponse, request) { let validBids = []; @@ -502,18 +500,7 @@ export const spec = { validBids = bids.filter(bid => isValidBid(bid)); validBids.forEach(addRenderer); } - const fledgeAuctionConfigs = deepAccess(serverResponse, 'body.ext.paApiAuctionConfigs') || []; - const ortbAuctionConfigs = deepAccess(serverResponse, 'body.ext.igi') || []; - if (fledgeAuctionConfigs.length === 0 && ortbAuctionConfigs.length === 0) { - return validBids; - } - if (ortbAuctionConfigs.length > 0) { - fledgeAuctionConfigs.push(...ortbAuctionConfigs.map(({ igs }) => igs || []).flat()); - } - return { - bids: validBids, - paapi: fledgeAuctionConfigs, - } + return validBids; }, getUserSyncs: function(syncOptions, serverResponses) { const cookieSyncUrls = fetchCookieSyncUrls(serverResponses); diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index 500c9f3f12b..1af49e5b6dc 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -181,23 +181,3 @@ var adUnits = [{ ``` - -# Protected Audience API (FLEDGE) - -In order to enable PAAPI auctions follow the instructions below: - -1. Add the paapiForGpt and paapi modules to your prebid bundle. -2. Add the following configuration for the module -``` -pbjs.que.push(function() { - pbjs.setConfig({ - paapi: { - enabled: true, - bidders: ['medianet'], - defaultForSlots: 1 - } - }); -}); -``` - -For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index 367f9046184..f370d220464 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { _each, deepAccess, @@ -25,6 +24,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js' +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/mileBidAdapter.ts b/modules/mileBidAdapter.ts index 7fb1def9a19..28728615051 100644 --- a/modules/mileBidAdapter.ts +++ b/modules/mileBidAdapter.ts @@ -1,8 +1,8 @@ import { type BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { deepAccess, deepSetValue, generateUUID, logInfo, logError } from '../src/utils.js'; -import { getDNT } from '../libraries/dnt/index.js'; import { ajax } from '../src/ajax.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * Mile Bid Adapter diff --git a/modules/msftBidAdapter.js b/modules/msftBidAdapter.js index 8cc396a9417..e9539cce6fc 100644 --- a/modules/msftBidAdapter.js +++ b/modules/msftBidAdapter.js @@ -31,6 +31,7 @@ import { logWarn, mergeDeep } from "../src/utils.js"; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = "msft"; const DEBUG_PARAMS = ['enabled', 'dongle', 'member_id', 'debug_timeout']; @@ -502,12 +503,10 @@ function newRenderer(adUnitCode, rtbBid, rendererOptions = {}) { /** * This function hides google div container for outstream bids to remove unwanted space on page. Appnexus renderer creates a new iframe outside of google iframe to render the outstream creative. - * @param {string} elementId element id */ -function hidedfpContainer(elementId) { +function hidedfpContainer(container) { try { - const el = document - .getElementById(elementId) + const el = container .querySelectorAll("div[id^='google_ads']"); if (el[0]) { el[0].style.setProperty("display", "none"); @@ -517,11 +516,10 @@ function hidedfpContainer(elementId) { } } -function hideSASIframe(elementId) { +function hideSASIframe(container) { try { // find script tag with id 'sas_script'. This ensures it only works if you're using Smart Ad Server. - const el = document - .getElementById(elementId) + const el = container .querySelectorAll("script[id^='sas_script']"); if (el[0]?.nextSibling?.localName === "iframe") { el[0].nextSibling.style.setProperty("display", "none"); @@ -539,8 +537,9 @@ function handleOutstreamRendererEvents(bid, id, eventName) { } function outstreamRender(bid, doc) { - hidedfpContainer(bid.adUnitCode); - hideSASIframe(bid.adUnitCode); + const container = getAdUnitElement(bid); + hidedfpContainer(container); + hideSASIframe(container); // push to render queue because ANOutstreamVideo may not be loaded yet bid.renderer.push(() => { const win = doc?.defaultView || window; diff --git a/modules/nexverseBidAdapter.js b/modules/nexverseBidAdapter.js index a52bc9ffa4c..4e65a98066c 100644 --- a/modules/nexverseBidAdapter.js +++ b/modules/nexverseBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { isArray, generateUUID, getWinDimensions, isNumber } from '../src/utils.js'; @@ -9,6 +8,8 @@ import { getDeviceModel, buildEndpointUrl, isBidRequestValid, parseNativeRespons import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; import { config } from '../src/config.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'nexverse'; const BIDDER_ENDPOINT = 'https://rtb.nexverse.ai'; @@ -191,7 +192,7 @@ function buildOpenRtbRequest(bid, bidderRequest) { const imps = []; // Calculate viewability percentage for the ad unit - const adUnitElement = document.getElementById(bid.adUnitCode); + const adUnitElement = getAdUnitElement(bid); let viewabilityPercentage = 0; if (adUnitElement) { const rect = getBoundingClientRect(adUnitElement); diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index 99efb9198e5..d9f1e8091db 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -1,7 +1,10 @@ import { + isArray, + getWindowTop, deepSetValue, logError, logWarn, + createTrackPixelHtml, getBidIdParameter, getUniqueIdentifierStr, formatQS, @@ -10,9 +13,11 @@ import { import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ajax } from '../src/ajax.js'; +import { percentInView } from '../libraries/percentInView/percentInView.js'; import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js'; -import { getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes } from '../libraries/omsUtils/index.js'; -import { getRoundedViewability } from '../libraries/omsUtils/viewability.js'; +import { getMinSize } from '../libraries/sizeUtils/sizeUtils.js'; +import { getBidFloor, isIframe } from '../libraries/omsUtils/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'oms'; const URL = 'https://rt.marphezis.com/hb'; @@ -35,9 +40,15 @@ export const spec = { function buildRequests(bidReqs, bidderRequest) { try { const impressions = bidReqs.map(bid => { - const bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes || []; - const processedSizes = getProcessedSizes(bidSizes); - const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); + 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 = getAdUnitElement(bid); + 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 = { @@ -91,7 +102,7 @@ function buildRequests(bidReqs, bidderRequest) { } }, device: { - devicetype: getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), + devicetype: _getDeviceType(navigator.userAgent, bidderRequest?.ortb2?.device?.sua), w: screen.width, h: screen.height }, @@ -182,7 +193,7 @@ function interpretResponse(serverResponse) { bidResponse.vastXml = bid.adm; } else { bidResponse.mediaType = BANNER; - bidResponse.ad = getAdMarkup(bid); + bidResponse.ad = _getAdMarkup(bid); } return bidResponse; @@ -234,6 +245,18 @@ function _trackEvent(endpoint, data) { }); } +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; @@ -244,6 +267,22 @@ function _getGpp(bidderRequest) { ); } +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, { w, h }) : 0; +} + function _extractGpidData(bid) { return { gpid: bid?.ortb2Imp?.ext?.gpid, diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index d919d1398b7..3d70c233805 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -9,6 +9,7 @@ import { deepClone, logError, deepAccess, getWinDimensions } from '../src/utils. import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { toOrtbNativeRequest } from '../src/native.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -145,7 +146,7 @@ function buildRequests(validBidRequests, bidderRequest) { const connection = getConnectionInfo(); payload.networkConnectionType = connection?.type || null; payload.networkEffectiveConnectionType = connection?.effectiveType || null; - payload.fledgeEnabled = Boolean(bidderRequest?.paapi?.enabled) + payload.fledgeEnabled = false; return { method: 'POST', url: ENDPOINT, @@ -160,7 +161,7 @@ function interpretResponse(serverResponse, bidderRequest) { if (!body || (body.nobid && body.nobid === true)) { return bids; } - if (!body.fledgeAuctionConfigs && (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0)) { + if (!body.bids || !Array.isArray(body.bids) || body.bids.length === 0) { return bids; } Array.isArray(body.bids) && body.bids.forEach(bid => { @@ -206,15 +207,7 @@ function interpretResponse(serverResponse, bidderRequest) { bids.push(responseBid); }); - if (body.fledgeAuctionConfigs && Array.isArray(body.fledgeAuctionConfigs)) { - const fledgeAuctionConfigs = body.fledgeAuctionConfigs - return { - bids, - paapi: fledgeAuctionConfigs - } - } else { - return bids; - } + return bids; } function createRenderer(bid, rendererOptions = {}) { @@ -366,14 +359,14 @@ function setGeneralInfo(bidRequest) { if (params.dealId) { this['dealId'] = params.dealId; } - const coords = getSpaceCoords(bidRequest.adUnitCode); + const coords = getSpaceCoords(bidRequest); if (coords) { this['coords'] = coords; } } -function getSpaceCoords(id) { - const space = document.getElementById(id); +function getSpaceCoords(bidRequest) { + const space = getAdUnitElement(bidRequest); try { const { top, left, width, height } = getBoundingClientRect(space); let window = space.ownerDocument.defaultView; diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js index ef4d3df777e..1dade3668ea 100644 --- a/modules/onomagicBidAdapter.js +++ b/modules/onomagicBidAdapter.js @@ -1,14 +1,18 @@ import { _each, - getBidIdParameter, + createTrackPixelHtml, getBidIdParameter, getUniqueIdentifierStr, + getWindowTop, + isArray, logError, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { getAdMarkup, getBidFloor, getDeviceType, getProcessedSizes } from '../libraries/omsUtils/index.js'; -import { getRoundedViewability } from '../libraries/omsUtils/viewability.js'; +import { percentInView } from '../libraries/percentInView/percentInView.js'; +import { getMinSize } from '../libraries/sizeUtils/sizeUtils.js'; +import { getBidFloor, isIframe } from '../libraries/omsUtils/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'onomagic'; const URL = 'https://bidder.onomagic.com/hb'; @@ -31,9 +35,17 @@ function buildRequests(bidReqs, bidderRequest) { const onomagicImps = []; const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); _each(bidReqs, function (bid) { - const bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; - const processedSizes = getProcessedSizes(bidSizes); - const viewabilityAmountRounded = getRoundedViewability(bid.adUnitCode, processedSizes); + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && 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 = getAdUnitElement(bid); + 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, @@ -63,7 +75,7 @@ function buildRequests(bidReqs, bidderRequest) { } }, device: { - devicetype: getDeviceType(), + devicetype: _getDeviceType(), w: screen.width, h: screen.height }, @@ -116,7 +128,7 @@ function interpretResponse(serverResponse) { currency: 'USD', netRevenue: true, mediaType: BANNER, - ad: getAdMarkup(onomagicBid), + ad: _getAdMarkup(onomagicBid), ttl: 60, meta: { advertiserDomains: onomagicBid && onomagicBid.adomain ? onomagicBid.adomain : [] @@ -135,4 +147,34 @@ function getUserSyncs(syncOptions, responses, gdprConsent) { return []; } +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} + +function _getAdMarkup(bid) { + let adm = bid.adm; + if ('nurl' in bid) { + adm += createTrackPixelHtml(bid.nurl); + } + return adm; +} + +function _isViewabilityMeasurable(element) { + return !isIframe() && element !== null; +} + +function _getViewability(element, topWin, { w, h } = {}) { + return getWindowTop().document.visibilityState === 'visible' + ? percentInView(element, { w, h }) + : 0; +} + registerBidder(spec); diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index 2e295cc8669..9627223b221 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -61,9 +61,7 @@ const converter = ortbConverter({ if (bid.params.coppa) { utils.deepSetValue(req, 'regs.coppa', 1); } - if (bid.params.doNotTrack) { - utils.deepSetValue(req, 'device.dnt', 1); - } + utils.deepSetValue(req, 'device.dnt', 0); if (bid.params.platform) { utils.deepSetValue(req, 'ext.platform', bid.params.platform); } @@ -98,27 +96,7 @@ const converter = ortbConverter({ utils.deepSetValue(ortbResponse, 'ext.platform', ortbRequest.ext.platform); } } - const response = buildResponse(bidResponses, ortbResponse, context); - // TODO: we may want to standardize this and move fledge logic to ortbConverter - let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return { - bidId, - config: mergeDeep(Object.assign({}, cfg), { - auctionSignals: { - ortb2Imp: context.impContext[bidId]?.imp, - }, - }), - } - }); - return { - bids: response.bids, - paapi: fledgeAuctionConfigs, - } - } else { - return response - } + return buildResponse(bidResponses, ortbResponse, context); }, overrides: { imp: { diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 33a86fc7330..4a8e22aaa6a 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, @@ -17,6 +16,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { Renderer } from '../src/Renderer.js'; import { OUTSTREAM } from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/optableBidAdapter.js b/modules/optableBidAdapter.js deleted file mode 100644 index d2dae252e6c..00000000000 --- a/modules/optableBidAdapter.js +++ /dev/null @@ -1,67 +0,0 @@ -import * as utils from '../src/utils.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { ortbConverter } from '../libraries/ortbConverter/converter.js' -const converter = ortbConverter({ - context: { netRevenue: true, ttl: 300 }, - imp(buildImp, bidRequest, context) { - const imp = buildImp(bidRequest, context); - utils.mergeDeep(imp, { - tagid: bidRequest.params.site, - }); - return imp; - } -}); -const BIDDER_CODE = 'optable'; -const DEFAULT_REGION = 'ca' -const DEFAULT_ORIGIN = 'https://ads.optable.co' - -function getOrigin() { - return config.getConfig('optable.origin') ?? DEFAULT_ORIGIN; -} - -function getBaseUrl() { - const region = config.getConfig('optable.region') ?? DEFAULT_REGION; - return `${getOrigin()}/${region}` -} - -export const spec = { - code: BIDDER_CODE, - isBidRequestValid: function(bid) { return !!bid.params?.site }, - buildRequests: function(bidRequests, bidderRequest) { - const requestURL = `${getBaseUrl()}/ortb2/v1/ssp/bid` - const data = converter.toORTB({ bidRequests, bidderRequest, context: { mediaType: BANNER } }); - return { method: 'POST', url: requestURL, data } - }, - buildPAAPIConfigs: function(bidRequests) { - const origin = getOrigin(); - return bidRequests - .filter(req => req.ortb2Imp?.ext?.ae) - .map(bid => ({ - bidId: bid.bidId, - config: { - seller: origin, - decisionLogicURL: `${getBaseUrl()}/paapi/v1/ssp/decision-logic.js?origin=${bid.params.site}`, - interestGroupBuyers: [origin], - perBuyerMultiBidLimits: { - [origin]: 100 - }, - perBuyerCurrencies: { - [origin]: 'USD' - } - } - })) - }, - interpretResponse: function(response, request) { - const bids = converter.fromORTB({ response: response.body, request: request.data }).bids - const auctionConfigs = (response.body.ext?.optable?.fledge?.auctionconfigs ?? []).map((cfg) => { - const { impid, ...config } = cfg; - return { bidId: impid, config } - }) - - return { bids, paapi: auctionConfigs } - }, - supportedMediaTypes: [BANNER] -} -registerBidder(spec); diff --git a/modules/optableBidAdapter.md b/modules/optableBidAdapter.md deleted file mode 100644 index a7c4829fe63..00000000000 --- a/modules/optableBidAdapter.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -``` -Module Name: Optable Bidder Adapter -Module Type: Bidder Adapter -Maintainer: prebid@optable.co -``` - -# Description - -Module that connects to Optable's demand sources. - -# Bid Parameters -## Banner - -| Name | Scope | Type | Description | Example -| ---- | ----- | ---- | ----------- | ------- -| `site` | required | String | Optable site ID provided by your Optable representative. | "aaaaaaaa" - -## Video - -Not supported at the moment. - -# Example -```javascript -var adUnits = [ - { - code: 'test-div', - sizes: [[728, 90]], // a display size - mediaTypes: {'banner': {}}, - bids: [ - { - bidder: 'optable', - params: { - site: 'aaaaaaaa', - }, - }, - ], - }, -]; -``` diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 1d7aff59ee2..ee631309610 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -1,13 +1,15 @@ import { - logInfo, - logError, deepAccess, - logWarn, + deepClone, deepSetValue, + generateUUID, + getBidIdParameter, isArray, + logError, + logInfo, + logWarn, mergeDeep, - parseUrl, - generateUUID, isInteger, deepClone, getBidIdParameter + parseUrl } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -16,6 +18,7 @@ import { getPriceBucketString } from '../src/cpmBucketManager.js'; import { Renderer } from '../src/Renderer.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { toOrtb25 } from '../libraries/ortb2.5Translator/translator.js'; + const BIDDER_CODE = 'ozone'; const ORIGIN = 'https://elb.the-ozone-project.com'; const AUCTIONURI = '/openrtb2/auction'; @@ -137,7 +140,6 @@ export const spec = { if (this.blockTheRequest()) { return []; } - const fledgeEnabled = !!bidderRequest.fledgeEnabled; let htmlParams = { 'publisherId': '', 'siteId': '' }; if (validBidRequests.length > 0) { Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); @@ -274,14 +276,6 @@ export const spec = { if (auctionId) { obj.ext.auctionId = auctionId; } - if (fledgeEnabled) { - const auctionEnvironment = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.ae'); - if (isInteger(auctionEnvironment)) { - deepSetValue(obj, 'ext.ae', auctionEnvironment); - } else { - logError(`ignoring ortb2Imp.ext.ae - not an integer for obj.id=${obj.id}`); - } - } return obj; }); const extObj = {}; @@ -572,20 +566,6 @@ export const spec = { } } let ret = arrAllBids; - let fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.igi') || []; - if (isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { - fledgeAuctionConfigs = fledgeAuctionConfigs.filter(cfg => { - if (typeof cfg !== 'object' || cfg === null) { - logWarn('Removing malformed fledge auction config:', cfg); - return false; - } - return true; - }); - ret = { - bids: arrAllBids, - fledgeAuctionConfigs, - }; - } const endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); logInfo('will return: ', deepClone(ret)); diff --git a/modules/paapi.js b/modules/paapi.js deleted file mode 100644 index e7e00b3f4e8..00000000000 --- a/modules/paapi.js +++ /dev/null @@ -1,808 +0,0 @@ -/** - * Collect PAAPI component auction configs from bid adapters and make them available through `pbjs.getPAAPIConfig()` - */ -import { config } from '../src/config.js'; -import { getHook, hook, module } from '../src/hook.js'; -import { - deepAccess, - deepEqual, - deepSetValue, - logError, - logInfo, - logWarn, - mergeDeep, - sizesToSizeTuples -} 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'; -import { currencyCompare } from '../libraries/currencyUtils/currency.js'; -import { keyCompare, maximum, minimum } from '../src/utils/reducers.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { auctionStore } from '../libraries/weakStore/weakStore.js'; -import { adapterMetrics, guardTids } from '../src/adapters/bidderFactory.js'; -import { defer, PbPromise } from '../src/utils/promise.js'; -import { auctionManager } from '../src/auctionManager.js'; - -const MODULE = 'PAAPI'; - -const submodules = []; -const USED = new WeakSet(); - -export function registerSubmodule(submod) { - submodules.push(submod); - submod.init && submod.init({ - getPAAPIConfig, - expandFilters - }); -} - -module('paapi', registerSubmodule); - -/* auction configs as returned by getPAAPIConfigs */ -const configsForAuction = auctionStore(); - -/* auction configs returned by adapters, but waiting for end-of-auction signals before they're added to configsForAuction */ -const pendingConfigsForAuction = auctionStore(); - -/* igb returned by adapters, waiting for end-of-auction signals before they're merged into configForAuctions */ -const pendingBuyersForAuction = auctionStore(); - -/* for auction configs that were generated in parallel with auctions (and contain promises), their resolve/reject methods */ -const deferredConfigsForAuction = auctionStore(); - -let latestAuctionForAdUnit = {}; -let moduleConfig = {}; - -config.getConfig('paapi', config => { - init(config.paapi); -}); - -export function reset() { - submodules.splice(0, submodules.length); - latestAuctionForAdUnit = {}; -} - -export function init(cfg) { - if (cfg && cfg.enabled === true) { - if (!moduleConfig.enabled) { - attachHandlers(); - } - moduleConfig = cfg; - logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); - } else { - if (moduleConfig.enabled) { - detachHandlers(); - } - moduleConfig = {}; - logInfo(`${MODULE} disabled`, cfg); - } -} - -function attachHandlers() { - getHook('addPaapiConfig').before(addPaapiConfigHook); - getHook('makeBidRequests').before(addPaapiData); - getHook('makeBidRequests').after(markForFledge); - getHook('processBidderRequests').before(parallelPaapiProcessing, 9); - // resolve params before parallel processing - getHook('processBidderRequests').before(buildPAAPIParams, 10); - getHook('processBidderRequests').before(adAuctionHeadersHook); - events.on(EVENTS.AUCTION_INIT, onAuctionInit); - events.on(EVENTS.AUCTION_END, onAuctionEnd); -} - -function detachHandlers() { - getHook('addPaapiConfig').getHooks({ hook: addPaapiConfigHook }).remove(); - getHook('makeBidRequests').getHooks({ hook: addPaapiData }).remove(); - getHook('makeBidRequests').getHooks({ hook: markForFledge }).remove(); - getHook('processBidderRequests').getHooks({ hook: parallelPaapiProcessing }).remove(); - getHook('processBidderRequests').getHooks({ hook: buildPAAPIParams }).remove(); - getHook('processBidderRequests').getHooks({ hook: adAuctionHeadersHook }).remove(); - events.off(EVENTS.AUCTION_INIT, onAuctionInit); - events.off(EVENTS.AUCTION_END, onAuctionEnd); -} - -export function adAuctionHeadersHook(next, spec, bids, bidderRequest, ajax, ...args) { - if (bidderRequest.paapi?.enabled) { - ajax = ((orig) => { - return function (url, callback, data, options) { - options = options ?? {}; - options.adAuctionHeaders = options.adAuctionHeaders ?? true; - return orig.call(this, url, callback, data, options); - } - })(ajax); - } - return next.call(this, spec, bids, bidderRequest, ajax, ...args); -} - -function getStaticSignals(adUnit = {}) { - const cfg = {}; - const requestedSize = getRequestedSize(adUnit); - if (requestedSize) { - cfg.requestedSize = requestedSize; - } - return cfg; -} - -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; -} - -export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config = moduleConfig?.componentSeller ?? {}, partitioners = { - compact: (igbRequests) => partitionBuyers(igbRequests.map(req => req[1])).map(part => [{}, part]), - expand: partitionBuyersByBidder -}) { - if (!config.auctionConfig) { - logWarn(MODULE, 'Cannot use IG buyers: paapi.componentSeller.auctionConfig not set', igbRequests.map(req => req[1])); - return []; - } - const partition = partitioners[config.separateAuctions ? 'expand' : 'compact']; - return partition(igbRequests) - .map(([request, igbs]) => { - const auctionConfig = mergeDeep(merge(igbs), config.auctionConfig); - auctionConfig.auctionSignals = setFPD(auctionConfig.auctionSignals || {}, request); - return [request, auctionConfig]; - }); -} - -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 = configsForAuction(auctionId); - (adUnitCodes || []).forEach(au => { - if (!paapiConfigs.hasOwnProperty(au)) { - paapiConfigs[au] = null; - } - !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); - }); - - const pendingConfigs = pendingConfigsForAuction(auctionId); - const pendingBuyers = pendingBuyersForAuction(auctionId); - - if (pendingConfigs && pendingBuyers) { - Object.entries(pendingBuyers).forEach(([adUnitCode, igbRequests]) => { - buyersToAuctionConfigs(igbRequests).forEach(([{ bidder }, auctionConfig]) => append(pendingConfigs, adUnitCode, { id: getComponentSellerConfigId(bidder), config: auctionConfig })) - }) - } - - const deferredConfigs = deferredConfigsForAuction(auctionId); - - const adUnitsWithConfigs = Array.from(new Set(Object.keys(pendingConfigs).concat(Object.keys(deferredConfigs)))); - const signals = Object.fromEntries( - adUnitsWithConfigs.map(adUnitCode => { - latestAuctionForAdUnit[adUnitCode] = auctionId; - const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - return [adUnitCode, { - ...getStaticSignals(adUnitsByCode[adUnitCode]), - ...getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)) - }] - }) - ) - - const configsById = {}; - Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { - auctionConfigs.forEach(({ id, config }) => append(configsById, id, { - adUnitCode, - config: mergeDeep({}, signals[adUnitCode], config) - })); - }); - - function resolveSignals(signals, deferrals) { - Object.entries(deferrals).forEach(([signal, { resolve, default: defaultValue }]) => { - let value = signals.hasOwnProperty(signal) ? signals[signal] : null; - if (value == null && defaultValue == null) { - value = undefined; - } else if (typeof defaultValue === 'object' && typeof value === 'object') { - value = mergeDeep({}, defaultValue, value); - } else { - value = value ?? defaultValue - } - resolve(value); - }) - } - - Object.entries(deferredConfigs).forEach(([adUnitCode, { top, components }]) => { - resolveSignals(signals[adUnitCode], top); - Object.entries(components).forEach(([configId, { deferrals }]) => { - const matchingConfigs = configsById.hasOwnProperty(configId) ? configsById[configId] : []; - if (matchingConfigs.length > 1) { - logWarn(`Received multiple PAAPI configs for the same bidder and seller (${configId}), active PAAPI auctions will only see the first`); - } - const { config } = matchingConfigs.shift() ?? { config: { ...signals[adUnitCode] } } - resolveSignals(config, deferrals); - }) - }); - - const newConfigs = Object.values(configsById).flatMap(configs => configs); - const hasDeferredConfigs = Object.keys(deferredConfigs).length > 0; - - if (moduleConfig.parallel && hasDeferredConfigs && newConfigs.length > 0) { - logError(`Received PAAPI configs after PAAPI auctions were already started in parallel with their contextual auction`, newConfigs) - } - - newConfigs.forEach(({ adUnitCode, config }) => { - if (paapiConfigs[adUnitCode] == null) { - paapiConfigs[adUnitCode] = { - ...signals[adUnitCode], - componentAuctions: [] - } - } - paapiConfigs[adUnitCode].componentAuctions.push(mergeDeep({}, signals[adUnitCode], config)); - }); - - if (!moduleConfig.parallel || !hasDeferredConfigs) { - submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, paapiConfigs)); - } -} - -function append(target, key, value) { - !target.hasOwnProperty(key) && (target[key] = []); - target[key].push(value); -} - -function setFPD(target, { ortb2, ortb2Imp }) { - ortb2 != null && deepSetValue(target, 'prebid.ortb2', mergeDeep({}, ortb2, target.prebid?.ortb2)); - ortb2Imp != null && deepSetValue(target, 'prebid.ortb2Imp', mergeDeep({}, ortb2Imp, target.prebid?.ortb2Imp)); - return target; -} - -function getConfigId(bidderCode, seller) { - return `${bidderCode}::${seller}`; -} - -function getComponentSellerConfigId(bidderCode) { - return moduleConfig.componentSeller.separateAuctions ? `igb::${bidderCode}` : 'igb'; -} - -export function addPaapiConfigHook(next, request, paapiConfig) { - if (getFledgeConfig(config.getCurrentBidder()).enabled) { - const { adUnitCode, auctionId, bidder } = request; - - function storePendingData(store, data) { - const target = store(auctionId); - if (target != null) { - append(target, adUnitCode, data) - } else { - logWarn(MODULE, `Received PAAPI config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, data); - } - } - - const { config, igb } = paapiConfig; - if (config) { - config.auctionSignals = setFPD(config.auctionSignals || {}, request); - const pbs = config.perBuyerSignals = config.perBuyerSignals ?? {}; - (config.interestGroupBuyers || []).forEach(buyer => { - pbs[buyer] = setFPD(pbs[buyer] ?? {}, request); - }) - storePendingData(pendingConfigsForAuction, { id: getConfigId(bidder, config.seller), config }); - } - if (igb && checkOrigin(igb)) { - igb.pbs = setFPD(igb.pbs || {}, request); - storePendingData(pendingBuyersForAuction, [request, igb]) - } - } - next(request, paapiConfig); -} - -export const IGB_TO_CONFIG = { - cur: 'perBuyerCurrencies', - pbs: 'perBuyerSignals', - ps: 'perBuyerPrioritySignals', - maxbid: 'auctionSignals.prebid.perBuyerMaxbid', -} - -function checkOrigin(igb) { - if (igb.origin) return true; - logWarn('PAAPI buyer does not specify origin and will be ignored', igb); -} - -/** - * Convert a list of InterestGroupBuyer (igb) objects into a partial auction config. - * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md - */ -export function mergeBuyers(igbs) { - const buyers = new Set(); - return Object.assign( - igbs.reduce((config, igb) => { - if (checkOrigin(igb)) { - if (!buyers.has(igb.origin)) { - buyers.add(igb.origin); - Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { - if (igb[igbField] != null) { - const entry = deepAccess(config, configField) || {} - entry[igb.origin] = igb[igbField]; - deepSetValue(config, configField, entry); - } - }); - } else { - logWarn(MODULE, `Duplicate buyer: ${igb.origin}. All but the first will be ignored`, igbs); - } - } - return config; - }, {}), - { - interestGroupBuyers: Array.from(buyers.keys()) - } - ); -} - -/** - * Partition a list of InterestGroupBuyer (igb) object into sets that can each be merged into a single auction. - * If the same buyer (origin) appears more than once, it will be split across different partition unless the igb objects - * are identical. - */ -export function partitionBuyers(igbs) { - return igbs.reduce((partitions, igb) => { - if (checkOrigin(igb)) { - let partition = partitions.find(part => !part.hasOwnProperty(igb.origin) || deepEqual(part[igb.origin], igb)); - if (!partition) { - partition = {}; - partitions.push(partition); - } - partition[igb.origin] = igb; - } - return partitions; - }, []).map(part => Object.values(part)); -} - -export function partitionBuyersByBidder(igbRequests) { - const requests = {}; - const igbs = {}; - igbRequests.forEach(([request, igb]) => { - !requests.hasOwnProperty(request.bidder) && (requests[request.bidder] = request); - append(igbs, request.bidder, igb); - }) - return Object.entries(igbs).map(([bidder, igbs]) => [requests[bidder], igbs]) -} - -/** - * Expand PAAPI api filters into a map from ad unit code to auctionId. - * - * auctionId when specified, the result will have this as the value for each entry. - * when not specified, each ad unit will map to the latest auction that involved that ad unit. - * adUnitCode when specified, the result will contain only one entry (for this ad unit) or be empty (if this ad - * unit was never involved in an auction). - * when not specified, the result will contain an entry for every ad unit that was involved in any auction. - * @return {{[adUnitCode: string]: string}} - */ -function expandFilters({ auctionId, adUnitCode } = {}) { - let adUnitCodes = []; - if (adUnitCode == null) { - adUnitCodes = Object.keys(latestAuctionForAdUnit); - } else if (latestAuctionForAdUnit.hasOwnProperty(adUnitCode)) { - adUnitCodes = [adUnitCode]; - } - return Object.fromEntries( - adUnitCodes.map(au => [au, auctionId ?? latestAuctionForAdUnit[au]]) - ); -} - -/** - * Get PAAPI auction configuration. - * - * @param {Object} [filters] - Filters object - * @param {string} [filters.auctionId] optional auction filter; if omitted, the latest auction for each ad unit is used - * @param {string} [filters.adUnitCode] optional ad unit filter - * @param {boolean} [includeBlanks=false] if true, include null entries for ad units that match the given filters but do not have any available auction configs. - * @returns {Object} a map from ad unit code to auction config for the ad unit. - */ -export function getPAAPIConfig(filters = {}, includeBlanks = false) { - const output = {}; - Object.entries(expandFilters(filters)).forEach(([au, auctionId]) => { - const auctionConfigs = configsForAuction(auctionId); - if (auctionConfigs?.hasOwnProperty(au)) { - // ad unit was involved in a PAAPI auction - const candidate = auctionConfigs[au]; - if (candidate && !USED.has(candidate)) { - output[au] = candidate; - USED.add(candidate); - } else if (includeBlanks) { - output[au] = null; - } - } else if (auctionId == null && includeBlanks) { - // ad unit was involved in a non-PAAPI auction - output[au] = null; - } - }); - return output; -} - -getGlobal().getPAAPIConfig = (filters) => getPAAPIConfig(filters); - -function isFledgeSupported() { - return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator; -} - -function getFledgeConfig(bidder) { - const enabled = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); - return { - enabled, - ae: enabled ? moduleConfig.defaultForSlots : undefined - }; -} - -/** - * Given an array of size tuples, return the one that should be used for PAAPI. - */ -export const getPAAPISize = hook('sync', function (sizes) { - sizes = sizes - ?.filter(([w, h]) => !(w === h && w <= 5)); - - if (sizes?.length) { - return sizes - .reduce(maximum(keyCompare(([w, h]) => w * h))); - } -}, 'getPAAPISize'); - -function getRequestedSize(adUnit) { - return adUnit.ortb2Imp?.ext?.paapi?.requestedSize || (() => { - const size = getPAAPISize(sizesToSizeTuples(adUnit.mediaTypes?.banner?.sizes)); - if (size) { - return { - width: size[0], - height: size[1] - }; - } - })(); -} - -export function addPaapiData(next, adUnits, ...args) { - if (isFledgeSupported() && moduleConfig.enabled) { - adUnits.forEach(adUnit => { - // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md - const igsAe = adUnit.ortb2Imp?.ext?.igs != null - ? adUnit.ortb2Imp.ext.igs.ae || 1 - : null; - const extAe = adUnit.ortb2Imp?.ext?.ae; - if (igsAe !== extAe && igsAe != null && extAe != null) { - logWarn(MODULE, `Ad unit defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, adUnit); - } - const ae = igsAe ?? extAe ?? moduleConfig.defaultForSlots; - if (ae) { - deepSetValue(adUnit, 'ortb2Imp.ext.ae', ae); - adUnit.ortb2Imp.ext.igs = Object.assign({ - ae: ae, - biddable: 1 - }, adUnit.ortb2Imp.ext.igs); - const requestedSize = getRequestedSize(adUnit); - if (requestedSize) { - deepSetValue(adUnit, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); - } - adUnit.bids.forEach(bidReq => { - if (!getFledgeConfig(bidReq.bidder).enabled) { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', 0); - bidReq.ortb2Imp.ext.igs = { ae: 0, biddable: 0 }; - } - }) - } - }) - } - next(adUnits, ...args); -} - -export const NAVIGATOR_APIS = ['createAuctionNonce', 'getInterestGroupAdAuctionData']; - -export function markForFledge(next, bidderRequests) { - if (isFledgeSupported()) { - bidderRequests.forEach((bidderReq) => { - const { enabled } = getFledgeConfig(bidderReq.bidderCode); - Object.assign(bidderReq, { - paapi: { - enabled, - componentSeller: !!moduleConfig.componentSeller?.auctionConfig - } - }); - if (enabled) { - NAVIGATOR_APIS.forEach(method => { - bidderReq.paapi[method] = (...args) => new AsyncPAAPIParam(() => navigator[method](...args)) - }) - } - }); - } - next(bidderRequests); -} - -export const ASYNC_SIGNALS = [ - 'auctionSignals', - 'sellerSignals', - 'perBuyerSignals', - 'perBuyerTimeouts', - 'directFromSellerSignals', - 'perBuyerCurrencies', - 'perBuyerCumulativeTimeouts', - 'serverResponse' -]; - -const validatePartialConfig = (() => { - const REQUIRED_SYNC_SIGNALS = [ - { - props: ['seller'], - validate: (val) => typeof val === 'string' - }, - { - props: ['interestGroupBuyers'], - validate: (val) => Array.isArray(val) && val.length > 0 - }, - { - props: ['decisionLogicURL', 'decisionLogicUrl'], - validate: (val) => typeof val === 'string' - } - ]; - - return function (config) { - const invalid = REQUIRED_SYNC_SIGNALS.find(({ props, validate }) => props.every(prop => !config.hasOwnProperty(prop) || !config[prop] || !validate(config[prop]))); - if (invalid) { - logError(`Partial PAAPI config has missing or invalid property "${invalid.props[0]}"`, config) - return false; - } - return true; - } -})() - -function callAdapterApi(spec, method, bids, bidderRequest) { - const metrics = adapterMetrics(bidderRequest); - const tidGuard = guardTids(bidderRequest); - let result; - metrics.measureTime(method, () => { - try { - result = spec[method](bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest)) - } catch (e) { - logError(`Error invoking "${method}":`, e); - } - }); - return result; -} - -/** - * Adapters can provide a `spec.buildPAAPIConfigs(validBidRequests, bidderRequest)` to be included in PAAPI auctions - * that can be started in parallel with contextual auctions. - * - * If PAAPI is enabled, and an adapter provides `buildPAAPIConfigs`, it is invoked just before `buildRequests`, - * and takes the same arguments. It should return an array of PAAPI configuration objects with the same format - * as in `interpretResponse` (`{bidId, config?, igb?}`). - * - * Everything returned by `buildPAAPIConfigs` is treated in the same way as if it was returned by `interpretResponse` - - * except for signals that can be provided asynchronously (cfr. `ASYNC_SIGNALS`), which are replaced by promises. - * When the (contextual) auction ends, the promises are resolved. - * - * If during the auction the adapter's `interpretResponse` returned matching configurations (same `bidId`, - * and a `config` with the same `seller`, or an `igb` with the same `origin`), the promises resolve to their contents. - * Otherwise, they resolve to the values provided by `buildPAAPIConfigs`, or an empty object if no value was provided. - * - * Promisified auction configs are available from `getPAAPIConfig` immediately after `requestBids`. - * If the `paapi.parallel` config flag is set, PAAPI submodules are also triggered at the same time - * (instead of when the auction ends). - */ -export function parallelPaapiProcessing(next, spec, bids, bidderRequest, ...args) { - function makeDeferrals(defaults = {}) { - const promises = {}; - const deferrals = Object.fromEntries(ASYNC_SIGNALS.map(signal => { - const def = defer({ promiseFactory: (resolver) => new Promise(resolver) }); - def.default = defaults.hasOwnProperty(signal) ? defaults[signal] : null; - promises[signal] = def.promise; - return [signal, def] - })) - return [deferrals, promises]; - } - - const { auctionId, paapi: { enabled, componentSeller } = {} } = bidderRequest; - const auctionConfigs = configsForAuction(auctionId); - bids.map(bid => bid.adUnitCode).forEach(adUnitCode => { - latestAuctionForAdUnit[adUnitCode] = auctionId; - if (!auctionConfigs.hasOwnProperty(adUnitCode)) { - auctionConfigs[adUnitCode] = null; - } - }); - - if (enabled && spec.buildPAAPIConfigs) { - const partialConfigs = callAdapterApi(spec, 'buildPAAPIConfigs', bids, bidderRequest) - const requestsById = Object.fromEntries(bids.map(bid => [bid.bidId, bid])); - (partialConfigs ?? []).forEach(({ bidId, config, igb }) => { - const bidRequest = requestsById.hasOwnProperty(bidId) && requestsById[bidId]; - if (!bidRequest) { - logError(`Received partial PAAPI config for unknown bidId`, { bidId, config }); - } else { - const adUnitCode = bidRequest.adUnitCode; - latestAuctionForAdUnit[adUnitCode] = auctionId; - const deferredConfigs = deferredConfigsForAuction(auctionId); - - const getDeferredConfig = () => { - if (!deferredConfigs.hasOwnProperty(adUnitCode)) { - const [deferrals, promises] = makeDeferrals(); - auctionConfigs[adUnitCode] = { - ...getStaticSignals(auctionManager.index.getAdUnit(bidRequest)), - ...promises, - componentAuctions: [] - } - deferredConfigs[adUnitCode] = { - top: deferrals, - components: {}, - auctionConfig: auctionConfigs[adUnitCode] - } - } - return deferredConfigs[adUnitCode]; - } - - if (config && validatePartialConfig(config)) { - const configId = getConfigId(bidRequest.bidder, config.seller); - const deferredConfig = getDeferredConfig(); - if (deferredConfig.components.hasOwnProperty(configId)) { - logWarn(`Received multiple PAAPI configs for the same bidder and seller; config will be ignored`, { - config, - bidder: bidRequest.bidder - }) - } else { - const [deferrals, promises] = makeDeferrals(config); - const auctionConfig = { - ...getStaticSignals(bidRequest), - ...config, - ...promises - } - deferredConfig.auctionConfig.componentAuctions.push(auctionConfig) - deferredConfig.components[configId] = { auctionConfig, deferrals }; - } - } - if (componentSeller && igb && checkOrigin(igb)) { - const configId = getComponentSellerConfigId(spec.code); - const deferredConfig = getDeferredConfig(); - const partialConfig = buyersToAuctionConfigs([[bidRequest, igb]])[0][1]; - if (deferredConfig.components.hasOwnProperty(configId)) { - const { auctionConfig, deferrals } = deferredConfig.components[configId]; - if (!auctionConfig.interestGroupBuyers.includes(igb.origin)) { - const immediate = {}; - Object.entries(partialConfig).forEach(([key, value]) => { - if (deferrals.hasOwnProperty(key)) { - mergeDeep(deferrals[key], { default: value }); - } else { - immediate[key] = value; - } - }) - mergeDeep(auctionConfig, immediate); - } else { - logWarn(`Received the same PAAPI buyer multiple times for the same PAAPI auction. Consider setting paapi.componentSeller.separateAuctions: true`, igb) - } - } else { - const [deferrals, promises] = makeDeferrals(partialConfig); - const auctionConfig = { - ...partialConfig, - ...getStaticSignals(bidRequest), - ...promises, - } - deferredConfig.components[configId] = { auctionConfig, deferrals }; - deferredConfig.auctionConfig.componentAuctions.push(auctionConfig); - } - } - } - }) - } - return next.call(this, spec, bids, bidderRequest, ...args); -} - -export class AsyncPAAPIParam { - constructor(resolve) { - this.resolve = resolve; - } -} - -export function buildPAAPIParams(next, spec, bids, bidderRequest, ...args) { - if (bidderRequest.paapi?.enabled && spec.paapiParameters) { - const params = callAdapterApi(spec, 'paapiParameters', bids, bidderRequest); - return PbPromise.all( - Object.entries(params ?? {}).map(([key, value]) => - value instanceof AsyncPAAPIParam - ? value.resolve().then(result => [key, result]) - : Promise.resolve([key, value])) - ).then(resolved => { - bidderRequest.paapi.params = Object.fromEntries(resolved); - }).catch(err => { - logError(`Could not resolve PAAPI parameters`, err); - }).then(() => { - next.call(this, spec, bids, bidderRequest, ...args); - }) - } else { - next.call(this, spec, bids, bidderRequest, ...args); - } -} - -export function onAuctionInit({ auctionId }) { - if (moduleConfig.parallel) { - auctionManager.index.getAuction({ auctionId }).requestsDone.then(() => { - if (Object.keys(deferredConfigsForAuction(auctionId)).length > 0) { - submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, configsForAuction(auctionId))); - } - }) - } -} - -export function setImpExtAe(imp, bidRequest, context) { - if (!context.bidderRequest.paapi?.enabled) { - delete imp.ext?.ae; - delete imp.ext?.igs; - } -} - -registerOrtbProcessor({ type: IMP, name: 'impExtAe', fn: setImpExtAe }); - -export function parseExtIgi(response, ortbResponse, context) { - paapiResponseParser( - (ortbResponse.ext?.igi || []).flatMap(igi => { - return (igi?.igs || []).map(igs => { - if (igs.impid !== igi.impid && igs.impid != null && igi.impid != null) { - logWarn(MODULE, 'ORTB response ext.igi.igs.impid conflicts with parent\'s impid', igi); - } - return { - config: igs.config, - impid: igs.impid ?? igi.impid - } - }).concat((igi?.igb || []).map(igb => ({ - igb, - impid: igi.impid - }))) - }), - response, - context - ) -} - -function paapiResponseParser(configs, response, context) { - configs.forEach((config) => { - const impCtx = context.impContext[config.impid]; - if (!impCtx?.imp?.ext?.ae) { - logWarn(MODULE, 'Received auction configuration for an impression that was not in the request or did not ask for it', config, impCtx?.imp); - } else { - impCtx.paapiConfigs = impCtx.paapiConfigs || []; - impCtx.paapiConfigs.push(config); - } - }); -} - -// 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) { - paapiResponseParser( - (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []), - response, - context - ) -} - -registerOrtbProcessor({ type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS] }); -registerOrtbProcessor({ type: RESPONSE, name: 'extIgiIgs', fn: parseExtIgi }); - -// ...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 setResponsePaapiConfigs(response, ortbResponse, context) { - const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => ({ - bidId: impCtx.bidRequest.bidId, - ...cfg - }))); - if (configs.length > 0) { - response.paapi = configs; - } -} - -registerOrtbProcessor({ - type: RESPONSE, - name: 'paapiConfigs', - priority: -1, - fn: setResponsePaapiConfigs, -}); diff --git a/modules/paapiForGpt.js b/modules/paapiForGpt.js deleted file mode 100644 index a7f1dc609f4..00000000000 --- a/modules/paapiForGpt.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * GPT-specific slot configuration logic for PAAPI. - */ -import { getHook, submodule } from '../src/hook.js'; -import { deepAccess, logInfo, logWarn, sizeTupleToSizeString } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { getGlobal } from '../src/prebidGlobal.js'; - -import { keyCompare } from '../src/utils/reducers.js'; -import { getGPTSlotsForAdUnits, targeting } from '../src/targeting.js'; - -const MODULE = 'paapiForGpt'; - -let getPAAPIConfig; - -config.getConfig('paapi', (cfg) => { - if (deepAccess(cfg, 'paapi.gpt.configWithTargeting', true)) { - logInfo(MODULE, 'enabling PAAPI configuration with setTargetingForGPTAsync') - targeting.setTargetingForGPT.before(setTargetingHook); - } else { - targeting.setTargetingForGPT.getHooks({ hook: setTargetingHook }).remove(); - } -}); - -export function setTargetingHookFactory(setPaapiConfig = getGlobal().setPAAPIConfigForGPT) { - return function(next, adUnit, customSlotMatching) { - const adUnitCodes = Array.isArray(adUnit) ? adUnit : [adUnit] - adUnitCodes - .map(adUnitCode => adUnitCode == null ? undefined : { adUnitCode }) - .forEach(filters => setPaapiConfig(filters, customSlotMatching)) - next(adUnit, customSlotMatching); - } -} - -export function slotConfigurator() { - const PREVIOUSLY_SET = {}; - return function setComponentAuction(adUnitCode, gptSlots, auctionConfigs, reset = true) { - if (gptSlots.length > 0) { - 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) { - gptSlots.forEach(gptSlot => { - gptSlot.setConfig({ componentAuction }); - logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); - // reference https://developers.google.com/publisher-tag/reference#googletag.config.ComponentAuctionConfig - }); - } - } else if (auctionConfigs.length > 0) { - logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); - } - }; -} - -const setComponentAuction = slotConfigurator(); - -export const getPAAPISizeHook = (() => { - /* - https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq - https://support.google.com/admanager/answer/1100453?hl=en - - Ignore any placeholder sizes, where placeholder is defined as a square creative with a side of <= 5 pixels - Look if there are any sizes that are part of the set of supported ad sizes defined here. If there are, choose the largest supported size by area (width * height) - For clarity, the set of supported ad sizes includes all of the ad sizes listed under “Top-performing ad sizes”, “Other supported ad sizes”, and “Regional ad sizes”. - If not, choose the largest remaining size (i.e. that isn’t in the list of supported ad sizes) by area (width * height) - */ - const SUPPORTED_SIZES = [ - [728, 90], - [336, 280], - [300, 250], - [300, 50], - [160, 600], - [1024, 768], - [970, 250], - [970, 90], - [768, 1024], - [480, 320], - [468, 60], - [320, 480], - [320, 100], - [320, 50], - [300, 600], - [300, 100], - [250, 250], - [234, 60], - [200, 200], - [180, 150], - [125, 125], - [120, 600], - [120, 240], - [120, 60], - [88, 31], - [980, 120], - [980, 90], - [950, 90], - [930, 180], - [750, 300], - [750, 200], - [750, 100], - [580, 400], - [250, 360], - [240, 400], - ].sort(keyCompare(([w, h]) => -(w * h))) - .map(size => [size, sizeTupleToSizeString(size)]); - - return function(next, sizes) { - if (sizes?.length) { - const sizeStrings = new Set(sizes.map(sizeTupleToSizeString)); - const preferredSize = SUPPORTED_SIZES.find(([_, sizeStr]) => sizeStrings.has(sizeStr)); - if (preferredSize) { - next.bail(preferredSize[0]); - return; - } - } - next(sizes); - } -})(); - -export function setPAAPIConfigFactory( - getConfig = (filters) => getPAAPIConfig(filters, true), - setGptConfig = setComponentAuction, - getSlots = getGPTSlotsForAdUnits) { - /** - * Configure GPT slots with PAAPI auction configs. - * `filters` are the same filters accepted by `pbjs.getPAAPIConfig`; - */ - return function(filters = {}, customSlotMatching) { - let some = false; - const cfg = getConfig(filters) || {}; - const auToSlots = getSlots(Object.keys(cfg), customSlotMatching); - - Object.entries(cfg).forEach(([au, config]) => { - if (config != null) { - some = true; - } - setGptConfig(au, auToSlots[au], config?.componentAuctions || [], true); - }) - if (!some) { - logInfo(`${MODULE}: No component auctions available to set`); - } - } -} -/** - * Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`. - */ -getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory(); -const setTargetingHook = setTargetingHookFactory(); - -submodule('paapi', { - name: 'gpt', - init(params) { - getPAAPIConfig = params.getPAAPIConfig; - getHook('getPAAPISize').before(getPAAPISizeHook); - } -}); diff --git a/modules/paapiForGpt.md b/modules/paapiForGpt.md deleted file mode 100644 index 8565987eb5b..00000000000 --- a/modules/paapiForGpt.md +++ /dev/null @@ -1,123 +0,0 @@ -# Overview -This module allows Prebid.js to support PAAPI by integrating it with GPT's [experimental PAAPI -support](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing). - -To learn more about PAAPI in general, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md). - -This document covers the steps necessary for publishers to enable PAAPI on their inventory. It also describes -the changes Bid Adapters need to implement in order to support PAAPI. - -## Publisher Integration -Publishers wishing to enable PAAPI support must do two things. First, they must compile Prebid.js with support for this module. -This is accomplished by adding the `paapiForGpt` module to the list of modules they are already using: - -``` -gulp build --modules=paapiForGpt,... -``` - -Second, they must enable PAAPI in their Prebid.js configuration. -This is done through module level configuration, but to provide a high degree of flexiblity for testing, PAAPI settings also exist the slot level. - -### Module Configuration -This module exposes the following settings: - -|Name |Type |Description |Notes | -| :------------ | :------------ | :------------ |:------------ | -|enabled | Boolean |Enable/disable the module |Defaults to `false` | -|bidders | Array[String] |Optional list of bidders |Defaults to all bidders | -|defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1 | - -As noted above, PAAPI support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). -using the `setConfig` method of Prebid.js. Optionally, a list of bidders to apply these settings to may be provided: - -```js -pbjs.que.push(function() { - pbjs.setConfig({ - paapi: { - enabled: true, - bidders: ['openx', 'rtbhouse'], - defaultForSlots: 1 - } - }); -}); -``` - -### AdUnit Configuration -All adunits can be opted-in to PAAPI in the global config via the `defaultForSlots` parameter. -If needed, adunits can be configured individually by setting an attribute of the `ortb2Imp` object for that -adunit. This attribute will take precedence over `defaultForSlots` setting. - -|Name |Type |Description |Notes | -| :------------ | :------------ | :------------ |:------------ | -| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates PAAPI eligible, 0 indicates it is not | Absence indicates this is not PAAPI eligible | - -The `ae` field stands for Auction Environment and was chosen to be consistent with the field that GAM passes to bidders -in their Open Bidding and Exchange Bidding APIs. More details on that can be found -[here](https://github.com/google/ads-privacy/tree/master/proposals/fledge-rtb#bid-request-changes-indicating-interest-group-auction-support) -In practice, this looks as follows: - -```js -pbjs.addAdUnits({ - code: "my-adunit-div", - // other config here - ortb2Imp: { - ext: { - ae: 1 - } - } -}); -``` - -## Bid Adapter Integration -Chrome has enabled a two-tier auction in PAAPI. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with -a single entity serving as the final decision maker. In their [current approach](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing), -GPT has opted to run the final auction layer while allowing other SSPs/sellers to participate as -[Component Auctions](https://github.com/WICG/turtledove/blob/main/PAAPI.md#21-initiating-an-on-device-auction) which feed their -bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md#24-scoring-bids-in-component-auctions). - -The PAAPI auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given -seller. This module enables PAAPI support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an -`AuctionConfig` object, Prebid.js will register it with the appropriate GPT ad slot so the bidder can participate as a Component Auction in the overall -PAAPI auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). - -Modifying a bid adapter to support PAAPI is a straightforward process and consists of the following steps: -1. Detecting when a bid request is PAAPI eligible -2. Responding with AuctionConfig - -PAAPI eligibility is made available to bid adapters through the `bidderRequest.paapi.enabled` field. -The [`bidderRequest`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidderrequest-parameters) object is passed to -the [`buildRequests`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#building-the-request) method of an adapter. Bid adapters -who wish to participate should read this flag and pass it to their server. PAAPI eligibility depends on a number of parameters: - -1. Chrome enablement -2. Publisher participatipon in the [Origin Trial](https://developer.chrome.com/docs/privacy-sandbox/unified-origin-trial/#configure) -3. Publisher Prebid.js configuration (detailed above) - -When a bid request is PAAPI enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: - -```js -function interpretResponse(resp, req) { - // Load the bids from the response - this is adapter specific - const bids = parseBids(resp); - - // Load the auctionConfigs from the response - also adapter specific - const auctionConfigs = parseAuctionConfigs(resp); - - if (auctionConfigs) { - // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. - return {bids, auctionConfigs}; - } else { - return bids; - } -} -``` - -An AuctionConfig must be associated with an adunit and auction, and this is accomplished using the value in the `bidId` field from the objects in the -`validBidRequests` array passed to the `buildRequests` function - see [here](https://docs.prebid.org/dev-docs/bidder-adaptor.html#ad-unit-params-in-the-validbidrequests-array) -for more details. This means that the AuctionConfig objects returned from `interpretResponse` must contain a `bidId` field whose value corresponds to -the request it should be associated with. This may raise the question: why isn't the AuctionConfig object returned as part of the bid? The -answer is that it's possible to participate in the PAAPI auction without returning a contextual bid. - -An example of this can be seen in the OpenX OpenRTB bid adapter [here](https://github.com/prebid/Prebid.js/blob/master/modules/openxOrtbBidAdapter.js#L327). - -Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in PAAPI. The details of creating an AuctionConfig object are beyond the scope of this document. diff --git a/modules/panxoBidAdapter.js b/modules/panxoBidAdapter.js index 6f9eb80ae84..5de64af33ff 100644 --- a/modules/panxoBidAdapter.js +++ b/modules/panxoBidAdapter.js @@ -8,6 +8,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { deepAccess, logWarn, isFn, isPlainObject } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'panxo'; const ENDPOINT_URL = 'https://panxo-sys.com/openrtb/2.5/bid'; @@ -112,7 +113,7 @@ function buildDevice() { ua: navigator.userAgent, language: navigator.language, js: 1, - dnt: navigator.doNotTrack === '1' ? 1 : 0 + dnt: getDNT() ? 1 : 0 }; if (typeof screen !== 'undefined') { diff --git a/modules/prebidServerBidAdapter/index.ts b/modules/prebidServerBidAdapter/index.ts index 9f875321934..056eca786a1 100644 --- a/modules/prebidServerBidAdapter/index.ts +++ b/modules/prebidServerBidAdapter/index.ts @@ -21,7 +21,7 @@ import { import { DEBUG_MODE, EVENTS, REJECTION_REASON, S2S } from '../../src/constants.js'; import adapterManager, { s2sActivityParams } from '../../src/adapterManager.js'; import { config } from '../../src/config.js'; -import { addPaapiConfig, isValid } from '../../src/adapters/bidderFactory.js'; +import { isValid } from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import { ajax } from '../../src/ajax.js'; import { hook } from '../../src/hook.js'; @@ -459,7 +459,6 @@ export type PbsAnalytics = SeatNonBid & { declare module '../../src/events' { interface Events { - [EVENTS.SEAT_NON_BID]: [SeatNonBid]; [EVENTS.PBS_ANALYTICS]: [PbsAnalytics]; [EVENTS.BEFORE_PBS_HTTP]: [PbsRequestData]; } @@ -497,15 +496,6 @@ export function PrebidServer() { bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); } const { seatNonBidData, atagData } = getAnalyticsFlags(s2sBidRequest.s2sConfig, response) - if (seatNonBidData) { - events.emit(EVENTS.SEAT_NON_BID, { - seatnonbid: response.ext.seatnonbid, - auctionId: bidRequests[0].auctionId, - requestedBidders, - response, - adapterMetrics - }); - } // pbs analytics event if (seatNonBidData || atagData) { const data: PbsAnalytics = { @@ -544,11 +534,6 @@ export function PrebidServer() { addBidResponse.reject(adUnit, bid, REJECTION_REASON.INVALID); } } - }, - onFledge: (params) => { - config.runWithBidder(params.bidder, () => { - addPaapiConfig({ auctionId: bidRequests[0].auctionId, ...params }, { config: params.config }); - }) } }) } @@ -577,7 +562,7 @@ type PbsRequestData = { * @param onError {function(String, {})} invoked on HTTP failure - with status message and XHR error * @param onBid {function({})} invoked once for each bid in the response - with the bid as returned by interpretResponse */ -export const processPBSRequest = hook('async', function (s2sBidRequest, bidRequests, ajax, { onResponse, onError, onBid, onFledge }) { +export const processPBSRequest = hook('async', function (s2sBidRequest, bidRequests, ajax, { onResponse, onError, onBid }) { const { gdprConsent } = getConsentData(bidRequests); const adUnits = deepClone(s2sBidRequest.ad_units); @@ -606,11 +591,8 @@ export const processPBSRequest = hook('async', function (s2sBidRequest, bidReque let result; try { result = JSON.parse(response); - const { bids, paapi } = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); + const { bids } = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); - if (paapi) { - paapi.forEach(onFledge); - } } catch (error) { logError(error); } diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index 0ced549fca6..47edd70eed2 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -248,23 +248,6 @@ const PBS_CONVERTER = ortbConverter({ // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, { ...context, bidderRequest: req, bidRequests: req.bids })); }, - paapiConfigs(orig, response, ortbResponse, context) { - const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.paapiConfigs || []).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, - bidder: cfg.bidder, - config: cfg.config - }; - })); - if (configs.length > 0) { - response.paapi = configs; - } - } } }, }); @@ -317,9 +300,6 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste const proxyBidderRequest = { ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), - paapi: { - enabled: bidderRequests.some(br => br.paapi?.enabled) - } } return PBS_CONVERTER.toORTB({ diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 511f0b8eca5..4a1161fca73 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -1,6 +1,6 @@ import { logWarn, isStr, isArray, deepAccess, deepSetValue, isBoolean, isInteger, logInfo, logError, deepClone, uniques, generateUUID, isPlainObject, isFn, getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE, ADPOD } from '../src/mediaTypes.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { isViewabilityMeasurable, getViewability } from '../libraries/percentInView/percentInView.js'; @@ -9,6 +9,7 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS } from '../src/constants.js'; import { addDealCustomTargetings, addPMPDeals } from '../libraries/dealUtils/dealUtils.js'; import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -95,7 +96,7 @@ const converter = ortbConverter({ if (imp.hasOwnProperty('banner')) updateBannerImp(imp.banner, adSlot); if (imp.hasOwnProperty('video')) updateVideoImp(mediaTypes?.video, adUnitCode, imp); if (imp.hasOwnProperty('native')) updateNativeImp(imp, mediaTypes?.native); - if (imp.hasOwnProperty('banner') || imp.hasOwnProperty('video')) addViewabilityToImp(imp, adUnitCode, bidRequest?.sizes); + if (imp.hasOwnProperty('banner') || imp.hasOwnProperty('video')) addViewabilityToImp(imp, bidRequest, bidRequest?.sizes); if (pmzoneid) imp.ext.pmZoneId = pmzoneid; setImpTagId(imp, adSlot.trim(), hashedKey); setImpFields(imp); @@ -142,12 +143,11 @@ const converter = ortbConverter({ if (mediaType === VIDEO) { if (!bidResponse.width) bidResponse.width = playerWidth; if (!bidResponse.height) bidResponse.height = playerHeight; - const { context, maxduration } = mediaTypes[mediaType]; + const { context } = mediaTypes[mediaType]; if (context === 'outstream' && params.outstreamAU && adUnitCode) { bidResponse.rendererCode = params.outstreamAU; bidResponse.renderer = BB_RENDERER.newRenderer(bidResponse.rendererCode, adUnitCode); } - assignDealTier(bidResponse, context, maxduration); } if (mediaType === NATIVE && bid.adm) { try { @@ -529,27 +529,6 @@ const addExtenstionParams = (req, bidderRequest) => { } } -/** - * In case of adpod video context, assign prebiddealpriority to the dealtier property of adpod-video bid, - * so that adpod module can set the hb_pb_cat_dur targetting key. - * @param {*} bid - * @param {*} context - * @param {*} maxduration - * @returns - */ -const assignDealTier = (bid, context, maxduration) => { - if (!bid?.ext?.prebiddealpriority || !FEATURES.VIDEO) return; - if (context !== ADPOD) return; - - const duration = bid?.ext?.video?.duration || maxduration; - // if (!duration) return; - bid.video = { - context: ADPOD, - durationSeconds: duration, - dealTier: bid.ext.prebiddealpriority - }; -} - const validateAllowedCategories = (acat) => { return [...new Set( acat @@ -626,7 +605,7 @@ const BB_RENDERER = { } const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode); - const ele = document.getElementById(bid.adUnitCode); // NB convention + const ele = getAdUnitElement(bid); const renderer = window.bluebillywig.renderers.find(r => r._id === rendererId); if (renderer) renderer.bootstrap(config, ele); @@ -728,10 +707,10 @@ function _getMinSize(sizes) { /** * Measures viewability for an element and adds it to the imp object at the ext level * @param {Object} imp - The impression object - * @param {string} adUnitCode - The ad unit code for element identification + * @param {Object} bidRequest - The bid request for element identification * @param {Object} sizes - Sizes object with width and height properties */ -export const addViewabilityToImp = (imp, adUnitCode, sizes) => { +export const addViewabilityToImp = (imp, bidRequest, sizes) => { let elementSize = { w: 0, h: 0 }; if (imp.video?.w > 0 && imp.video?.h > 0) { @@ -740,7 +719,7 @@ export const addViewabilityToImp = (imp, adUnitCode, sizes) => { } else { elementSize = _getMinSize(sizes); } - const element = document.getElementById(adUnitCode); + const element = getAdUnitElement(bidRequest); if (!element) return; const viewabilityAmount = isViewabilityMeasurable(element) diff --git a/modules/pwbidBidAdapter.js b/modules/pwbidBidAdapter.js index eaa06107b39..1d2fa182b79 100644 --- a/modules/pwbidBidAdapter.js +++ b/modules/pwbidBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { _each, isBoolean, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError, isPlainObject } 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 { OUTSTREAM, INSTREAM } from '../src/video.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js deleted file mode 100644 index e8dc9ff6852..00000000000 --- a/modules/quantcastBidAdapter.js +++ /dev/null @@ -1,297 +0,0 @@ -import { deepAccess, isArray, isEmpty, logError, logInfo } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { config } from '../src/config.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { registerBidder } from '../src/adapters/bidderFactory.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; - -const QUANTCAST_VENDOR_ID = '11'; -// Check other required purposes on server -const PURPOSE_DATA_COLLECT = '1'; - -export const QUANTCAST_DOMAIN = 'qcx.quantserve.com'; -export const QUANTCAST_TEST_DOMAIN = 's2s-canary.quantserve.com'; -export const QUANTCAST_NET_REVENUE = true; -export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; -export const QUANTCAST_TTL = 4; -export const QUANTCAST_PROTOCOL = 'https'; -export const QUANTCAST_PORT = '8443'; -export const QUANTCAST_FPA = '__qca'; - -export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); - -function makeVideoImp(bid) { - const videoInMediaType = deepAccess(bid, 'mediaTypes.video') || {}; - const videoInParams = deepAccess(bid, 'params.video') || {}; - const video = Object.assign({}, videoInParams, videoInMediaType); - - if (video.playerSize) { - video.w = video.playerSize[0]; - video.h = video.playerSize[1]; - } - const videoCopy = { - mimes: video.mimes, - minduration: video.minduration, - maxduration: video.maxduration, - protocols: video.protocols, - startdelay: video.startdelay, - linearity: video.linearity, - battr: video.battr, - maxbitrate: video.maxbitrate, - playbackmethod: video.playbackmethod, - delivery: video.delivery, - api: video.api, - w: video.w, - h: video.h - } - - return { - video: videoCopy, - placementCode: bid.placementCode, - bidFloor: DEFAULT_BID_FLOOR - }; -} - -function makeBannerImp(bid) { - const sizes = bid.sizes || bid.mediaTypes.banner.sizes; - - return { - banner: { - battr: bid.params.battr, - sizes: sizes.map(size => { - return { - width: size[0], - height: size[1] - }; - }) - }, - placementCode: bid.placementCode, - bidFloor: DEFAULT_BID_FLOOR - }; -} - -function checkTCF(tcData) { - const restrictions = tcData.publisher ? tcData.publisher.restrictions : {}; - const qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT] - ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] - : null; - - if (qcRestriction === 0 || qcRestriction === 2) { - // Not allowed by publisher, or requires legitimate interest - return false; - } - - const vendorConsent = tcData.vendor && tcData.vendor.consents && tcData.vendor.consents[QUANTCAST_VENDOR_ID]; - const purposeConsent = tcData.purpose && tcData.purpose.consents && tcData.purpose.consents[PURPOSE_DATA_COLLECT]; - - return !!(vendorConsent && purposeConsent); -} - -function getQuantcastFPA() { - const fpa = storage.getCookie(QUANTCAST_FPA) - return fpa || '' -} - -let hasUserSynced = false; - -/** - * The documentation for Prebid.js Adapter 1.0 can be found at link below, - * http://prebid.org/dev-docs/bidder-adapter-1.html - */ -export const spec = { - code: BIDDER_CODE, - gvlid: QUANTCAST_VENDOR_ID, - supportedMediaTypes: ['banner', 'video'], - - /** - * Verify the `AdUnits.bids` response with `true` for valid request and `false` - * for invalid request. - * - * @param {object} bid - * @return boolean `true` is this is a valid bid, and `false` otherwise - */ - isBidRequestValid(bid) { - return !!bid.params.publisherId; - }, - - /** - * Make a server request when the page asks Prebid.js for bids from a list of - * `BidRequests`. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be send to Quantcast server - * @param bidderRequest - * @return ServerRequest information describing the request to the server. - */ - buildRequests(bidRequests, bidderRequest) { - const bids = bidRequests || []; - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent') || {}; - const uspConsent = deepAccess(bidderRequest, 'uspConsent'); - const referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - const page = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - const domain = parseDomain(page, { noLeadingWww: true }); - - // Check for GDPR consent for purpose 1, and drop request if consent has not been given - // Remaining consent checks are performed server-side. - if (gdprConsent.gdprApplies) { - if (gdprConsent.vendorData) { - if (!checkTCF(gdprConsent.vendorData)) { - logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); - return; - } - } - } - - const bidRequestsList = []; - - bids.forEach(bid => { - let imp; - if (bid.mediaTypes) { - if (bid.mediaTypes.video && bid.mediaTypes.video.context === 'instream') { - imp = makeVideoImp(bid); - } else if (bid.mediaTypes.banner) { - imp = makeBannerImp(bid); - } else { - // Unsupported mediaType - logInfo(`${BIDDER_CODE}: No supported mediaTypes found in ${JSON.stringify(bid.mediaTypes)}`); - return; - } - } else { - // Parse as banner by default - imp = makeBannerImp(bid); - } - - // Request Data Format can be found at https://wiki.corp.qc/display/adinf/QCX - const requestData = { - publisherId: bid.params.publisherId, - requestId: bid.bidId, - imp: [imp], - site: { - page, - referrer, - domain - }, - bidId: bid.bidId, - gdprSignal: gdprConsent.gdprApplies ? 1 : 0, - gdprConsent: gdprConsent.consentString, - uspSignal: uspConsent ? 1 : 0, - uspConsent, - coppa: config.getConfig('coppa') === true ? 1 : 0, - prebidJsVersion: '$prebid.version$', - fpa: getQuantcastFPA() - }; - - const data = JSON.stringify(requestData); - const qcDomain = bid.params.publisherId === QUANTCAST_TEST_PUBLISHER - ? QUANTCAST_TEST_DOMAIN - : QUANTCAST_DOMAIN; - const url = `${QUANTCAST_PROTOCOL}://${qcDomain}:${QUANTCAST_PORT}/qchb`; - - bidRequestsList.push({ - data, - method: 'POST', - url - }); - }); - - return bidRequestsList; - }, - - /** - * Function get called when the browser has received the response from Quantcast server. - * The function parse the response and create a `bidResponse` object containing one/more bids. - * Returns an empty array if no valid bids - * - * Response Data Format can be found at https://wiki.corp.qc/display/adinf/QCX - * - * @param {*} serverResponse A successful response from Quantcast server. - * @return {Bid[]} An array of bids which were nested inside the server. - * - */ - interpretResponse(serverResponse) { - if (serverResponse === undefined) { - logError('Server Response is undefined'); - return []; - } - - const response = serverResponse['body']; - - if (response === undefined || !response.hasOwnProperty('bids')) { - logError('Sub-optimal JSON received from Quantcast server'); - return []; - } - - if (isEmpty(response.bids)) { - // Shortcut response handling if no bids are present - return []; - } - - const bidResponsesList = response.bids.map(bid => { - const { ad, cpm, width, height, creativeId, currency, videoUrl, dealId, meta } = bid; - - const result = { - requestId: response.requestId, - cpm, - width, - height, - ad, - ttl: QUANTCAST_TTL, - creativeId, - netRevenue: QUANTCAST_NET_REVENUE, - currency - }; - - if (videoUrl !== undefined && videoUrl) { - result['vastUrl'] = videoUrl; - result['mediaType'] = 'video'; - } - - if (dealId !== undefined && dealId) { - result['dealId'] = dealId; - } - - if (meta !== undefined && meta.advertiserDomains && isArray(meta.advertiserDomains)) { - result.meta = {}; - result.meta.advertiserDomains = meta.advertiserDomains; - } - - return result; - }); - - return bidResponsesList; - }, - onTimeout(timeoutData) { - const url = `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb_notify?type=timeout`; - ajax(url, null, null); - }, - getUserSyncs(syncOptions, serverResponses) { - const syncs = [] - if (!hasUserSynced && syncOptions.pixelEnabled) { - const responseWithUrl = ((serverResponses) || []).find(serverResponse => - deepAccess(serverResponse.body, 'userSync.url') - ); - - if (responseWithUrl) { - const url = deepAccess(responseWithUrl.body, 'userSync.url') - syncs.push({ - type: 'image', - url: url - }); - } - hasUserSynced = true; - } - return syncs; - }, - resetUserSync() { - hasUserSynced = false; - } -}; - -registerBidder(spec); diff --git a/modules/quantcastBidAdapter.md b/modules/quantcastBidAdapter.md deleted file mode 100644 index edbbc538b65..00000000000 --- a/modules/quantcastBidAdapter.md +++ /dev/null @@ -1,74 +0,0 @@ -# Overview - -``` -Module Name: Quantcast Bidder Adapter -Module Type: Bidder Adapter -Maintainer: inventoryteam@quantcast.com -``` - -# Description - -Module that connects to Quantcast demand sources to fetch bids. - -# Test Parameters - -## Sample Banner Ad Unit -```js -const adUnits = [{ - code: 'banner', - sizes: [ - [300, 250] - ], - bids: [ - { - bidder: 'quantcast', - params: { - publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast - battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 - } - } - ], - userSync: { - url: 'https://quantcast.com/pixelUrl' - } -}]; -``` - -## Sample Video Ad Unit -```js -var adUnits = [{ - code: 'video', - mediaTypes: { - video: { - context: 'instream', // required - playerSize: [600, 300] // required - } - }, - bids: [ - { - bidder: 'quantcast', - params: { - publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast - // Video object as specified in OpenRTB 2.5 - video: { - mimes: ['video/mp4'], // required - minduration: 3, // optional - maxduration: 5, // optional - protocols: [3], // optional - startdelay: 1, // optional - linearity: 1, // optinal - battr: [1, 2], // optional - maxbitrate: 10, // optional - playbackmethod: [1], // optional - delivery: [1], // optional - placement: 1, // optional - api: [2, 3] // optional - } - } - } - ], - userSync: { - url: 'https://quantcast.com/pixelUrl' - } -}]; -``` diff --git a/modules/quantcastIdSystem.js b/modules/quantcastIdSystem.js deleted file mode 100644 index 4b4d7b97d40..00000000000 --- a/modules/quantcastIdSystem.js +++ /dev/null @@ -1,230 +0,0 @@ -/** - * This module adds QuantcastID to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/quantcastIdSystem - * @requires module:modules/userId - */ - -import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; -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; -const PREBID_PCODE = 'p-KceJUEvXN48CE'; -const QSERVE_URL = 'https://pixel.quantserve.com/pixel'; -const QUANTCAST_VENDOR_ID = '11'; -const PURPOSE_DATA_COLLECT = '1'; -const PURPOSE_PRODUCT_IMPROVEMENT = '10'; -const QC_TCF_REQUIRED_PURPOSES = [PURPOSE_DATA_COLLECT, PURPOSE_PRODUCT_IMPROVEMENT]; -const QC_TCF_CONSENT_FIRST_PURPOSES = [PURPOSE_DATA_COLLECT]; -const QC_TCF_CONSENT_ONLY_PUPROSES = [PURPOSE_DATA_COLLECT]; -const GDPR_PRIVACY_STRING = gdprDataHandler.getConsentData(); -const US_PRIVACY_STRING = uspDataHandler.getConsentData(); -const MODULE_NAME = 'quantcastId'; - -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); - -export function firePixel(clientId, cookieExpDays = DEFAULT_COOKIE_EXP_DAYS) { - // check for presence of Quantcast Measure tag _qevent obj and publisher provided clientID - if (!window._qevents && clientId) { - var fpa = storage.getCookie(QUANTCAST_FPA); - var fpan = '0'; - var domain = quantcastIdSubmodule.findRootDomain(); - var now = new Date(); - var usPrivacyParamString = ''; - var firstPartyParamStrings; - var gdprParamStrings; - - if (!fpa) { - var et = now.getTime(); - var expires = new Date(et + (cookieExpDays * DAY_MS)).toGMTString(); - var rand = Math.round(Math.random() * 2147483647); - fpa = `B0-${rand}-${et}`; - fpan = '1'; - storage.setCookie(QUANTCAST_FPA, fpa, expires, '/', domain, null); - } - - firstPartyParamStrings = `&fpan=${fpan}&fpa=${fpa}`; - gdprParamStrings = '&gdpr=0'; - if (GDPR_PRIVACY_STRING && typeof GDPR_PRIVACY_STRING.gdprApplies === 'boolean' && GDPR_PRIVACY_STRING.gdprApplies) { - gdprParamStrings = `gdpr=1&gdpr_consent=${GDPR_PRIVACY_STRING.consentString}`; - } - if (US_PRIVACY_STRING && typeof US_PRIVACY_STRING === 'string') { - usPrivacyParamString = `&us_privacy=${US_PRIVACY_STRING}`; - } - - const url = QSERVE_URL + - '?d=' + domain + - '&client_id=' + clientId + - '&a=' + PREBID_PCODE + - usPrivacyParamString + - gdprParamStrings + - firstPartyParamStrings; - - triggerPixel(url); - } -}; - -export function hasGDPRConsent(gdprConsent) { - // Check for GDPR consent for purpose 1 and 10, and drop request if consent has not been given - // Remaining consent checks are performed server-side. - if (gdprConsent && typeof gdprConsent.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { - if (!gdprConsent.vendorData) { - return false; - } - return checkTCFv2(gdprConsent.vendorData); - } - return true; -} - -export function checkTCFv2(vendorData, requiredPurposes = QC_TCF_REQUIRED_PURPOSES) { - var gdprApplies = vendorData.gdprApplies; - var purposes = vendorData.purpose; - var vendors = vendorData.vendor; - var qcConsent = vendors && vendors.consents && vendors.consents[QUANTCAST_VENDOR_ID]; - var qcInterest = vendors && vendors.legitimateInterests && vendors.legitimateInterests[QUANTCAST_VENDOR_ID]; - var restrictions = vendorData.publisher ? vendorData.publisher.restrictions : {}; - - if (!gdprApplies) { - return true; - } - - return requiredPurposes.map(function(purpose) { - var purposeConsent = purposes.consents ? purposes.consents[purpose] : false; - var purposeInterest = purposes.legitimateInterests ? purposes.legitimateInterests[purpose] : false; - - var qcRestriction = restrictions && restrictions[purpose] - ? restrictions[purpose][QUANTCAST_VENDOR_ID] - : null; - - if (qcRestriction === 0) { - return false; - } - - // Seek consent or legitimate interest based on our default legal - // basis for the purpose, falling back to the other if possible. - if ( - // we have positive vendor consent - qcConsent && - // there is positive purpose consent - purposeConsent && - // publisher does not require legitimate interest - qcRestriction !== 2 && - // purpose is a consent-first purpose or publisher has explicitly restricted to consent - (QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) !== -1 || qcRestriction === 1) - ) { - return true; - } else if ( - // publisher does not require consent - qcRestriction !== 1 && - // we have legitimate interest for vendor - qcInterest && - // there is legitimate interest for purpose - purposeInterest && - // purpose's legal basis does not require consent - QC_TCF_CONSENT_ONLY_PUPROSES.indexOf(purpose) === -1 && - // purpose is a legitimate-interest-first purpose or publisher has explicitly restricted to legitimate interest - (QC_TCF_CONSENT_FIRST_PURPOSES.indexOf(purpose) === -1 || qcRestriction === 2) - ) { - return true; - } - - return false; - }).reduce(function(a, b) { - return a && b; - }, true); -} - -/** - * tests if us_privacy consent string is present, us_privacy applies, and notice_given / do-not-sell is set to yes - * @returns {boolean} - */ -export function hasCCPAConsent(usPrivacyConsent) { - if ( - usPrivacyConsent && - typeof usPrivacyConsent === 'string' && - usPrivacyConsent.length === 4 && - usPrivacyConsent.charAt(1) === 'Y' && - usPrivacyConsent.charAt(2) === 'Y' - ) { - return false - } - return true; -} - -/** @type {Submodule} */ -export const quantcastIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - - /** - * Vendor id of Quantcast - * @type {Number} - */ - gvlid: QUANTCAST_VENDOR_ID, - - /** - * decode the stored id value for passing to bid requests - * @function - * @returns {{quantcastId: string} | undefined} - */ - decode(value) { - return value; - }, - - /** - * read Quantcast first party cookie and pass it along in quantcastId - * @function - * @returns {{id: {quantcastId: string} | undefined}}} - */ - getId(config) { - // Consent signals are currently checked on the server side. - const fpa = storage.getCookie(QUANTCAST_FPA); - - const coppa = coppaDataHandler.getCoppa(); - - if (coppa || !hasCCPAConsent(US_PRIVACY_STRING) || !hasGDPRConsent(GDPR_PRIVACY_STRING)) { - var expired = new Date(0).toUTCString(); - var domain = quantcastIdSubmodule.findRootDomain(); - logInfo('QuantcastId: Necessary consent not present for Id, exiting QuantcastId'); - storage.setCookie(QUANTCAST_FPA, '', expired, '/', domain, null); - return undefined; - } - - const configParams = (config && config.params) || {}; - const storageParams = (config && config.storage) || {}; - - var clientId = configParams.clientId || ''; - var cookieExpDays = storageParams.expires || DEFAULT_COOKIE_EXP_DAYS; - - // Callbacks on Event Listeners won't trigger if the event is already complete so this check is required - if (document.readyState === 'complete') { - firePixel(clientId, cookieExpDays); - } else { - window.addEventListener('load', function () { - firePixel(clientId, cookieExpDays); - }); - } - - return { id: fpa ? { quantcastId: fpa } : undefined }; - }, - eids: { - 'quantcastId': { - source: 'quantcast.com', - atype: 1 - }, - } -}; - -submodule('userId', quantcastIdSubmodule); diff --git a/modules/quantcastIdSystem.md b/modules/quantcastIdSystem.md deleted file mode 100644 index 7e90764432b..00000000000 --- a/modules/quantcastIdSystem.md +++ /dev/null @@ -1,46 +0,0 @@ -#### Overview - -``` -Module Name: Quantcast Id System -Module Type: Id System -Maintainer: asig@quantcast.com -``` - -#### Description - - The Prebid Quantcast ID module stores a Quantcast ID in a first party cookie. The ID is then made available in the bid request. The ID from the cookie added in the bidstream allows Quantcast to more accurately bid on publisher inventories without third party cookies, which can result in better monetization across publisher sites from Quantcast. And, it’s free to use! For easier integration, you can work with one of our SSP partners, like PubMatic, who can facilitate the legal process as well as the software integration for you. - - Add it to your Prebid.js package with: - - `gulp build --modules=userId,quantcastIdSystem` - - Quantcast’s privacy policies for the services rendered can be found at - https://www.quantcast.com/privacy/ - - Publishers deploying the module are responsible for ensuring legally required notices and choices for users. - - The Quantcast ID module will only perform any action and return an ID in situations where: - 1. the publisher has not set a ‘coppa' flag on the prebid configuration on their site (see [pbjs.setConfig.coppa](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-coppa)) - 2. there is not a IAB us-privacy string indicating the digital property has provided user notice and the user has made a choice to opt out of sale - 3. if GDPR applies, an IAB TCF v2 string exists indicating that Quantcast does not have consent for purpose 1 (cookies, device identifiers, or other information can be stored or accessed on your device for the purposes presented to you), or an established legal basis (by default legitimate interest) for purpose 10 (your data can be used to improve existing systems and software, and to develop new products). - - #### Quantcast ID Configuration - - | Param under userSync.userIds[] | Scope | Type | Description | Example | - | --- | --- | --- | --- | --- | - | name | Required | String | `"quantcastId"` | `"quantcastId"` | - | params | Optional | Object | Details for Quantcast initialization. | | - | params.ClientID | Optional | String | Optional parameter for Quantcast prebid managed service partners. The parameter is not required for websites with Quantcast Measure tag. Reach out to Quantcast for ClientID if you are not an existing Quantcast prebid managed service partner: quantcast-idsupport@quantcast.com. | | - - - #### Quantcast ID Example - -```js - pbjs.setConfig({ - userSync: { - userIds: [{ - name: "quantcastId" - }] - } - }); -``` diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index ca268a6f949..f7518d0d916 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -1,9 +1,9 @@ 'use strict'; -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, parseSizesInput, isArray } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getDNT } from '../libraries/dnt/index.js'; function RhythmOneBidAdapter() { this.code = 'rhythmone'; diff --git a/modules/ringieraxelspringerBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js deleted file mode 100644 index c5b7e000f87..00000000000 --- a/modules/ringieraxelspringerBidAdapter.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Backward-compatibility shim for ringieraxelspringer bidder. - * This bidder has been renamed to 'das'. - * - * This file will be removed in Prebid 11. - * See dasBidAdapter.js for implementation. - */ -export { spec } from './dasBidAdapter.js'; // eslint-disable-line prebid/validate-imports diff --git a/modules/ringieraxelspringerBidAdapter.md b/modules/ringieraxelspringerBidAdapter.md deleted file mode 100644 index 4527d9f8c6d..00000000000 --- a/modules/ringieraxelspringerBidAdapter.md +++ /dev/null @@ -1,8 +0,0 @@ -# Overview - -The `ringieraxelspringer` bidder has been renamed to `das`. -Please use the `das` bidder code instead. - -See [dasBidAdapter.md](./dasBidAdapter.md) for documentation. - -This adapter will be removed in Prebid 11. diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index 7fcae1299b2..b8b59aa9edc 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -65,46 +65,3 @@ Please reach out to pmp@rtbhouse.com to receive your own } ]; ``` - -# Protected Audience API (FLEDGE) support -There’s an option to receive demand for Protected Audience API (FLEDGE/PAAPI) -ads using RTB House bid adapter. -Prebid’s [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) -module and Google Ad Manager is currently required. - -The following steps should be taken to setup Protected Audience for RTB House: - -1. Reach out to your RTB House representative for setup coordination. - -2. Build and enable FLEDGE module as described in -[paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) -module documentation. - - a. Make sure to enable RTB House bidder to participate in FLEDGE. If there are any other bidders to be allowed for that, add them to the **bidders** array: - ```javascript - pbjs.setConfig({ - paapi: { - bidders: ["rtbhouse"], - enabled: true - } - }); - ``` - - b. If you as a publisher have your own [decisionLogicUrl](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) - you may utilize it by setting up a dedicated `fledgeConfig` object: - ```javascript - pbjs.setConfig({ - paapi: { - bidders: ["rtbhouse"], - enabled: true - }, - fledgeConfig: { - seller: 'https://seller.domain', - decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', - sellerTimeout: 100 - } - }); - ``` - The `decisionLogicUrl` must be in the same domain as `seller` and has to respond with `X-Allow-FLEDGE: true` http header. - - `sellerTimeout` is optional, defaults to 50 as per spec, will be clamped to 500 if greater. diff --git a/modules/rtdModule/index.ts b/modules/rtdModule/index.ts index fa91f22b467..8a358aead21 100644 --- a/modules/rtdModule/index.ts +++ b/modules/rtdModule/index.ts @@ -53,8 +53,7 @@ const setEventsListeners = (function () { [EVENTS.AUCTION_INIT]: ['onAuctionInitEvent'], [EVENTS.AUCTION_END]: ['onAuctionEndEvent', getAdUnitTargeting], [EVENTS.BID_RESPONSE]: ['onBidResponseEvent'], - [EVENTS.BID_REQUESTED]: ['onBidRequestEvent'], - [EVENTS.BID_ACCEPTED]: ['onBidAcceptedEvent'] + [EVENTS.BID_REQUESTED]: ['onBidRequestEvent'] }).forEach(([ev, [handler, preprocess]]) => { events.on(ev as any, (args) => { preprocess && (preprocess as any)(args); diff --git a/modules/rtdModule/spec.ts b/modules/rtdModule/spec.ts index 60276400c1d..0fde369867d 100644 --- a/modules/rtdModule/spec.ts +++ b/modules/rtdModule/spec.ts @@ -32,8 +32,7 @@ export type RTDProviderConfig

= BaseConfig

& ( type RTDEvent = typeof EVENTS.AUCTION_INIT | typeof EVENTS.AUCTION_END | typeof EVENTS.BID_RESPONSE | - typeof EVENTS.BID_REQUESTED | - typeof EVENTS.BID_ACCEPTED; + typeof EVENTS.BID_REQUESTED; type EventHandlers

= { [EV in RTDEvent]: (payload: EventPayload, config: RTDProviderConfig

, consent: AllConsentData) => void; diff --git a/modules/scaliburBidAdapter.js b/modules/scaliburBidAdapter.js index fcea9317881..71613297001 100644 --- a/modules/scaliburBidAdapter.js +++ b/modules/scaliburBidAdapter.js @@ -3,6 +3,7 @@ import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { sizesToSizeTuples } from "../src/utils.js"; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'scalibur'; const ENDPOINT_SERVER = new URLSearchParams(window.location.search).get('sclServer') || 'srv'; @@ -115,7 +116,7 @@ export const spec = { ua: ortb2Device.ua, language: ortb2Device.language, sua: ortb2Device.sua || {}, - dnt: ortb2Device.dnt ?? 0, + dnt: getDNT() ? 1 : 0, }, user: { eids, diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index 10b1a872fed..c38325ece0a 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -5,6 +5,7 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { _map, getWinDimensions, isArray, triggerPixel } from '../src/utils.js'; import { getViewportCoordinates } from '../libraries/viewport/viewport.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -121,7 +122,7 @@ function buildBidRequest(validBidRequest) { supplyTypes: mediaTypes, adUnitId: params.adUnitId, adUnitCode: validBidRequest.adUnitCode, - geom: geom(validBidRequest.adUnitCode), + geom: geom(validBidRequest), placement: params.placement, requestCount: validBidRequest.bidderRequestsCount || 1, }; @@ -215,8 +216,8 @@ function ttfb() { return ttfb >= 0 && ttfb <= performance.now() ? ttfb : 0; } -function geom(adunitCode) { - const slot = document.getElementById(adunitCode); +function geom(bidRequest) { + const slot = getAdUnitElement(bidRequest); if (slot) { const { top, left, width, height } = getBoundingClientRect(slot); const viewport = { diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index a11820b5897..406ed77fc2b 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { handleCookieSync, PID_STORAGE_NAME, prepareSplitImps } from '../libraries/equativUtils/equativUtils.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -6,6 +5,7 @@ import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js'; +import { getDNT } from '../libraries/dnt/index.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; @@ -159,10 +159,6 @@ export const sharethroughAdapterSpec = { const nativeRequest = deepAccess(bidReq, 'mediaTypes.native'); const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); - if (bidderRequest.paapi?.enabled && 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]; @@ -292,8 +288,6 @@ export const sharethroughAdapterSpec = { return []; } - const fledgeAuctionEnabled = body.ext?.auctionConfigs; - const imp = req.data.imp[0]; const bidsFromExchange = body.seatbid[0].bid.map((bid) => { @@ -340,15 +334,7 @@ export const sharethroughAdapterSpec = { return response; }); - - if (fledgeAuctionEnabled && !isEqtvTest) { - return { - bids: bidsFromExchange, - paapi: body.ext?.auctionConfigs || {}, - }; - } else { - return bidsFromExchange; - } + return bidsFromExchange; }, getUserSyncs: (syncOptions, serverResponses, gdprConsent) => { diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js index 1ee5888c67f..c579619c8db 100644 --- a/modules/silverpushBidAdapter.js +++ b/modules/silverpushBidAdapter.js @@ -117,22 +117,7 @@ export const CONVERTER = ortbConverter({ }, response(buildResponse, bidResponses, ortbResponse, context) { const response = buildResponse(bidResponses, ortbResponse, context); - - let fledgeAuctionConfigs = utils.deepAccess(ortbResponse, 'ext.fledge_auction_configs'); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs = Object.entries(fledgeAuctionConfigs).map(([bidId, cfg]) => { - return Object.assign({ - bidId, - auctionSignals: {} - }, cfg); - }); - return { - bids: response.bids, - paapi: fledgeAuctionConfigs, - } - } else { - return response.bids - } + return response.bids } }); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 28fbdf61ae7..99bb13ff3a7 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -1,13 +1,11 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, isEmpty, isNumber, logError, logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; -import { ADPOD, BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.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'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -48,30 +46,14 @@ export const spec = { return false; } - if (deepAccess(bid, 'mediaTypes.video.context') === ADPOD) { - logInfo('[SMAATO] Verifying adpod bid request'); - - if (typeof bid.params.adbreakId !== 'string') { - logError('[SMAATO] Missing for adpod request mandatory adbreakId param'); - return false; - } - - if (bid.params.adspaceId) { - logError('[SMAATO] The adspaceId param is not allowed in an adpod bid request'); - return false; - } - } else { - logInfo('[SMAATO] Verifying a non adpod bid request'); - - if (typeof bid.params.adspaceId !== 'string') { - logError('[SMAATO] Missing mandatory adspaceId param'); - return false; - } + if (typeof bid.params.adspaceId !== 'string') { + logError('[SMAATO] Missing mandatory adspaceId param'); + return false; + } - if (bid.params.adbreakId) { - logError('[SMAATO] The adbreakId param is only allowed in an adpod bid request'); - return false; - } + if (bid.params.adbreakId) { + logError('[SMAATO] The adbreakId param is not supported'); + return false; } logInfo('[SMAATO] Verification done, all good'); @@ -148,39 +130,25 @@ export const spec = { } }; - const videoContext = deepAccess(JSON.parse(bidRequest.data).imp[0], 'video.ext.context'); - if (videoContext === ADPOD) { - resultingBid.vastXml = bid.adm; - resultingBid.mediaType = VIDEO; - if (config.getConfig('adpod.brandCategoryExclusion')) { - resultingBid.meta.primaryCatId = bid.cat[0]; - } - resultingBid.video = { - context: ADPOD, - durationSeconds: bid.ext.duration - }; - bids.push(resultingBid); - } else { - switch (smtAdType) { - case 'Img': - case 'Richmedia': - resultingBid.ad = createBannerAd(bid); - resultingBid.mediaType = BANNER; - bids.push(resultingBid); - break; - case 'Video': - resultingBid.vastXml = bid.adm; - resultingBid.mediaType = VIDEO; - bids.push(resultingBid); - break; - case 'Native': - resultingBid.native = createNativeAd(bid.adm); - resultingBid.mediaType = NATIVE; - bids.push(resultingBid); - break; - default: - logInfo('[SMAATO] Invalid ad type:', smtAdType); - } + switch (smtAdType) { + case 'Img': + case 'Richmedia': + resultingBid.ad = createBannerAd(bid); + resultingBid.mediaType = BANNER; + bids.push(resultingBid); + break; + case 'Video': + resultingBid.vastXml = bid.adm; + resultingBid.mediaType = VIDEO; + bids.push(resultingBid); + break; + case 'Native': + resultingBid.native = createNativeAd(bid.adm); + resultingBid.mediaType = NATIVE; + bids.push(resultingBid); + break; + default: + logInfo('[SMAATO] Invalid ad type:', smtAdType); } resultingBid.meta.mediaType = resultingBid.mediaType; }); @@ -248,15 +216,6 @@ const converter = ortbConverter({ const request = buildRequest(imps, bidderRequest, context); const bidRequest = context.bidRequests[0]; - let content; - const mediaType = context.mediaType; - if (mediaType === VIDEO) { - const videoParams = bidRequest.mediaTypes[VIDEO]; - if (videoParams.context === ADPOD) { - request.imp = createAdPodImp(request.imp[0], videoParams); - content = addOptionalAdpodParameters(videoParams); - } - } request.at = 1; @@ -276,15 +235,9 @@ const converter = ortbConverter({ if (request.site) { request.site.id = window.location.hostname - if (content) { - request.site.content = content; - } setPublisherId(request.site); } else if (request.dooh) { request.dooh.id = window.location.hostname - if (content) { - request.dooh.content = content; - } setPublisherId(request.dooh); } else { request.site = { @@ -292,7 +245,7 @@ const converter = ortbConverter({ domain: bidderRequest.refererInfo.domain || window.location.hostname, page: bidderRequest.refererInfo.page || window.location.href, ref: bidderRequest.refererInfo.ref, - content: content || null + content: null } setPublisherId(request.site); } @@ -363,7 +316,6 @@ const converter = ortbConverter({ imp: { banner(orig, imp, bidRequest, context) { const mediaType = context.mediaType; - if (mediaType === BANNER) { imp.bidfloor = getBidFloor(bidRequest, BANNER, getAdUnitSizes(bidRequest)); } @@ -376,11 +328,9 @@ const converter = ortbConverter({ 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 - }) - } + deepSetValue(imp, 'video.ext', { + rewarded: videoParams.ext && videoParams.ext.rewarded ? videoParams.ext.rewarded : 0 + }) } orig(imp, bidRequest, context); @@ -388,7 +338,6 @@ const converter = ortbConverter({ native(orig, imp, bidRequest, context) { const mediaType = context.mediaType; - if (mediaType === NATIVE) { imp.bidfloor = getBidFloor(bidRequest, NATIVE, getNativeMainImageSize(bidRequest.nativeOrtbRequest)); } @@ -432,81 +381,6 @@ function getNativeMainImageSize(nativeRequest) { return [] } -function createAdPodImp(imp, videoMediaType) { - const bce = config.getConfig('adpod.brandCategoryExclusion') - imp.video.ext = { - context: ADPOD, - brandcategoryexclusion: bce !== undefined && bce - }; - - const numberOfPlacements = getAdPodNumberOfPlacements(videoMediaType) - const imps = fill(imp, numberOfPlacements) - - const durationRangeSec = videoMediaType.durationRangeSec - if (videoMediaType.requireExactDuration) { - // equal distribution of numberOfPlacement over all available durations - const divider = Math.ceil(numberOfPlacements / durationRangeSec.length) - const chunked = chunk(imps, divider) - - // each configured duration is set as min/maxduration for a subset of requests - durationRangeSec.forEach((duration, index) => { - chunked[index].forEach(imp => { - const sequence = index + 1; - imp.video.minduration = duration - imp.video.maxduration = duration - imp.video.sequence = sequence - }); - }); - } else { - // all maxdurations should be the same - const maxDuration = Math.max(...durationRangeSec); - imps.forEach((imp, index) => { - const sequence = index + 1; - imp.video.maxduration = maxDuration - imp.video.sequence = sequence - }); - } - - return imps -} - -function getAdPodNumberOfPlacements(videoMediaType) { - const { adPodDurationSec, durationRangeSec, requireExactDuration } = videoMediaType - const minAllowedDuration = Math.min(...durationRangeSec) - const numberOfPlacements = Math.floor(adPodDurationSec / minAllowedDuration) - - return requireExactDuration - ? Math.max(numberOfPlacements, durationRangeSec.length) - : numberOfPlacements -} - -const addOptionalAdpodParameters = (videoMediaType) => { - const content = {} - - if (videoMediaType.tvSeriesName) { - content.series = videoMediaType.tvSeriesName - } - if (videoMediaType.tvEpisodeName) { - content.title = videoMediaType.tvEpisodeName - } - if (typeof videoMediaType.tvSeasonNumber === 'number') { - content.season = videoMediaType.tvSeasonNumber.toString() // conversion to string as in OpenRTB season is a string - } - if (typeof videoMediaType.tvEpisodeNumber === 'number') { - content.episode = videoMediaType.tvEpisodeNumber - } - if (typeof videoMediaType.contentLengthSec === 'number') { - content.len = videoMediaType.contentLengthSec - } - if (videoMediaType.contentMode && ['live', 'on-demand'].indexOf(videoMediaType.contentMode) >= 0) { - content.livestream = videoMediaType.contentMode === 'live' ? 1 : 0 - } - - if (!isEmpty(content)) { - return content - } -} - function getBidFloor(bidRequest, mediaType, sizes) { if (typeof bidRequest.getFloor === 'function') { const size = sizes.length === 1 ? sizes[0] : '*'; diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index a012f2ca2f4..46765cf217c 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { logError, deepAccess, @@ -21,6 +20,7 @@ import { import { VIDEO } from '../src/mediaTypes.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 15d09b2fa26..44fa0e257c7 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; 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, generateUUID } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { getViewportSize } from '../libraries/viewport/viewport.js'; +import { getDNT } from '../libraries/dnt/index.js'; const BIDDER_CODE = 'snigel'; const GVLID = 1076; diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index 9332e99a6f5..f7a6c472815 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -91,6 +91,7 @@ function createRenderer(rendererConfig) { } function outstreamRender(bid) { + // TODO this should use getAdUnitElement if (!document.getElementById(bid.adUnitCode)) { logError(`Sparteo Bid Adapter: Video renderer did not started. bidResponse.adUnitCode is probably not a DOM element : ${bid.adUnitCode}`); return; diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 7a7275d5ff2..72caeb713e4 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -6,6 +6,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { EVENT_TYPE_VIEWABLE, TRACKER_METHOD_IMG } from '../src/eventTrackers.js'; const BIDDER_CODE = 'sspBC'; const BIDDER_URL = 'https://ssp.wp.pl/bidder/'; @@ -671,8 +672,7 @@ const spec = { interpretResponse(serverResponse, request) { const { bidderRequest } = request; const { body: response = {} } = serverResponse; - const { seatbid: responseSeat, ext: responseExt = {} } = response; - const { paapi: fledgeAuctionConfigs = [] } = responseExt; + const { seatbid: responseSeat } = response; const bids = []; let site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) @@ -738,6 +738,7 @@ const spec = { }, netRevenue: true, vurls, + eventtrackers: vurls.map(url => ({ event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url })), }; // mediaType and ad data for instream / native / banner @@ -788,7 +789,7 @@ const spec = { }); } - return fledgeAuctionConfigs.length ? { bids, fledgeAuctionConfigs } : bids; + return bids; }, getUserSyncs(syncOptions, _, gdprConsent = {}) { diff --git a/modules/storageControl.ts b/modules/storageControl.ts index 188ece48107..9ee860883d2 100644 --- a/modules/storageControl.ts +++ b/modules/storageControl.ts @@ -116,7 +116,7 @@ export function storageControlRule(getEnforcement = () => enforcement, check = c const { disclosed, parent, reason } = check(params); if (disclosed === null) return; if (!disclosed) { - const enforcement = getEnforcement(); + const enforcement = getEnforcement() ?? ENFORCE_STRICT; if (enforcement === ENFORCE_STRICT || (enforcement === ENFORCE_ALIAS && !parent)) return { allow: false, reason }; if (reason) { logWarn('storageControl:', reason); @@ -125,14 +125,19 @@ export function storageControlRule(getEnforcement = () => enforcement, check = c } } -registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'storageControl', storageControlRule()); +const rule = registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'storageControl', storageControlRule()); + +export function deactivate() { + // turn off this module; should only be used in testing + rule(); +} export type StorageControlConfig = { /** - * - 'off': logs a warning when an undisclosed storage key is used - * - 'strict': deny access to undisclosed storage keys + * - 'strict': deny access to undisclosed storage keys (default) * - 'allowAliases': deny access to undisclosed storage keys, unless the use is from an alias of a module that does * disclose them + * - 'off': logs a warning when an undisclosed storage key is used */ enforcement?: typeof ENFORCE_OFF | typeof ENFORCE_ALIAS | typeof ENFORCE_STRICT; } @@ -144,7 +149,7 @@ declare module '../src/config' { } config.getConfig('storageControl', (cfg) => { - enforcement = cfg?.storageControl?.enforcement ?? ENFORCE_OFF; + enforcement = cfg?.storageControl?.enforcement ?? ENFORCE_STRICT; }) export function dynamicDisclosureCollector() { diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index f04080709a0..3a73cbbac6b 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -156,6 +156,7 @@ const isMainPageAccessible = () => { } const elementInView = (elementId) => { + // TODO this should use getAdUnitElement const resolveElement = (elId) => { const win = getWindowSelf(); diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index a82cdf0adf0..b4b04d4123a 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,7 +3,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; -import { deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject, getWinDimensions } from '../src/utils.js'; +import { + deepSetValue, + getWinDimensions, + getWindowSelf, + isPlainObject, + replaceAuctionPrice, + safeJSONParse +} from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; @@ -11,6 +18,7 @@ import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.j import { getViewportCoordinates } from '../libraries/viewport/viewport.js'; import { percentInView } from '../libraries/percentInView/percentInView.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'taboola'; const GVLID = 42; @@ -140,9 +148,9 @@ export function getDeviceExtSignals(existingExt = {}) { }; } -export function getElementSignals(adUnitCode) { +export function getElementSignals(bidRequest) { try { - const element = document.getElementById(adUnitCode); + const element = getAdUnitElement(bidRequest); if (!element) return null; const rect = getBoundingClientRect(element); @@ -247,58 +255,11 @@ export const spec = { return []; } const bids = []; - const fledgeAuctionConfigs = []; if (!serverResponse.body.seatbid || !serverResponse.body.seatbid.length || !serverResponse.body.seatbid[0].bid || !serverResponse.body.seatbid[0].bid.length) { - if (!serverResponse.body.ext || !serverResponse.body.ext.igbid || !serverResponse.body.ext.igbid.length) { - return []; - } + 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; - } - const buyerdata = safeJSONParse(igbid.igbuyer[0]?.buyerdata) - if (!buyerdata) { - return; - } - const perBuyerSignals = {}; - igbid.igbuyer.forEach(buyerItem => { - if (!buyerItem || !buyerItem.buyerdata || !buyerItem.origin) { - return; - } - const 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, - }, - }); - }); - } - - if (fledgeAuctionConfigs.length) { - return { - bids, - paapi: fledgeAuctionConfigs, - }; - } return bids; }, onBidWon: (bid) => { @@ -501,7 +462,7 @@ function fillTaboolaImpData(bid, imp, context) { deepSetValue(imp, 'ext.prebid.bidderRequestsCount', bid.bidderRequestsCount); deepSetValue(imp, 'ext.prebid.bidderWinsCount', bid.bidderWinsCount); - const elementSignals = getElementSignals(bid.adUnitCode); + const elementSignals = getElementSignals(bid); if (elementSignals) { if (elementSignals.viewability !== undefined) { deepSetValue(imp, 'ext.viewability', elementSignals.viewability); diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index 869222a6614..266a0186e30 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -1,12 +1,12 @@ 'use strict'; -import { getDNT } from '../libraries/dnt/index.js'; import { logWarn, deepAccess, isFn, isPlainObject, isBoolean, isNumber, isStr, isArray } 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 { Renderer } from '../src/Renderer.js'; import { parseDomain } from '../src/refererDetection.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 168990f84c0..4e140cb0913 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -201,7 +201,6 @@ function getSharedViewerIdParameters(validBidRequests) { id5Id: 'id5-sync.com', // id5IdSystem criteoId: 'criteo.com', // criteoIdSystem yahooConnectId: 'yahoo.com', // connectIdSystem - quantcastId: 'quantcast.com', // quantcastIdSystem epsilonPublisherLinkId: 'epsilon.com', // publinkIdSystem publisherFirstPartyViewerId: 'pubcid.org', // sharedIdSystem merkleId: 'merkleinc.com', // merkleIdSystem diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index 2d4343b329e..bac8645eb0b 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { logInfo, isEmpty, deepAccess, parseUrl, parseSizesInput, _map } from '../src/utils.js'; import { BANNER, @@ -10,6 +9,7 @@ import { } from '../src/adapters/bidderFactory.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js deleted file mode 100644 index 60eb99f8588..00000000000 --- a/modules/topLevelPaapi.js +++ /dev/null @@ -1,207 +0,0 @@ -import { submodule } from '../src/hook.js'; -import { config } from '../src/config.js'; -import { logError, logInfo, logWarn, mergeDeep } from '../src/utils.js'; -import { auctionStore } from '../libraries/weakStore/weakStore.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { emit } from '../src/events.js'; -import { BID_STATUS, EVENTS } from '../src/constants.js'; -import { PbPromise } from '../src/utils/promise.js'; -import { getBidToRender, getRenderingData, markWinningBid } from '../src/adRendering.js'; - -let getPAAPIConfig, expandFilters, moduleConfig; - -const paapiBids = auctionStore(); -const MODULE_NAME = 'topLevelPaapi'; - -config.getConfig('paapi', (cfg) => { - moduleConfig = cfg.paapi?.topLevelSeller; - if (moduleConfig) { - getBidToRender.before(renderPaapiHook); - getRenderingData.before(getRenderingDataHook); - markWinningBid.before(markWinningBidHook); - } else { - getBidToRender.getHooks({ hook: renderPaapiHook }).remove(); - getRenderingData.getHooks({ hook: getRenderingDataHook }).remove(); - markWinningBid.getHooks({ hook: markWinningBidHook }).remove(); - } -}); - -function isPaapiBid(bid) { - return bid?.source === 'paapi'; -} - -function bidIfRenderable(bid) { - if (bid && !bid.urn) { - logWarn(MODULE_NAME, 'rendering in fenced frames is not supported. Consider using resolveToConfig: false', bid); - return; - } - return bid; -} - -function renderPaapiHook(next, adId, forRender = true, cb) { - PbPromise - .resolve() - .then(() => { - const ids = parsePaapiAdId(adId); - if (ids) { - const [auctionId, adUnitCode] = ids; - return paapiBids(auctionId)?.[adUnitCode]?.then(bid => { - if (!bid) { - logWarn(MODULE_NAME, `No PAAPI bid found for auctionId: "${auctionId}", adUnit: "${adUnitCode}"`); - } - return bidIfRenderable(bid); - }); - } - }) - .then((bid) => { - if (bid != null) return bid; - return new Promise(resolve => next(adId, forRender, resolve)) - }) - .then((bid) => { - if (bid == null || isPaapiBid(bid) || bid?.status === BID_STATUS.RENDERED) return bid; - return getPAAPIBids({ adUnitCode: bid.adUnitCode }).then(res => { - const paapiBid = bidIfRenderable(res[bid.adUnitCode]); - if (paapiBid) { - if (!forRender) return paapiBid; - if (forRender && paapiBid.status !== BID_STATUS.RENDERED) { - paapiBid.overriddenAdId = bid.adId; - logInfo(MODULE_NAME, 'overriding contextual bid with PAAPI bid', bid, paapiBid) - return paapiBid; - } - } - return bid; - }); - }) - .then(cb); -} - -export function getRenderingDataHook(next, bid, options) { - if (isPaapiBid(bid)) { - next.bail({ - width: bid.width, - height: bid.height, - adUrl: bid.urn - }); - } else { - next(bid, options); - } -} - -export function markWinningBidHook(next, bid) { - if (isPaapiBid(bid)) { - emit(EVENTS.BID_WON, bid); - next.bail(); - } else { - next(bid); - } -} - -function getBaseAuctionConfig() { - if (moduleConfig?.auctionConfig) { - return Object.assign({ - resolveToConfig: false - }, moduleConfig.auctionConfig); - } -} - -function onAuctionConfig(auctionId, auctionConfigs) { - const base = getBaseAuctionConfig(); - if (base) { - Object.entries(auctionConfigs).forEach(([adUnitCode, auctionConfig]) => { - mergeDeep(auctionConfig, base); - if (moduleConfig.autorun ?? true) { - getPAAPIBids({ adUnitCode, auctionId }); - } - }); - } -} - -export function parsePaapiSize(size) { - /* From https://github.com/WICG/turtledove/blob/main/FLEDGE.md#12-interest-group-attributes: - * Each size has the format {width: widthVal, height: heightVal}, - * where the values can have either pixel units (e.g. 100 or '100px') or screen dimension coordinates (e.g. 100sw or 100sh). - */ - if (typeof size === 'number') return size; - if (typeof size === 'string') { - const px = /^(\d+)(px)?$/.exec(size)?.[1]; - if (px) { - return parseInt(px, 10); - } - } - return null; -} - -export function getPaapiAdId(auctionId, adUnitCode) { - return `paapi:/${auctionId.replace(/:/g, '::')}/:/${adUnitCode.replace(/:/g, '::')}`; -} - -export function parsePaapiAdId(adId) { - const match = /^paapi:\/(.*)\/:\/(.*)$/.exec(adId); - if (match) { - return [match[1], match[2]].map(s => s.replace(/::/g, ':')); - } -} - -/** - * Returns the PAAPI runAdAuction result for the given filters, as a map from ad unit code to auction result - * (an object with `width`, `height`, and one of `urn` or `frameConfig`). - * - * @param filters - * @param raa - * @return {Promise<{[p: string]: any}>} - */ -export function getPAAPIBids(filters, raa = (...args) => navigator.runAdAuction(...args)) { - return Promise.all( - Object.entries(expandFilters(filters)) - .map(([adUnitCode, auctionId]) => { - const bids = paapiBids(auctionId); - if (bids && !bids.hasOwnProperty(adUnitCode)) { - const auctionConfig = getPAAPIConfig({ adUnitCode, auctionId })[adUnitCode]; - if (auctionConfig) { - emit(EVENTS.RUN_PAAPI_AUCTION, { - auctionId, - adUnitCode, - auctionConfig - }); - bids[adUnitCode] = new Promise((resolve, reject) => raa(auctionConfig).then(resolve, reject)) - .then(result => { - if (result) { - const bid = { - source: 'paapi', - adId: getPaapiAdId(auctionId, adUnitCode), - width: parsePaapiSize(auctionConfig.requestedSize?.width), - height: parsePaapiSize(auctionConfig.requestedSize?.height), - adUnitCode, - auctionId, - [typeof result === 'string' ? 'urn' : 'frameConfig']: result, - auctionConfig, - }; - emit(EVENTS.PAAPI_BID, bid); - return bid; - } else { - emit(EVENTS.PAAPI_NO_BID, { auctionId, adUnitCode, auctionConfig }); - return null; - } - }).catch(error => { - logError(MODULE_NAME, `error (auction "${auctionId}", adUnit "${adUnitCode}"):`, error); - emit(EVENTS.PAAPI_ERROR, { auctionId, adUnitCode, error, auctionConfig }); - return null; - }); - } - } - return bids?.[adUnitCode]?.then(res => [adUnitCode, res]); - }).filter(e => e) - ).then(result => Object.fromEntries(result)); -} - -getGlobal().getPAAPIBids = (filters) => getPAAPIBids(filters); - -export const topLevelPAAPI = { - name: MODULE_NAME, - init(params) { - getPAAPIConfig = params.getPAAPIConfig; - expandFilters = params.expandFilters; - }, - onAuctionConfig -}; -submodule('paapi', topLevelPAAPI); diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index 4cbe600df92..a9284e08400 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -58,10 +58,6 @@ export const tripleliftAdapterSpec = { tlCall = tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } - if (bidderRequest?.paapi?.enabled) { - tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.paapi.enabled); - } - if (config.getConfig('coppa') === true) { tlCall = tryAppendQueryString(tlCall, 'coppa', true); } @@ -81,26 +77,8 @@ export const tripleliftAdapterSpec = { interpretResponse: function(serverResponse, { bidderRequest }) { let bids = serverResponse.body.bids || []; - const paapi = serverResponse.body.paapi || []; - - bids = bids.map(bid => _buildResponseObject(bidderRequest, bid)); - if (paapi.length > 0) { - const fledgeAuctionConfigs = paapi.map(config => { - return { - bidId: bidderRequest.bids[config.imp_id].bidId, - config: config.auctionConfig - }; - }); - - logMessage('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); - return { - bids, - paapi: fledgeAuctionConfigs - }; - } else { - return bids; - } + return bids.map(bid => _buildResponseObject(bidderRequest, bid)); }, getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy, gppConsent) { diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index 1da498cbe06..944c75ae616 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -3,8 +3,8 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { isNumber } from '../src/utils.js'; -import { getDNT } from '../libraries/dnt/index.js'; import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.js' +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 5b791bf6a25..1794de9ed0d 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -1,10 +1,10 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { generateUUID, _each, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 738fb22e312..d78782ad3f1 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -13,6 +13,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { isSlotMatchingAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { percentInView } from '../libraries/percentInView/percentInView.js'; import { isIframe } from '../libraries/omsUtils/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'underdogmedia'; const UDM_ADAPTER_VERSION = '7.30V'; @@ -109,7 +110,7 @@ export const spec = { sizes = flatten(sizes, parseSizesInput(bidParamSizes)); siteId = +bidParam.params.siteId; const adUnitCode = bidParam.adUnitCode - const element = _getAdSlotHTMLElement(adUnitCode) + const element = _getAdSlotHTMLElement(bidParam) const minSize = _getMinSize(bidParamSizes) placementObject.sizes = parseSizesInput(bidParamSizes) @@ -234,9 +235,9 @@ function _getMinSize(bidParamSizes) { return bidParamSizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min) } -function _getAdSlotHTMLElement(adUnitCode) { - return document.getElementById(adUnitCode) || - document.getElementById(_mapAdUnitPathToElementId(adUnitCode)); +function _getAdSlotHTMLElement(bidRequest) { + return getAdUnitElement(bidRequest) || + document.getElementById(_mapAdUnitPathToElementId(bidRequest.adUnitCode)); } function _mapAdUnitPathToElementId(adUnitCode) { diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index fbfd236e91f..e1da10b1da0 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -7,6 +7,7 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC import { getViewportCoordinates } from '../libraries/viewport/viewport.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'undertone'; const URL = 'https://hb.undertone.com/hb'; @@ -38,8 +39,8 @@ function getGdprQueryParams(gdprConsent) { return `gdpr=${gdpr}&gdprstr=${gdprstr}`; } -function getBannerCoords(id) { - const element = document.getElementById(id); +function getBannerCoords(bidRequest) { + const element = getAdUnitElement(bidRequest); if (element) { const { left, top } = getBoundingClientRect(element); const viewport = getViewportCoordinates(); @@ -109,7 +110,7 @@ export const spec = { validBidRequests.forEach(bidReq => { const bid = { bidRequestId: bidReq.bidId, - coordinates: getBannerCoords(bidReq.adUnitCode), + coordinates: getBannerCoords(bidReq), hbadaptor: 'prebid', url: pageUrl, domain: domain, diff --git a/modules/userId/userId.md b/modules/userId/userId.md index f7bea8fd9f8..328e23d96b0 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -26,16 +26,6 @@ pbjs.setConfig({ name: "_pubcid", expires: 60 } - }, { - name: 'dmdId', - storage: { - name: 'dmd-dgid', - type: 'cookie', - expires: 30 - }, - params: { - api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by DMD - } }, { name: "unifiedId", params: { diff --git a/modules/valuadBidAdapter.js b/modules/valuadBidAdapter.js index ec969ab719d..31b320c2557 100644 --- a/modules/valuadBidAdapter.js +++ b/modules/valuadBidAdapter.js @@ -12,6 +12,7 @@ import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { config } from '../src/config.js'; import { getBoundingBox, percentInView } from '../libraries/percentInView/percentInView.js'; import { isIframe } from '../libraries/omsUtils/index.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'valuad'; const GVL_ID = 1478; @@ -106,7 +107,7 @@ const converter = ortbConverter({ const size = { w: adSize[0], h: adSize[1] }; - const element = document.getElementById(bid.adUnitCode) || document.getElementById(getGptSlotInfoForAdUnitCode(bid.adUnitCode)?.divId); + const element = getAdUnitElement(bid) || document.getElementById(getGptSlotInfoForAdUnitCode(bid.adUnitCode)?.divId); const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, getWindowTop(), size) : 0; const rect = element && getBoundingBox(element, size); diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index f75fc1fe4ef..bc7f72833f3 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -7,6 +7,7 @@ import { getStorageManager } from '../src/storageManager.js'; import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; import { getBidFromResponse } from '../libraries/processResponse/index.js'; import { getCurrencyFromBidderRequest } from '../libraries/ortb2Utils/currency.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'visx'; const GVLID = 154; @@ -295,7 +296,7 @@ function makeVideo(videoParams = {}) { } function buildImpObject(bid) { - const { params: { uid }, bidId, mediaTypes, sizes, adUnitCode } = bid; + const { params: { uid }, bidId, mediaTypes, sizes } = bid; const video = mediaTypes && _isVideoBid(bid) && _isValidVideoBid(bid) && makeVideo(mediaTypes.video); const banner = makeBanner((mediaTypes && mediaTypes.banner) || (!video && { sizes })); const impObject = { @@ -308,7 +309,7 @@ function buildImpObject(bid) { }; if (impObject.banner) { - impObject.ext.bidder.adslotExists = _isAdSlotExists(adUnitCode); + impObject.ext.bidder.adslotExists = _isAdSlotExists(bid); } if (bid.ortb2Imp?.ext?.gpid) { @@ -410,12 +411,12 @@ function _isValidVideoBid(bid, logErrors = false) { return result; } -function _isAdSlotExists(adUnitCode) { - if (document.getElementById(adUnitCode)) { +function _isAdSlotExists(bidRequest) { + if (getAdUnitElement(bidRequest)) { return true; } - const gptAdSlot = getGptSlotInfoForAdUnitCode(adUnitCode); + const gptAdSlot = getGptSlotInfoForAdUnitCode(bidRequest.adUnitCode); if (gptAdSlot.divId && document.getElementById(gptAdSlot.divId)) { return true; } diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index 498cf5ba7ea..51bd244009d 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -4,6 +4,7 @@ import { deepClone, parseQueryStringParameters, parseSizesInput } from '../src/u import { getStorageManager } from '../src/storageManager.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; import { getConnectionInfo } from '../libraries/connectionInfo/connectionUtils.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; const BIDDER_CODE = 'widespace'; const WS_ADAPTER_VERSION = '2.0.1'; @@ -53,7 +54,7 @@ export const spec = { 'inFrame': 1, 'sid': bid.params.sid, 'lcuid': LC_UID, - 'vol': isInHostileIframe ? '' : visibleOnLoad(document.getElementById(bid.adUnitCode)), + 'vol': isInHostileIframe ? '' : visibleOnLoad(getAdUnitElement(bid)), 'gdprCmp': bidderRequest && bidderRequest.gdprConsent ? 1 : 0, 'hb': '1', 'hb.cd': CUST_DATA ? encodedParamValue(CUST_DATA) : '', diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index 901bbcf29de..910a8e75a87 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -7,6 +7,7 @@ import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingC import { ajax } from '../src/ajax.js'; import { config as pbjsConfig } from '../src/config.js'; import { isWebdriverEnabled } from '../libraries/webdriver/webdriver.js'; +import { getAdUnitElement } from '../src/utils/adUnits.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -183,7 +184,7 @@ export const spec = { queryParams['tcf-consent'] = consentString; } - const adUnitElement = document.getElementById(bidRequest.params.pubcontainerid || bidRequest.adUnitCode); + const adUnitElement = bidRequest.params.pubcontainerid ? document.getElementById(bidRequest.params.pubcontainerid) : getAdUnitElement(bidRequest); const windowContext = getContext(adUnitElement); const isIframe = inIframe(); const coords = isIframe ? getFramePosition() : { diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index eb3ee1de1da..89502f490c3 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -1,4 +1,3 @@ -import { getDNT } from '../libraries/dnt/index.js'; import { deepAccess, deepSetValue, @@ -18,6 +17,7 @@ import { import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; +import { getDNT } from '../libraries/dnt/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest diff --git a/scope3_segtax_pr.md b/scope3_segtax_pr.md deleted file mode 100644 index 7589507f8a4..00000000000 --- a/scope3_segtax_pr.md +++ /dev/null @@ -1,35 +0,0 @@ -# Proposed Addition to segtax.md - -Add this line to the "Vendor-specific Taxonomies" section (in numerical order): - -``` -604: Scope3 Agentic Execution Engine (AEE) Targeting Signals -``` - -## PR Description Template: - -**Title:** Add Scope3 AEE Targeting Signals Taxonomy (segtax ID 604) - -**Description:** - -This PR registers Scope3's Agentic Execution Engine (AEE) targeting signal taxonomy for use in OpenRTB segment data. - -**Details:** -- **Taxonomy ID:** 604 -- **Name:** Scope3 Agentic Execution Engine (AEE) Targeting Signals -- **Purpose:** Identifies proprietary targeting signals generated by Scope3's AEE for real-time programmatic optimization -- **Usage:** These are opaque targeting codes (e.g., "x82s", "a91k") used for line item targeting decisions, not traditional audience segments - -**Contact:** [Your email] - -cc: @bretg @slimkrazy (as listed approvers in the document) - -## Alternative Higher ID: - -If you want to avoid any potential conflicts with IDs in the 600s range, you could use: - -``` -1001: Scope3 Agentic Execution Engine (AEE) Targeting Signals -``` - -This would put you well clear of any existing entries while still in the vendor-specific range. \ No newline at end of file diff --git a/src/adRendering.ts b/src/adRendering.ts index dc214734dd9..c7b02468f7d 100644 --- a/src/adRendering.ts +++ b/src/adRendering.ts @@ -56,15 +56,6 @@ declare module './events' { } } -/** - * NOTE: this is here to support PAAPI, which is soon to be removed; - * and should *not* be made asynchronous or it breaks `legacyRender` (unyielding) - * rendering logic - */ -export const getBidToRender = hook('sync', function (adId, forRender, cb) { - cb(auctionManager.findBidByAdId(adId)); -}) - export const markWinningBid = hook('sync', function (bid) { (parseEventTrackers(bid.eventtrackers)[EVENT_TYPE_WIN]?.[TRACKER_METHOD_IMG] || []) .forEach(url => triggerPixel(url)); @@ -399,10 +390,8 @@ export const renderAdDirect = yieldsIf(() => !legacyRender, function renderAdDir if (!adId || !doc) { fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { - getBidToRender(adId, true, (bidResponse) => { - bid = bidResponse; - handleRender({ renderFn, resizeFn, adId, options: { clickUrl: options?.clickThrough }, bidResponse, doc }); - }); + bid = auctionManager.findBidByAdId(adId) + handleRender({ renderFn, resizeFn, adId, options: { clickUrl: options?.clickThrough }, bidResponse: bid, doc }); } } catch (e) { fail(EXCEPTION, e.message); diff --git a/src/adUnits.ts b/src/adUnits.ts index a1aced172ef..bdc86ee33b5 100644 --- a/src/adUnits.ts +++ b/src/adUnits.ts @@ -88,6 +88,11 @@ export interface AdUnitDefinition { * Used by setTargetingForGPTAsync() to match which auction is for which ad slot. */ code: AdUnitCode; + /** + * A DOM element corresponding to this ad unit. + * By default, this is `document.getElementById(adUnit.code)`. + */ + element?: HTMLElement; /** * Bid requests representing demand partners and associated parameters. */ diff --git a/src/adapterManager.ts b/src/adapterManager.ts index 81c63be66bc..328ab57335d 100644 --- a/src/adapterManager.ts +++ b/src/adapterManager.ts @@ -214,6 +214,7 @@ const ADUNIT_BID_PROPERTIES = [ 'nativeParams', 'nativeOrtbRequest', 'renderer', + 'element', ] as const; type GetBidsOptions = { diff --git a/src/adapters/bidderFactory.ts b/src/adapters/bidderFactory.ts index 0376e3a0607..737c26117e1 100644 --- a/src/adapters/bidderFactory.ts +++ b/src/adapters/bidderFactory.ts @@ -279,11 +279,11 @@ export function newBidder(spec: BidderSpec) { const tidGuard = guardTids(bidderRequest); const adUnitCodesHandled = {}; - function addBidWithCode(adUnitCode: string, bid: Bid) { + function addBidWithCode(adUnitCode: string, bid: Bid, responseMediaType = null) { const metrics = useMetrics(bid.metrics); metrics.checkpoint('addBidResponse'); adUnitCodesHandled[adUnitCode] = true; - if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid))) { + if (metrics.measureTime('addBidResponse.validate', () => isValid(adUnitCode, bid, { responseMediaType }))) { addBidResponse(adUnitCode, bid); } else { addBidResponse.reject(adUnitCode, bid, REJECTION_REASON.INVALID) @@ -319,14 +319,6 @@ export function newBidder(spec: BidderSpec) { onTimelyResponse(spec.code); responses.push(resp) }, - onPaapi: (paapiConfig: any) => { - const bidRequest = bidRequestMap[paapiConfig.bidId]; - if (bidRequest) { - addPaapiConfig(bidRequest, paapiConfig); - } 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) => { if (!error.timedOut) { @@ -353,7 +345,10 @@ export function newBidder(spec: BidderSpec) { bid.deferBilling = bidRequest.deferBilling; bid.deferRendering = bid.deferBilling && (bidResponse.deferRendering ?? typeof spec.onBidBillable !== 'function'); const prebidBid: Bid = Object.assign(createBid(bidRequest), bid, pick(bidRequest, Object.keys(TIDS))); - addBidWithCode(bidRequest.adUnitCode, prebidBid); + const responseMediaType = Object.prototype.hasOwnProperty.call(bidResponse, 'mediaType') + ? bidResponse.mediaType + : null; + addBidWithCode(bidRequest.adUnitCode, prebidBid, responseMediaType); } else { logWarn(`Bidder ${spec.code} made bid for unknown request ID: ${bidResponse.requestId}. Ignoring.`); addBidResponse.reject(null, bidResponse, REJECTION_REASON.INVALID_REQUEST_ID); @@ -390,7 +385,12 @@ export function newBidder(spec: BidderSpec) { } } -const RESPONSE_PROPS = ['bids', 'paapi'] +const RESPONSE_PROPS = [ + 'bids', + // allow bid adapters to still reply with paapi (which will be ignored). + 'paapi', +] + /** * Run a set of bid requests - that entails converting them to HTTP requests, sending * them over the network, and parsing the responses. @@ -407,7 +407,7 @@ export const processBidderRequests = hook('async', function, ajax: Ajax, wrapCallback: (fn: T) => Wraps, - { onRequest, onResponse, onPaapi, onError, onBid, onCompletion }: { + { onRequest, onResponse, onError, onBid, onCompletion }: { /** * invoked once for each HTTP request built by the adapter - with the raw request */ @@ -424,10 +424,6 @@ export const processBidderRequests = hook('async', function void; - /** - * invoked once with each member of the adapter response's 'paapi' array. - */ - onPaapi: (paapi: unknown) => void; /** * invoked once when all bid requests have been processed */ @@ -483,16 +479,12 @@ export const processBidderRequests = hook('async', function !RESPONSE_PROPS.includes(key))) { bids = response.bids; - paapiConfigs = response.paapi; } else { bids = response; } - if (isArray(paapiConfigs)) { - paapiConfigs.forEach(onPaapi); - } if (bids) { if (isArray(bids)) { bids.forEach(addBid); @@ -616,9 +608,6 @@ export const registerSyncInner = hook('async', function(spec: BidderSpec { -}, 'addPaapiConfig'); - declare module '../bidfactory' { interface BannerBidProperties { width?: number; @@ -661,7 +650,7 @@ function validBidSize(adUnitCode, bid: BannerBid, { index = auctionManager.index } // Validate the arguments sent to us by the adapter. If this returns false, the bid should be totally ignored. -export function isValid(adUnitCode: string, bid: Bid, { index = auctionManager.index } = {}) { +export function isValid(adUnitCode: string, bid: Bid, { index = auctionManager.index, responseMediaType = bid.mediaType } = {}) { function hasValidKeys() { const bidKeys = Object.keys(bid); return COMMON_BID_RESPONSE_KEYS.every(key => bidKeys.includes(key) && ![undefined, null].includes(bid[key])); @@ -686,6 +675,21 @@ export function isValid(adUnitCode: string, bid: Bid, { index = auctionManager.i return false; } + const auctionOptions = config.getConfig('auctionOptions') || {}; + const rejectUnknownMediaTypes = auctionOptions.rejectUnknownMediaTypes === true; + const rejectInvalidMediaTypes = auctionOptions.rejectInvalidMediaTypes !== false; + const mediaTypes = index.getMediaTypes(bid); + if (mediaTypes && Object.keys(mediaTypes).length > 0) { + if (responseMediaType == null && rejectUnknownMediaTypes) { + logError(errorMessage(`Bid mediaType is required. Allowed: ${Object.keys(mediaTypes).join(', ')}`)); + return false; + } + if (responseMediaType != null && rejectInvalidMediaTypes && !mediaTypes.hasOwnProperty(responseMediaType)) { + logError(errorMessage(`Bid mediaType '${responseMediaType}' is not supported by the ad unit. Allowed: ${Object.keys(mediaTypes).join(', ')}`)); + return false; + } + } + if (FEATURES.NATIVE && bid.mediaType === 'native' && !nativeBidIsValid(bid, { index })) { logError(errorMessage('Native bid missing some required properties.')); return false; diff --git a/src/ajax.ts b/src/ajax.ts index 986369c7a0f..a1eda919d42 100644 --- a/src/ajax.ts +++ b/src/ajax.ts @@ -53,10 +53,6 @@ export interface AjaxOptions { * Whether chrome's `Sec-Browing-Topics` header should be sent */ browsingTopics?: boolean - /** - * Whether chrome's PAAPI headers should be sent. - */ - adAuctionHeaders?: boolean; /** * If true, suppress warnings */ @@ -94,8 +90,8 @@ export function toFetchRequest(url, data, options: AjaxOptions = {}) { rqOpts.credentials = 'include'; } if (isSecureContext) { - ['browsingTopics', 'adAuctionHeaders'].forEach(opt => { - // the Request constructor will throw an exception if the browser supports topics/fledge + ['browsingTopics'].forEach(opt => { + // the Request constructor will throw an exception if the browser supports topics // but we're not in a secure context if (options[opt]) { rqOpts[opt] = true; diff --git a/src/auction.ts b/src/auction.ts index 8130d37e590..6a7a9643938 100644 --- a/src/auction.ts +++ b/src/auction.ts @@ -33,7 +33,7 @@ import { getMinBidCacheTTL, onMinBidCacheTTLChange } from './bidTTL.js'; import type { Bid, BidResponse } from "./bidfactory.ts"; import type { AdUnitCode, BidderCode, Identifier, ORTBFragments } from './types/common.d.ts'; import type { TargetingMap } from "./targeting.ts"; -import type { AdUnit } from "./adUnits.ts"; +import type { AdUnit, AdUnitDefinition } from "./adUnits.ts"; import type { MediaType } from "./mediaTypes.ts"; import type { VideoContext } from "./video.ts"; import { isActivityAllowed } from './activities/rules.js'; @@ -100,10 +100,6 @@ declare module './events' { * Fired when an auction times out. */ [EVENTS.BID_TIMEOUT]: [BidRequest[]]; - /** - * Fired when a bid is received. - */ - [EVENTS.BID_ACCEPTED]: [Partial]; /** * Fired when a bid is rejected. */ @@ -147,6 +143,18 @@ export interface AuctionOptionsConfig { * to pre-10.12 rendering logic. */ legacyRender?: boolean; + + /** + * When true, reject bids without a response `mediaType` when the ad unit has an explicit mediaTypes list. + * Default is false to preserve legacy behavior for responses that omit mediaType. + */ + rejectUnknownMediaTypes?: boolean; + + /** + * When true, reject bids with a response `mediaType` that does not match the ad unit's explicit mediaTypes list. + * Default is true; set to false to keep mismatched mediaType responses. + */ + rejectInvalidMediaTypes?: boolean; } export interface PriceBucketConfig { @@ -428,8 +436,8 @@ export function newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels, adapterManager.callSetTargetingBidder(bid.adapterCode || bid.bidder, bid); } - events.on(EVENTS.SEAT_NON_BID, (event) => { - if (event.auctionId === _auctionId) { + events.on(EVENTS.PBS_ANALYTICS, (event) => { + if (event.auctionId === _auctionId && event.seatnonbid != null) { addNonBids(event.seatnonbid) } }); @@ -530,7 +538,6 @@ export function auctionCallbacks(auctionDone, auctionInstance, { index = auction function acceptBidResponse(adUnitCode: string, bid: Partial) { handleBidResponse(adUnitCode, bid, (done) => { const bidResponse = getPreparedBidForAuction(bid); - events.emit(EVENTS.BID_ACCEPTED, bidResponse); if ((FEATURES.VIDEO && bidResponse.mediaType === VIDEO) || (FEATURES.AUDIO && bidResponse.mediaType === AUDIO)) { tryAddVideoAudioBid(auctionInstance, bidResponse, done); } else { @@ -666,6 +673,7 @@ declare module './bidfactory' { } interface BaseBid { + element?: AdUnitDefinition['element']; /** * true if this bid is for an interstitial slot. */ @@ -775,6 +783,7 @@ function getPreparedBidForAuction(bid: Partial, { index = auctionManager.in const adUnit = index.getAdUnit(bid); bid.instl = adUnit?.ortb2Imp?.instl === 1; + bid.element = adUnit?.element; // a publisher-defined renderer can be used to render bids const bidRenderer = index.getBidRequest(bid)?.renderer || adUnit.renderer; diff --git a/src/config.ts b/src/config.ts index 01874d74329..edb30c14c12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -64,7 +64,7 @@ function attachProperties(config, useDefaultValues = true) { } : {} const validateauctionOptions = (() => { - const boolKeys = ['secondaryBidders', 'suppressStaleRender', 'suppressExpiredRender', 'legacyRender']; + const boolKeys = ['suppressStaleRender', 'suppressExpiredRender', 'legacyRender', 'rejectUnknownMediaTypes', 'rejectInvalidMediaTypes']; const arrKeys = ['secondaryBidders'] const allKeys = [].concat(boolKeys).concat(arrKeys); diff --git a/src/constants.ts b/src/constants.ts index 6380d89f712..5c4c0a6f00f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,7 +25,6 @@ export const EVENTS = { BID_RESPONSE: 'bidResponse', BID_REJECTED: 'bidRejected', NO_BID: 'noBid', - SEAT_NON_BID: 'seatNonBid', BID_WON: 'bidWon', BIDDER_DONE: 'bidderDone', BIDDER_ERROR: 'bidderError', @@ -33,7 +32,6 @@ export const EVENTS = { 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', @@ -42,12 +40,7 @@ export const EVENTS = { STALE_RENDER: 'staleRender', EXPIRED_RENDER: 'expiredRender', BILLABLE_EVENT: 'billableEvent', - BID_ACCEPTED: 'bidAccepted', - RUN_PAAPI_AUCTION: 'paapiRunAuction', PBS_ANALYTICS: 'pbsAnalytics', - PAAPI_BID: 'paapiBid', - PAAPI_NO_BID: 'paapiNoBid', - PAAPI_ERROR: 'paapiError', BEFORE_PBS_HTTP: 'beforePBSHttp', BROWSI_INIT: 'browsiInit', BROWSI_DATA: 'browsiData', diff --git a/src/eventTrackers.js b/src/eventTrackers.js index 561084a1a4a..306db1fe300 100644 --- a/src/eventTrackers.js +++ b/src/eventTrackers.js @@ -2,6 +2,7 @@ export const TRACKER_METHOD_IMG = 1; export const TRACKER_METHOD_JS = 2; export const EVENT_TYPE_IMPRESSION = 1; export const EVENT_TYPE_WIN = 500; +export const EVENT_TYPE_VIEWABLE = 2; /** * Returns a map from event type (EVENT_TYPE_*) diff --git a/src/fpd/enrichment.ts b/src/fpd/enrichment.ts index 107d98d9667..4d3f8327e63 100644 --- a/src/fpd/enrichment.ts +++ b/src/fpd/enrichment.ts @@ -12,7 +12,6 @@ import { mergeDeep, memoize } from '../utils.js'; -import { getDNT } from '../../libraries/dnt/index.js'; import { config } from '../config.js'; import { getHighEntropySUA, getLowEntropySUA } from './sua.js'; import { PbPromise } from '../utils/promise.js'; @@ -158,7 +157,6 @@ const ENRICHMENTS = { const device = { w, h, - dnt: getDNT() ? 1 : 0, ua: win.navigator.userAgent, language: win.navigator.language.split('-').shift(), ext: { diff --git a/src/native.ts b/src/native.ts index f9e05d9ed34..c8e27fc6fcc 100644 --- a/src/native.ts +++ b/src/native.ts @@ -23,7 +23,7 @@ import { import { NATIVE } from './mediaTypes.js'; import { getRenderingData } from './adRendering.js'; import { getCreativeRendererSource, PUC_MIN_VERSION } from './creativeRenderers.js'; -import { EVENT_TYPE_IMPRESSION, parseEventTrackers, TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from './eventTrackers.js'; +import { EVENT_TYPE_IMPRESSION, EVENT_TYPE_VIEWABLE, parseEventTrackers, TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from './eventTrackers.js'; import type { Link, NativeRequest, NativeResponse } from "./types/ortb/native.d.ts"; import type { Size } from "./types/common.d.ts"; import type { Ext } from "./types/ortb/common.d.ts"; @@ -350,14 +350,15 @@ export function fireNativeTrackers(message, bidResponse) { if (message.action === 'click') { fireClickTrackers(nativeResponse, message?.assetId); } else { - fireImpressionTrackers(nativeResponse); + fireImpressionTrackers(nativeResponse, bidResponse); } return message.action; } -export function fireImpressionTrackers(nativeResponse, { runMarkup = (mkup) => insertHtmlIntoIframe(mkup), fetchURL = triggerPixel } = {}) { +export function fireImpressionTrackers(nativeResponse, bidResponse, { runMarkup = (mkup) => insertHtmlIntoIframe(mkup), fetchURL = triggerPixel } = {}) { + const filteredEventTrackers = filterEventTrackers(nativeResponse, bidResponse); let { [TRACKER_METHOD_IMG]: img = [], [TRACKER_METHOD_JS]: js = [] } = parseEventTrackers( - nativeResponse.eventtrackers || [] + filteredEventTrackers || [] )[EVENT_TYPE_IMPRESSION] || {}; if (nativeResponse.imptrackers) { @@ -398,6 +399,20 @@ export function fireClickTrackers(nativeResponse, assetId = null, { fetchURL = t } } +export function filterEventTrackers(nativeResponse, bid) { + const DEFAULT_ALLOWED_TRACKERS = [ + { event: EVENT_TYPE_IMPRESSION, methods: [TRACKER_METHOD_IMG, TRACKER_METHOD_JS] }, + { event: EVENT_TYPE_VIEWABLE, methods: [TRACKER_METHOD_IMG, TRACKER_METHOD_JS] }, + ]; + const mediaTypes = auctionManager.index.getMediaTypes(bid) || {}; + const nativeMediaType = mediaTypes.native || {}; + const requestEventTrackers = nativeMediaType.ortb?.eventtrackers || DEFAULT_ALLOWED_TRACKERS; + + const { eventtrackers = [] } = nativeResponse || {}; + + return eventtrackers.filter(tracker => requestEventTrackers.some(requestTracker => requestTracker.event === tracker.event && requestTracker.methods.includes(tracker.method))); +} + export function setNativeResponseProperties(bid, adUnit) { const nativeOrtbRequest = adUnit?.nativeOrtbRequest; const nativeOrtbResponse = bid.native?.ortb; diff --git a/src/prebid.ts b/src/prebid.ts index 5d37a6dcce8..c2d21b9c4eb 100644 --- a/src/prebid.ts +++ b/src/prebid.ts @@ -28,7 +28,7 @@ import { listenMessagesFromCreative } from './secureCreatives.js'; import { userSync } from './userSync.js'; import { config } from './config.js'; import { auctionManager } from './auctionManager.js'; -import { isBidUsable, type SlotMatchingFn, targeting } from './targeting.js'; +import { isBidUsable, targeting } from './targeting.js'; import { hook, wrapHook } from './hook.js'; import { loadSession } from './debugging.js'; import { storageCallbacks } from './storageManager.js'; @@ -70,7 +70,7 @@ const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; /* private variables */ -const { ADD_AD_UNITS, REQUEST_BIDS, SET_TARGETING } = EVENTS; +const { REQUEST_BIDS, SET_TARGETING } = EVENTS; // initialize existing debugging sessions if present loadSession(); @@ -610,14 +610,13 @@ addApiMethod('getBidResponsesForAdUnitCode', getBidResponsesForAdUnitCode); /** * Set query string targeting on one or more GPT ad units. * @param adUnit a single `adUnit.code` or multiple. - * @param customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; */ -function setTargetingForGPTAsync(adUnit?: AdUnitCode | AdUnitCode[], customSlotMatching?: SlotMatchingFn) { +function setTargetingForGPTAsync(adUnit?: AdUnitCode | AdUnitCode[]) { if (!isGptPubadsDefined()) { logError('window.googletag is not defined on the page'); return; } - targeting.setTargetingForGPT(adUnit, customSlotMatching); + targeting.setTargetingForGPT(adUnit); } addApiMethod('setTargetingForGPTAsync', setTargetingForGPTAsync); @@ -954,21 +953,12 @@ export function executeCallbacks(fn, reqBidsConfigObj) { // This hook will execute all storage callbacks which were registered before gdpr enforcement hook was added. Some bidders, user id modules use storage functions when module is parsed but gdpr enforcement hook is not added at that stage as setConfig callbacks are yet to be called. Hence for such calls we execute all the stored callbacks just before requestBids. At this hook point we will know for sure that tcfControl module is added or not requestBids.before(executeCallbacks, 49); -declare module './events' { - interface Events { - /** - * Fired when `.addAdUniuts` is called. - */ - [ADD_AD_UNITS]: []; - } -} /** * Add ad unit(s) * @param adUnits */ function addAdUnits(adUnits: AdUnitDefinition | AdUnitDefinition[]) { pbjsInstance.adUnits.push(...(Array.isArray(adUnits) ? adUnits : [adUnits])) - events.emit(ADD_AD_UNITS); } addApiMethod('addAdUnits', addAdUnits); diff --git a/src/secureCreatives.js b/src/secureCreatives.js index cbcf8e85bdd..4c81217b215 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -8,7 +8,6 @@ import { BID_STATUS, MESSAGES } from './constants.js'; import { isApnGetTagDefined, isGptPubadsDefined, logError, logWarn } from './utils.js'; import { deferRendering, - getBidToRender, handleCreativeEvent, handleNativeMessage, handleRender, @@ -16,6 +15,8 @@ import { } from './adRendering.js'; import { getCreativeRendererSource, PUC_MIN_VERSION } from './creativeRenderers.js'; import { PbPromise } from './utils/promise.js'; +import { getAdUnitElement } from './utils/adUnits.js'; +import { auctionManager } from './auctionManager.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; @@ -70,10 +71,8 @@ export function receiveMessage(ev, cb) { } if (data && data.adId && data.message && HANDLER_MAP.hasOwnProperty(data.message)) { - return getBidToRender(data.adId, data.message === MESSAGES.REQUEST, (adObject) => { - HANDLER_MAP[data.message](ensureAdId(data.adId, getReplier(ev)), data, adObject); - cb && cb(); - }); + HANDLER_MAP[data.message](ensureAdId(data.adId, getReplier(ev)), data, auctionManager.findBidByAdId(data.adId)); + cb && cb(); } } @@ -167,7 +166,7 @@ export function resizeAnchor(ins, width, height) { }) } -export function resizeRemoteCreative({ instl, adId, adUnitCode, width, height }) { +export function resizeRemoteCreative({ instl, element, adId, adUnitCode, width, height }) { // do not resize interstitials - the creative frame takes the full screen and sizing of the ad should // be handled within it. if (instl) return; @@ -190,7 +189,7 @@ export function resizeRemoteCreative({ instl, adId, adUnitCode, width, height }) function getElementByAdUnit(elmType) { const id = getElementIdBasedOnAdServer(adId, adUnitCode); - const parentDivEle = document.getElementById(id); + const parentDivEle = id == null ? getAdUnitElement({ element, adUnitCode }) : document.getElementById(id); return parentDivEle && parentDivEle.querySelector(elmType); } @@ -207,13 +206,16 @@ export function resizeRemoteCreative({ instl, adId, adUnitCode, width, height }) return apnId; } } - return adUnitCode; } function getDfpElementId(adId) { const slot = window.googletag.pubads().getSlots().find(slot => { - return slot.getTargetingKeys().find(key => { - return slot.getTargeting(key).includes(adId); + const targetingMap = slot.getConfig('targeting'); + const keys = Object.keys(targetingMap); + + return keys.find(key => { + const values = targetingMap[key]; + return values.includes(adId); }); }); return slot ? slot.getSlotElementId() : null; diff --git a/src/targeting.ts b/src/targeting.ts index 371436e614a..d035bb1713e 100644 --- a/src/targeting.ts +++ b/src/targeting.ts @@ -5,7 +5,6 @@ import { config } from './config.js'; import { BID_STATUS, DEFAULT_TARGETING_KEYS, EVENTS, JSON_MAPPING, TARGETING_KEYS } from './constants.js'; import * as events from './events.js'; import { hook } from './hook.js'; -import { ADPOD } from './mediaTypes.js'; import { deepAccess, deepClone, @@ -133,13 +132,12 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { * Return a map where each code in `adUnitCodes` maps to a list of GPT slots that match it. * * @param adUnitCodes - * @param customSlotMatching * @param getSlots */ -export function getGPTSlotsForAdUnits(adUnitCodes: AdUnitCode[], customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()): ByAdUnit { +export function getGPTSlotsForAdUnits(adUnitCodes: AdUnitCode[], getSlots = () => window.googletag.pubads().getSlots()): ByAdUnit { return getSlots().reduce((auToSlots, slot) => { - const customMatch = isFn(customSlotMatching) && customSlotMatching(slot); - Object.keys(auToSlots).filter(isFn(customMatch) ? customMatch : isAdUnitCodeMatchingSlot(slot)).forEach(au => auToSlots[au].push(slot)); + Object.keys(auToSlots).filter(isAdUnitCodeMatchingSlot(slot)) + .forEach(au => auToSlots[au].push(slot)); return auToSlots; }, Object.fromEntries(adUnitCodes.map(au => [au, []]))); } @@ -306,13 +304,13 @@ export function newTargeting(auctionManager) { return flatTargeting; }, - setTargetingForGPT: hook('sync', function (adUnit?: AdUnitCode | AdUnitCode[], customSlotMatching?: SlotMatchingFn) { + setTargetingForGPT: hook('sync', function (adUnit?: AdUnitCode | AdUnitCode[]) { // get our ad unit codes const targetingSet: ByAdUnit = targeting.getAllTargeting(adUnit); const resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); - Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { + Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet))).forEach(([targetId, slots]) => { slots.forEach(slot => { // now set new targeting keys Object.keys(targetingSet[targetId]).forEach(key => { @@ -324,7 +322,8 @@ export function newTargeting(auctionManager) { targetingSet[targetId][key] = value; }); logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingSet[targetId]); - slot.updateTargetingFromMap(Object.assign({}, resetMap, targetingSet[targetId])) + const targetingMap = Object.assign({}, resetMap, targetingSet[targetId]); + slot.setConfig({ targeting: targetingMap } as any); lock.lock(targetingSet[targetId]); }) }) @@ -678,7 +677,7 @@ export function newTargeting(auctionManager) { const cacheFilter = bidCacheEnabled || isBidFromLastAuction; const bidFilter = cacheFilter && filterFunctionResult; - if (bidFilter && bid?.video?.context !== ADPOD && isBidUsable(bid)) { + if (bidFilter && isBidUsable(bid)) { bid.latestTargetedAuctionId = latestAuctionForAdUnit[bid.adUnitCode]; bids.push(bid) } diff --git a/src/utils.js b/src/utils.js index f9ca35de8a7..65cb3713cce 100644 --- a/src/utils.js +++ b/src/utils.js @@ -772,7 +772,7 @@ export function groupBy(xs, key) { */ export function isValidMediaTypes(mediaTypes) { const SUPPORTED_MEDIA_TYPES = ['banner', 'native', 'video', 'audio']; - const SUPPORTED_STREAM_TYPES = ['instream', 'outstream', 'adpod']; + const SUPPORTED_STREAM_TYPES = ['instream', 'outstream']; const types = Object.keys(mediaTypes); @@ -810,7 +810,9 @@ export const compareCodeAndSlot = (slot, adUnitCode) => slot.getAdUnitPath() === * @return filter function */ export function isAdUnitCodeMatchingSlot(slot) { - return (adUnitCode) => compareCodeAndSlot(slot, adUnitCode); + const customGptSlotMatching = config.getConfig('customGptSlotMatching'); + const match = isFn(customGptSlotMatching) && customGptSlotMatching(slot); + return isFn(match) ? match : (adUnitCode) => compareCodeAndSlot(slot, adUnitCode); } /** diff --git a/src/utils/adUnits.ts b/src/utils/adUnits.ts new file mode 100644 index 00000000000..d9f4196e19c --- /dev/null +++ b/src/utils/adUnits.ts @@ -0,0 +1,21 @@ +import type { AdUnitDefinition } from "../adUnits.ts"; +import type { BidRequest } from "../adapterManager.ts"; +import type { Bid } from "../bidfactory.ts"; + +export function getAdUnitElement(bidRequest: BidRequest): HTMLElement +export function getAdUnitElement(bidResponse: Bid): HTMLElement +export function getAdUnitElement(adUnit: AdUnitDefinition): HTMLElement +export function getAdUnitElement(target: { + code?: string, + adUnitCode?: string, + element?: HTMLElement +}): HTMLElement | null { + if (target.element != null) { + return target.element; + } + const id = target.adUnitCode ?? target.code; + if (id) { + return document.getElementById(id); + } + return null; +} diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js index 388ea2c5e2d..58151526325 100644 --- a/test/spec/auctionmanager_spec.js +++ b/test/spec/auctionmanager_spec.js @@ -105,6 +105,10 @@ function mockBidRequest(bid, opts) { const defaultMediaType = { banner: { sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + renderer: {} } } const mediaType = (opts && opts.mediaType) ? opts.mediaType : defaultMediaType; @@ -836,7 +840,7 @@ describe('auctionmanager.js', function () { } const auction = auctionManager.createAuction({ adUnits, ortb2Fragments }); expect(auction.getNonBids()[0]).to.equal(undefined); - events.emit(EVENTS.SEAT_NON_BID, { + events.emit(EVENTS.PBS_ANALYTICS, { auctionId: auction.getAuctionId(), seatnonbid: ['test'] }); @@ -1159,7 +1163,7 @@ describe('auctionmanager.js', function () { bids[0], { bidderCode: BIDDER_CODE, - mediaType: 'video-outstream', + mediaType: 'video', } ); spec.interpretResponse.returns(bids1); @@ -1278,6 +1282,12 @@ describe('auctionmanager.js', function () { expect(auction.getBidsReceived()[0].ttlBuffer).to.eql(0); }); + it('sets bidResponse.element from adUnit.element', () => { + adUnits[0].element = 'test'; + auction.callBids(); + expect(auction.getBidsReceived()[0].element).to.equal('test'); + }); + [ { request: 1, diff --git a/test/spec/libraries/bidViewabilityPixels_spec.js b/test/spec/libraries/bidViewabilityPixels_spec.js new file mode 100644 index 00000000000..05f248c30ba --- /dev/null +++ b/test/spec/libraries/bidViewabilityPixels_spec.js @@ -0,0 +1,178 @@ +import { fireViewabilityPixels, getViewabilityTrackersFromBid } from 'libraries/bidViewabilityPixels/index.js'; +import * as utils from 'src/utils.js'; +import * as sinon from 'sinon'; +import { expect } from 'chai'; +import { EVENT_TYPE_IMPRESSION, EVENT_TYPE_VIEWABLE, TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from 'src/eventTrackers.js'; +import { auctionManager } from 'src/auctionManager.js'; + +const VIEWABILITY_PIXEL_URLS = [ + 'https://domain-1.com/end-point?a=1', + 'https://domain-2.com/end-point/', + 'https://domain-3.com/end-point?a=1' +]; + +const bidWithViewabilityTrackers = { + adUnitCode: 'test-unit', + bidder: 'test-bidder', + eventtrackers: VIEWABILITY_PIXEL_URLS.map(url => ({ + event: EVENT_TYPE_VIEWABLE, + method: TRACKER_METHOD_IMG, + url + })) +}; + +describe('bidViewabilityPixels library', function () { + let sandbox; + let triggerPixelSpy; + let insertHtmlIntoIframeSpy; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + triggerPixelSpy = sandbox.spy(utils, 'triggerPixel'); + insertHtmlIntoIframeSpy = sandbox.spy(utils, 'insertHtmlIntoIframe'); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('getViewabilityTrackersFromBid', function () { + it('should return { img: [], js: [] } when bid is null, undefined, or has no valid eventtrackers', function () { + const empty = { img: [], js: [] }; + expect(getViewabilityTrackersFromBid(null)).to.deep.equal(empty); + expect(getViewabilityTrackersFromBid(undefined)).to.deep.equal(empty); + expect(getViewabilityTrackersFromBid({ adUnitCode: 'x' })).to.deep.equal(empty); + expect(getViewabilityTrackersFromBid({ eventtrackers: {} })).to.deep.equal(empty); + expect(getViewabilityTrackersFromBid({ eventtrackers: 'trackers' })).to.deep.equal(empty); + expect(getViewabilityTrackersFromBid({ eventtrackers: [] })).to.deep.equal(empty); + }); + + it('should return img and js URLs for viewable trackers', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://img.com' }, + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_JS, url: 'https://js.com' } + ] + }; + expect(getViewabilityTrackersFromBid(bid)).to.deep.equal({ + img: ['https://img.com'], + js: ['https://js.com'] + }); + }); + + it('should return only viewable trackers when eventtrackers has mixed event types', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://viewable.com' }, + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'https://impression.com' } + ] + }; + expect(getViewabilityTrackersFromBid(bid)).to.deep.equal({ + img: ['https://viewable.com'], + js: [] + }); + }); + + it('should return only js when viewable trackers have JS method only', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_JS, url: 'https://viewable-js.com' } + ] + }; + expect(getViewabilityTrackersFromBid(bid)).to.deep.equal({ + img: [], + js: ['https://viewable-js.com'] + }); + }); + }); + + describe('fireViewabilityPixels', function () { + it('should not call triggerPixel or insertHtmlIntoIframe when bid has no eventtrackers', function () { + fireViewabilityPixels({ adUnitCode: 'x' }); + expect(triggerPixelSpy.callCount).to.equal(0); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(0); + }); + + it('should not call triggerPixel or insertHtmlIntoIframe when eventtrackers is empty array', function () { + fireViewabilityPixels({ eventtrackers: [] }); + expect(triggerPixelSpy.callCount).to.equal(0); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(0); + }); + + it('should fire one pixel per viewable img URL in eventtrackers', function () { + fireViewabilityPixels(bidWithViewabilityTrackers); + expect(triggerPixelSpy.callCount).to.equal(VIEWABILITY_PIXEL_URLS.length); + VIEWABILITY_PIXEL_URLS.forEach((url, i) => { + expect(triggerPixelSpy.getCall(i).args[0]).to.equal(url); + }); + }); + + it('should fire viewable JS trackers via insertHtmlIntoIframe with script tag', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_JS, url: 'https://viewable-js.com' } + ] + }; + fireViewabilityPixels(bid); + expect(triggerPixelSpy.callCount).to.equal(0); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(1); + expect(insertHtmlIntoIframeSpy.getCall(0).args[0]).to.include('script async src="https://viewable-js.com"'); + }); + + it('should fire both img (triggerPixel) and js (insertHtmlIntoIframe) viewable trackers', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://img.com' }, + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_JS, url: 'https://js.com' } + ] + }; + fireViewabilityPixels(bid); + expect(triggerPixelSpy.callCount).to.equal(1); + expect(triggerPixelSpy.getCall(0).args[0]).to.equal('https://img.com'); + expect(insertHtmlIntoIframeSpy.callCount).to.equal(1); + expect(insertHtmlIntoIframeSpy.getCall(0).args[0]).to.include('script async src="https://js.com"'); + }); + + it('should only fire EVENT_TYPE_VIEWABLE URLs', function () { + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://viewable-img.com' }, + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'https://impression.com' } + ] + }; + fireViewabilityPixels(bid); + expect(triggerPixelSpy.callCount).to.equal(1); + expect(triggerPixelSpy.getCall(0).args[0]).to.equal('https://viewable-img.com'); + }); + + describe('when bid has native response with eventtrackers (viewable)', function () { + let indexStub; + let getMediaTypesStub; + + beforeEach(function () { + getMediaTypesStub = sinon.stub(); + indexStub = sandbox.stub(auctionManager, 'index').get(() => ({ getMediaTypes: getMediaTypesStub })); + }); + + it('should fire viewable trackers from bid.native.ortb.eventtrackers in addition to bid.eventtrackers', function () { + getMediaTypesStub.returns({}); + const bid = { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://from-bid.com' } + ], + native: { + ortb: { + eventtrackers: [ + { event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url: 'https://from-native.com' } + ] + } + } + }; + fireViewabilityPixels(bid); + expect(triggerPixelSpy.callCount).to.equal(2); + expect(triggerPixelSpy.getCall(0).args[0]).to.equal('https://from-bid.com'); + expect(triggerPixelSpy.getCall(1).args[0]).to.equal('https://from-native.com'); + }); + }); + }); +}); diff --git a/test/spec/libraries/dnt_spec.js b/test/spec/libraries/dnt_spec.js deleted file mode 100644 index f2415dfae6e..00000000000 --- a/test/spec/libraries/dnt_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import { getDNT } from '../../../libraries/dnt/index.js'; - -describe('dnt helper', () => { - let win; - beforeEach(() => { - win = {}; - }); - - [ - 'top', - 'doNotTrack', - 'navigator', - 'navigator.doNotTrack', - 'top.doNotTrack', - 'top.navigator.doNotTrack' - ].forEach(path => { - it(`should not choke if ${path} throws`, () => { - path = path.split('.'); - path.reduce((parent, name, i) => { - if (i === path.length - 1) { - Object.defineProperty(parent, name, { - get() { - throw new Error(); - } - }) - } else { - parent = parent[name] = {}; - } - return parent; - }, win); - expect(getDNT(win)).to.be.false; - }) - }) -}) diff --git a/test/spec/libraries/percentInView_spec.js b/test/spec/libraries/percentInView_spec.js index 7405e95f95d..fd3f9b5ea20 100644 --- a/test/spec/libraries/percentInView_spec.js +++ b/test/spec/libraries/percentInView_spec.js @@ -1,6 +1,23 @@ -import { getViewportOffset } from '../../../libraries/percentInView/percentInView.js'; +import { + getViewportOffset, + intersections, + mkIntersectionHook, + percentInView, + viewportIntersections, +} from '../../../libraries/percentInView/percentInView.js'; +import * as bbox from 'libraries/boundingClientRect/boundingClientRect'; + +import { defer } from 'src/utils/promise.js'; describe('percentInView', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + afterEach(() => { + sandbox.restore(); + }); + describe('getViewportOffset', () => { function mockWindow(offsets = []) { let win, leaf, child; @@ -17,6 +34,7 @@ describe('percentInView', () => { } return leaf; } + it('returns 0, 0 for the top window', () => { expect(getViewportOffset(mockWindow())).to.eql({ x: 0, y: 0 }); }); @@ -37,4 +55,218 @@ describe('percentInView', () => { expect(getViewportOffset(win)).to.eql({ x: 0, y: 0 }); }); }); + + async function delay(ms = 10) { + await new Promise(resolve => setTimeout(resolve, ms)); + } + + describe('intersections', () => { + let callback, obs, nakedObs, mkObserver, el; + beforeEach(() => { + el = document.createElement('div'); + nakedObs = sinon.stub(); + nakedObs.observe = sinon.stub(); + mkObserver = sinon.stub().callsFake((cb) => { + callback = cb; + return nakedObs; + }); + obs = intersections(mkObserver); + }); + describe('when mkObserver throws', () => { + beforeEach(() => { + mkObserver = sinon.stub().callsFake(() => { + throw new Error(); + }); + obs = intersections(mkObserver); + }); + it('getIntersection should return undef', () => { + expect(obs.getIntersection({})).to.not.exist; + }); + + it('observe should resolve', async () => { + await obs.observe({}); + }); + }); + + it('observe should reject if the element cannot be observed', async () => { + let err = new Error(); + nakedObs.observe.throws(err); + try { + await obs.observe(null); + } catch (e) { + expect(e).to.eql(err); + return; + } + sinon.assert.fail('promise should reject'); + }); + it('does not observe the same element more than once', () => { + obs.observe(el); + obs.observe(el); + sinon.assert.calledOnce(nakedObs.observe); + }); + it('getIntersection should return undefined if the element is not observed', () => { + expect(obs.getIntersection(el)).to.not.exist; + }); + it('observe should resolve to latest intersection entry', () => { + let pm = obs.observe(el); + let entry = { + target: el, + time: 100 + }; + callback([entry, { + target: el, + time: 50 + }]); + return pm.then(result => { + expect(result).to.eql(entry); + }); + }); + it('observe should resolve immediately if an entry is available', () => { + const entry = { + target: el, + time: 10 + }; + callback([entry]); + const pm = obs.observe(el); + callback([{ + target: el, + time: 20 + }]); + return pm.then((result) => { + expect(result).to.eql(entry); + }); + }); + it('should ignore stale entries', async () => { + const entry = { + target: el, + time: 100 + }; + obs.observe(el); + callback([entry]); + callback([{ + target: el, + time: 10 + }]); + expect(obs.getIntersection(el)).to.eql(entry); + }); + + it('should not resolve until the targeted element has intersected', async () => { + const entry = { + target: el, + time: 100 + }; + const pm = obs.observe(el); + callback([{ + target: {}, + time: 20 + }]); + await delay(); + callback([entry]); + expect(await pm).to.eql(entry); + }); + }); + + describe('intersection hook', () => { + let intersections, hook, next, request; + beforeEach(() => { + next = sinon.stub(); + intersections = { + observe: sinon.stub() + }; + hook = mkIntersectionHook(intersections); + request = {}; + }); + + it('should observe elements for every ad unit', async () => { + request.adUnits = [{ + element: 'el1' + }, { + code: 'el2' + }]; + sandbox.stub(document, 'getElementById').returns('el2'); + hook(next, request); + sinon.assert.calledWith(intersections.observe, 'el1'); + sinon.assert.calledWith(intersections.observe, 'el2'); + await delay(); + sinon.assert.calledWith(next, request); + }); + + describe('promise resolution', () => { + let adUnits; + beforeEach(() => { + adUnits = { + el1: { + element: 'el1', + df: defer() + }, + el2: { + element: 'el2', + df: defer() + } + }; + request.adUnits = Object.values(adUnits); + intersections.observe.callsFake((element) => adUnits[element].df.promise); + }); + it('should wait for all promises to resolve', async () => { + hook(next, request); + sinon.assert.notCalled(next); + adUnits.el1.df.resolve(); + await delay(); + sinon.assert.notCalled(next); + adUnits.el2.df.resolve(); + await delay(); + sinon.assert.calledWith(next, request); + }); + + it('should still continue if some promises reject', async () => { + hook(next, request); + adUnits.el1.df.reject(); + await delay(); + sinon.assert.notCalled(next); + adUnits.el2.df.resolve(); + await delay(); + sinon.assert.calledWith(next, request); + }); + + it('should continue if promises never resolve', async () => { + hook(next, request); + await delay(100); + sinon.assert.called(next); + }); + + it('should not delay if there are no elements to observe', async () => { + request.adUnits = []; + hook(next, request); + await delay(); + sinon.assert.called(next); + }) + }); + }); + + describe('percentInView', () => { + let intersection; + beforeEach(() => { + sandbox.stub(viewportIntersections, 'getIntersection').callsFake(() => intersection); + sandbox.stub(viewportIntersections, 'observe'); + sandbox.stub(bbox, 'getBoundingClientRect'); + }); + + it('does not use intersection if w/h are relevant', () => { + bbox.getBoundingClientRect.returns({ + width: 0, + height: 0, + left: -50, + top: -100, + }); + intersection = { + boundingClientRect: { + width: 0, + height: 0, + }, + isIntersecting: true, + intersectionRatio: 1 + }; + expect(percentInView({}, { w: 100, h: 200 })).to.not.eql(100); + }); + }); }); diff --git a/test/spec/libraries/placementPositionInfo_spec.js b/test/spec/libraries/placementPositionInfo_spec.js index 91aee5c6d81..bfd886b8898 100644 --- a/test/spec/libraries/placementPositionInfo_spec.js +++ b/test/spec/libraries/placementPositionInfo_spec.js @@ -3,6 +3,7 @@ import * as utils from '../../../src/utils.js'; import * as boundingClientRectLib from '../../../libraries/boundingClientRect/boundingClientRect.js'; import * as percentInViewLib from '../../../libraries/percentInView/percentInView.js'; import * as winDimensions from 'src/utils/winDimensions.js'; +import * as adUnits from 'src/utils/adUnits'; import assert from 'assert'; import sinon from 'sinon'; @@ -80,7 +81,7 @@ describe('placementPositionInfo', function () { beforeEach(function () { mockElement = { id: 'test-ad-unit' }; - mockDocument.getElementById.returns(mockElement); + sandbox.stub(adUnits, 'getAdUnitElement').returns(mockElement); getBoundingClientRectStub.returns({ top: 100, @@ -162,7 +163,7 @@ describe('placementPositionInfo', function () { }); it('should handle null element gracefully', function () { - mockDocument.getElementById.returns(null); + adUnits.getAdUnitElement.returns(null); const bidReq = { adUnitCode: 'non-existent-unit', @@ -177,7 +178,7 @@ describe('placementPositionInfo', function () { }); it('should not call getViewability when element is null', function () { - mockDocument.getElementById.returns(null); + adUnits.getAdUnitElement.returns(null); const bidReq = { adUnitCode: 'non-existent-unit', @@ -400,7 +401,7 @@ describe('placementPositionInfo', function () { describe('iframe coordinate translation', function () { beforeEach(() => { - mockDocument.getElementById = sandbox.stub().returns({ id: 'test' }); + sandbox.stub(adUnits, 'getAdUnitElement').returns({ id: 'test' }) mockWindow.innerHeight = 1000; mockDocument.body = { scrollHeight: 2000, offsetHeight: 1800 diff --git a/test/spec/libraries/storageDisclosure_spec.js b/test/spec/libraries/storageDisclosure_spec.js index fa345b6115c..98a98700484 100644 --- a/test/spec/libraries/storageDisclosure_spec.js +++ b/test/spec/libraries/storageDisclosure_spec.js @@ -1,5 +1,4 @@ import { getStorageDisclosureSummary } from '../../../libraries/storageDisclosure/summary.js'; -import { dynamicDisclosureCollector } from '../../../modules/storageControl.js'; describe('storageDisclosure', () => { let moduleMeta; diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 7c861c0723f..6c7c98d197c 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -21,6 +21,10 @@ describe('33acrossIdSystem', () => { }); describe('getId', () => { + afterEach(() => { + sinon.restore(); + }); + it('should call endpoint', () => { const completeCallback = sinon.spy(); @@ -96,6 +100,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -110,10 +115,6 @@ describe('33acrossIdSystem', () => { expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.false; expect(setCookie.calledWithExactly('33acrossIdHm', '', sinon.match.string, 'Lax', 'foo.com')).to.be.false; - - removeDataFromLocalStorage.restore(); - setCookie.restore(); - domainUtils.domainOverride.restore(); }); }); @@ -519,6 +520,40 @@ describe('33acrossIdSystem', () => { setDataInLocalStorage.restore(); }); + + it('should not store a publisher-provided hashed email in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: false, + hem: '33acrossIdHmValue+' + }, + enabledStorageTypes: ['html5'], + storage: {} + }); + + 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' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdHm', '33acrossIdHmValue+')).to.be.false; + + setDataInLocalStorage.restore(); + }); }); }); @@ -614,6 +649,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -630,10 +666,6 @@ describe('33acrossIdSystem', () => { expect(removeDataFromLocalStorage.calledWith(`33acrossId${suffix}`)).to.be.true; expect(setCookie.calledWithExactly(`33acrossId${suffix}`, '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; }); - - removeDataFromLocalStorage.restore(); - setCookie.restore(); - domainUtils.domainOverride.restore(); }); }); @@ -1480,6 +1512,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); + const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { @@ -1497,8 +1530,42 @@ describe('33acrossIdSystem', () => { removeDataFromLocalStorage.restore(); setCookie.restore(); + cookiesAreEnabled.restore(); domainUtils.domainOverride.restore(); }); + + it('should not wipe any stored hashed email when first-party ID support is disabled', () => { + const completeCallback = () => {}; + + const { callback } = thirtyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: false + }, + enabledStorageTypes: ['html5'], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + // no envelope field + }, + expires: 1645667805067 + })); + + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdHm')).to.be.false; + + removeDataFromLocalStorage.restore(); + }); }); context('when the server returns an error status code', () => { diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index 917b39a8eb4..34afb59ec4a 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/adkernelBidAdapter'; import * as utils from 'src/utils'; -import * as navigatorDnt from 'libraries/dnt/index.js'; import { NATIVE, BANNER, VIDEO } from 'src/mediaTypes'; import { config } from 'src/config'; import { parseDomain } from '../../../src/refererDetection.js'; @@ -341,11 +340,9 @@ describe('Adkernel adapter', function () { } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); - function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { - const dntmock = sandbox.stub(navigatorDnt, 'getDNT').callsFake(() => dnt); + function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST) { bidderRequest.bids = bidRequests; const pbRequests = spec.buildRequests(bidRequests, bidderRequest); - dntmock.restore(); const rtbRequests = pbRequests.map(r => JSON.parse(r.data)); return [pbRequests, rtbRequests]; } @@ -418,7 +415,6 @@ describe('Adkernel adapter', function () { expect(bidRequest.device).to.have.property('ip', 'caller'); expect(bidRequest.device).to.have.property('ipv6', 'caller'); expect(bidRequest.device).to.have.property('ua', 'caller'); - expect(bidRequest.device).to.have.property('dnt', 1); }); it('should copy FPD to imp.banner', function() { @@ -470,11 +466,6 @@ describe('Adkernel adapter', function () { expect(bidRequest).to.not.have.property('user'); }); - it('should\'t pass dnt if state is unknown', function () { - const [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST, false); - expect(bidRequests[0].device).to.not.have.property('dnt'); - }); - it('should forward default bidder timeout', function() { const [_, bidRequests] = buildRequest([bid1_zone1]); expect(bidRequests[0]).to.have.property('tmax', 3000); diff --git a/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js b/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js index 55cddbb0dd2..0fc8c700aff 100644 --- a/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js +++ b/test/spec/modules/adnuntiusAnalyticsAdapter_spec.js @@ -2,7 +2,7 @@ import adnAnalyticsAdapter, { BID_WON_TIMEOUT } from 'modules/adnuntiusAnalytics 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'; +import * as adUnits from 'src/utils/adUnits'; const events = require('src/events'); const utils = require('src/utils'); @@ -301,7 +301,7 @@ describe('Adnuntius analytics adapter', function () { } sandbox.stub(events, 'getEvents').returns([]); sandbox.stub(utils, 'timestamp').returns(1519149562416); - sandbox.stub(document, 'getElementById').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); clock = sandbox.useFakeTimers(1519767013781); }); diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index fe0096bf91b..4ab511a5a2d 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -63,38 +63,6 @@ describe('AdoceanAdapter', function () { expect(spec.isBidRequestValid(videoInscreenBid)).to.equal(true); }); - const videoAdpodBid = { - bidder: 'adocean', - params: { - masterId: 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', - slaveId: 'adoceanmyaozpniqismex', - emitter: 'myao.adocean.pl' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [300, 250], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: false - } - }, - bidId: '30b31c1838de1e', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a475', - }; - - it('should return true for adpod video without requireExactDuration', function () { - expect(spec.isBidRequestValid(videoAdpodBid)).to.equal(true); - }); - - it('should return false for adpod video with requireExactDuration', function () { - const invalidBid = Object.assign({}, videoAdpodBid); - invalidBid.mediaTypes.video.requireExactDuration = true; - expect(spec.isBidRequestValid(invalidBid)).to.equal(false); - }); - const videoOutstreamBid = { bidder: 'adocean', params: { @@ -243,41 +211,6 @@ describe('AdoceanAdapter', function () { expect(request.url).to.include('maxdur=60'); expect(request.url).to.include('mindur=10'); }); - - const videoAdpodBidRequests = [ - { - bidder: 'adocean', - params: { - masterId: 'tmYF.DMl7ZBq.Nqt2Bq4FutQTJfTpxCOmtNPZoQUDcL.G7', - slaveId: 'adoceanmyaozpniqismex', - emitter: 'myao.adocean.pl' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - video: { - playerSize: [200, 200], - context: 'adpod', - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: false - } - }, - bidId: '30b31c1838de1h', - bidderRequestId: '22edbae2733bf6', - auctionId: '1d1a030790a476', - } - ]; - - it('should build correct video adpod request', function () { - const request = spec.buildRequests(videoAdpodBidRequests, bidderRequest)[0]; - expect(request).to.exist; - expect(request.url).to.include('id=' + videoAdpodBidRequests[0].params.masterId); - expect(request.url).to.include('slaves=zpniqismex'); - expect(request.url).to.include('spots=20'); // 300 / 15 = 20 - expect(request.url).to.include('dur=300'); - expect(request.url).to.include('maxdur=30'); - expect(request.url).to.not.include('mindur='); - }); }); describe('interpretResponseBanner', function () { diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js deleted file mode 100644 index e833666ef62..00000000000 --- a/test/spec/modules/adpod_spec.js +++ /dev/null @@ -1,1238 +0,0 @@ -import * as utils from 'src/utils.js'; -import { config } from 'src/config.js'; -import * as videoCache from 'src/videoCache.js'; -import * as auction from 'src/auction.js'; -import { ADPOD } from 'src/mediaTypes.js'; - -import { callPrebidCacheHook, checkAdUnitSetupHook, checkVideoBidSetupHook, adpodSetConfig, sortByPricePerSecond } from 'modules/adpod.js'; - -const expect = require('chai').expect; - -describe('adpod.js', function () { - let logErrorStub; - let logWarnStub; - let logInfoStub; - - describe('callPrebidCacheHook', function () { - let callbackResult; - let clock; - let addBidToAuctionStub; - let doCallbacksIfTimedoutStub; - let storeStub; - let afterBidAddedSpy; - let auctionBids = []; - - const callbackFn = function() { - callbackResult = true; - }; - - const auctionInstance = { - getAuctionStatus: function() { - return auction.AUCTION_IN_PROGRESS; - } - } - - const fakeStoreFn = function(bids, callback) { - const payload = []; - bids.forEach(bid => payload.push({ uuid: bid.customCacheKey })); - callback(null, payload); - }; - - beforeEach(function() { - callbackResult = null; - afterBidAddedSpy = sinon.spy(); - storeStub = sinon.stub(videoCache, 'store'); - logWarnStub = sinon.stub(utils, 'logWarn'); - logInfoStub = sinon.stub(utils, 'logInfo'); - addBidToAuctionStub = sinon.stub(auction, 'addBidToAuction').callsFake(function (auctionInstance, bid) { - auctionBids.push(bid); - }); - clock = sinon.useFakeTimers(); - config.setConfig({ - cache: { - url: 'https://test.cache.url/endpoint' - } - }); - }); - - afterEach(function() { - storeStub.restore(); - logWarnStub.restore(); - logInfoStub.restore(); - addBidToAuctionStub.restore(); - clock.restore(); - config.resetConfig(); - auctionBids = []; - }) - - it('should redirect back to the original function if bid is not an adpod video', function () { - const bid = { - adId: 'testAdId_123', - mediaType: 'video' - }; - - const videoMT = { - context: 'outstream' - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bid, function () {}, videoMT); - expect(callbackResult).to.equal(true); - }); - - it('should immediately add the adpod bid to auction if adpod.deferCaching in config is true', function() { - config.setConfig({ - adpod: { - deferCaching: true, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'adId01277', - auctionId: 'no_defer_123', - mediaType: 'video', - cpm: 5, - pbMg: '5.00', - adserverTargeting: { - hb_pb: '5.00' - }, - meta: { - adServerCatId: 'test' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const bidResponse2 = { - adId: 'adId46547', - auctionId: 'no_defer_123', - mediaType: 'video', - cpm: 12, - pbMg: '12.00', - adserverTargeting: { - hb_pb: '12.00' - }, - meta: { - adServerCatId: 'value' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 300, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - // check if bid adsereverTargeting is setup - expect(callbackResult).to.be.null; - expect(storeStub.called).to.equal(false); - expect(afterBidAddedSpy.calledTwice).to.equal(true); - expect(auctionBids.length).to.equal(2); - expect(auctionBids[0].adId).to.equal(bidResponse1.adId); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_test_15s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_test_15s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - expect(auctionBids[1].adId).to.equal(bidResponse2.adId); - expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^12\.00_value_15s_.*/); - expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); - expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[1].adserverTargeting.hb_cache_id).to.equal(auctionBids[0].adserverTargeting.hb_cache_id); - expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); - }); - - it('should send prebid cache call once bid queue is full', function () { - storeStub.callsFake(fakeStoreFn); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'adId123', - auctionId: 'full_abc123', - mediaType: 'video', - cpm: 10, - pbMg: '10.00', - adserverTargeting: { - hb_pb: '10.00' - }, - meta: { - adServerCatId: 'airline' - }, - video: { - context: ADPOD, - durationSeconds: 20, - durationBucket: 30 - } - }; - const bidResponse2 = { - adId: 'adId234', - auctionId: 'full_abc123', - mediaType: 'video', - cpm: 15, - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00' - }, - meta: { - adServerCatId: 'airline' - }, - video: { - context: ADPOD, - durationSeconds: 25, - durationBucket: 30 - } - }; - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledTwice).to.equal(true); - expect(auctionBids.length).to.equal(2); - expect(auctionBids[0].adId).to.equal('adId123'); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_airline_30s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_airline_30s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - expect(auctionBids[1].adId).to.equal('adId234'); - expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); - expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); - expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - }); - - it('should send prebid cache call after set period of time (even if queue is not full)', function () { - storeStub.callsFake(fakeStoreFn); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - bidQueueTimeDelay: 30, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse = { - adId: 'adId234', - auctionId: 'timer_abc234', - mediaType: 'video', - cpm: 15, - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00' - }, - meta: { - adServerCatId: 'airline' - }, - video: { - context: ADPOD, - durationSeconds: 30, - durationBucket: 30 - } - }; - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30], - requireExactDuration: true - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse, afterBidAddedSpy, videoMT); - clock.tick(31); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledOnce).to.equal(true); - expect(auctionBids.length).to.equal(1); - expect(auctionBids[0].adId).to.equal('adId234'); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_30s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_30s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - }); - - it('should execute multiple prebid cache calls when number of bids exceeds queue size', function () { - storeStub.callsFake(fakeStoreFn); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - bidQueueTimeDelay: 30, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'multi_ad1', - auctionId: 'multi_call_abc345', - mediaType: 'video', - cpm: 15, - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00' - }, - meta: { - adServerCatId: 'airline' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - const bidResponse2 = { - adId: 'multi_ad2', - auctionId: 'multi_call_abc345', - mediaType: 'video', - cpm: 15, - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00' - }, - meta: { - adServerCatId: 'news' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - const bidResponse3 = { - adId: 'multi_ad3', - auctionId: 'multi_call_abc345', - mediaType: 'video', - cpm: 10, - pbMg: '10.00', - adserverTargeting: { - hb_pb: '10.00' - }, - meta: { - adServerCatId: 'sports' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse3, afterBidAddedSpy, videoMT); - clock.next(); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledThrice).to.equal(true); - expect(storeStub.calledTwice).to.equal(true); - expect(auctionBids.length).to.equal(3); - expect(auctionBids[0].adId).to.equal('multi_ad1'); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^15\.00_airline_15s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_airline_15s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - expect(auctionBids[1].adId).to.equal('multi_ad2'); - expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_news_15s_.*/); - expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_news_15s'); - expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); - expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - expect(auctionBids[2].adId).to.equal('multi_ad3'); - expect(auctionBids[2].customCacheKey).to.exist.and.to.match(/^10\.00_sports_15s_.*/); - expect(auctionBids[2].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_sports_15s'); - expect(auctionBids[2].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); - expect(auctionBids[2].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - }); - - it('should cache the bids with a shortened custom key when adpod.brandCategoryExclusion is false', function() { - storeStub.callsFake(fakeStoreFn); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - bidQueueTimeDelay: 30, - deferCaching: false, - brandCategoryExclusion: false - } - }); - - const bidResponse1 = { - adId: 'nocat_ad1', - auctionId: 'no_category_abc345', - mediaType: 'video', - cpm: 10, - pbMg: '10.00', - adserverTargeting: { - hb_pb: '10.00' - }, - meta: { - adServerCatId: undefined - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - const bidResponse2 = { - adId: 'nocat_ad2', - auctionId: 'no_category_abc345', - mediaType: 'video', - cpm: 15, - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00' - }, - meta: { - adServerCatId: undefined - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledTwice).to.equal(true); - expect(storeStub.calledOnce).to.equal(true); - expect(auctionBids.length).to.equal(2); - expect(auctionBids[0].adId).to.equal('nocat_ad1'); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^10\.00_15s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('10.00_15s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - expect(auctionBids[1].adId).to.equal('nocat_ad2'); - expect(auctionBids[1].customCacheKey).to.exist.and.to.match(/^15\.00_15s_.*/); - expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('15.00_15s'); - expect(auctionBids[1].adserverTargeting.hb_cache_id).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id); - expect(auctionBids[1].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - }); - - it('should not add bid to auction when config adpod.brandCategoryExclusion is true but bid is missing adServerCatId', function() { - storeStub.callsFake(fakeStoreFn); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - bidQueueTimeDelay: 30, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'missingCat_ad1', - auctionId: 'missing_category_abc345', - mediaType: 'video', - cpm: 10, - meta: { - adServerCatId: undefined - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledOnce).to.equal(true); - expect(storeStub.called).to.equal(false); - expect(logWarnStub.calledOnce).to.equal(true); - expect(auctionBids.length).to.equal(0); - }); - - it('should not add bid to auction when Prebid Cache detects an existing key', function () { - storeStub.callsFake(function(bids, callback) { - const payload = []; - bids.forEach(bid => payload.push({ uuid: bid.customCacheKey })); - - // fake a duplicate bid response from PBC (sets an empty string for the uuid) - payload[1].uuid = ''; - callback(null, payload); - }); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'dup_ad_1', - auctionId: 'duplicate_def123', - mediaType: 'video', - cpm: 5, - pbMg: '5.00', - adserverTargeting: { - hb_pb: '5.00' - }, - meta: { - adServerCatId: 'tech' - }, - video: { - context: ADPOD, - durationSeconds: 45, - durationBucket: 45 - } - }; - const bidResponse2 = { - adId: 'dup_ad_2', - auctionId: 'duplicate_def123', - mediaType: 'video', - cpm: 5, - pbMg: '5.00', - adserverTargeting: { - hb_pb: '5.00' - }, - meta: { - adServerCatId: 'tech' - }, - video: { - context: ADPOD, - durationSeconds: 45, - durationBucket: 45 - } - }; - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledTwice).to.equal(true); - expect(storeStub.calledOnce).to.equal(true); - expect(logInfoStub.calledOnce).to.equal(true); - expect(auctionBids.length).to.equal(1); - expect(auctionBids[0].adId).to.equal('dup_ad_1'); - expect(auctionBids[0].customCacheKey).to.exist.and.to.match(/^5\.00_tech_45s_.*/); - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('5.00_tech_45s'); - expect(auctionBids[0].adserverTargeting.hb_cache_id).to.exist; - expect(auctionBids[0].videoCacheKey).to.exist.and.to.equal(auctionBids[0].adserverTargeting.hb_cache_id) - }); - - it('should not add bids to auction if PBC returns an error', function() { - storeStub.callsFake(function(bids, callback) { - const payload = []; - const errmsg = 'invalid json'; - - callback(errmsg, payload); - }); - - config.setConfig({ - adpod: { - bidQueueSizeLimit: 2, - deferCaching: false, - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'err_ad_1', - auctionId: 'error_xyz123', - mediaType: 'video', - cpm: 5, - meta: { - adServerCatId: 'tech' - }, - video: { - context: ADPOD, - durationSeconds: 30, - durationBucket: 30 - } - }; - const bidResponse2 = { - adId: 'err_ad_2', - auctionId: 'error_xyz123', - mediaType: 'video', - cpm: 5, - meta: { - adServerCatId: 'tech' - }, - video: { - context: ADPOD, - durationSeconds: 30, - durationBucket: 30 - } - }; - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 120, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - expect(logWarnStub.calledOnce).to.equal(true); - expect(auctionBids.length).to.equal(0); - }); - - it('should use bid.adserverTargeting.hb_pb when custom price granularity is configured', function() { - storeStub.callsFake(fakeStoreFn); - - const customConfigObject = { - 'buckets': [{ - 'precision': 2, // default is 2 if omitted - means 2.1234 rounded to 2 decimal places = 2.12 - 'max': 5, - 'increment': 0.01 // from $0 to $5, 1-cent increments - }, - { - 'precision': 2, - 'max': 8, - 'increment': 0.05 // from $5 to $8, round down to the previous 5-cent increment - }, - { - 'precision': 2, - 'max': 40, - 'increment': 0.5 // from $8 to $40, round down to the previous 50-cent increment - }] - }; - config.setConfig({ - priceGranularity: customConfigObject, - adpod: { - brandCategoryExclusion: true - } - }); - - const bidResponse1 = { - adId: 'cat_ad1', - auctionId: 'test_category_abc345', - mediaType: 'video', - cpm: 15, - pbAg: '15.00', - pbCg: '15.00', - pbDg: '15.00', - pbHg: '15.00', - pbLg: '5.00', - pbMg: '15.00', - adserverTargeting: { - hb_pb: '15.00', - }, - meta: { - adServerCatId: 'test' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 45, - durationRangeSec: [15, 30], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - - expect(callbackResult).to.be.null; - expect(afterBidAddedSpy.calledOnce).to.equal(true); - expect(storeStub.called).to.equal(false); - expect(auctionBids.length).to.equal(1); - }); - - it('should set deal tier in place of cpm when prioritzeDeals is true', function() { - config.setConfig({ - adpod: { - deferCaching: true, - brandCategoryExclusion: true, - prioritizeDeals: true, - dealTier: { - 'appnexus': { - 'prefix': 'tier', - 'minDealTier': 5 - } - } - } - }); - - const bidResponse1 = { - adId: 'adId01277', - auctionId: 'no_defer_123', - mediaType: 'video', - bidderCode: 'appnexus', - cpm: 5, - pbMg: '5.00', - adserverTargeting: { - hb_pb: '5.00' - }, - meta: { - adServerCatId: 'test' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15, - dealTier: 7 - } - }; - - const bidResponse2 = { - adId: 'adId46547', - auctionId: 'no_defer_123', - mediaType: 'video', - bidderCode: 'appnexus', - cpm: 12, - pbMg: '12.00', - adserverTargeting: { - hb_pb: '12.00' - }, - meta: { - adServerCatId: 'value' - }, - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - } - }; - - const videoMT = { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 300, - durationRangeSec: [15, 30, 45], - requireExactDuration: false - }; - - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, videoMT); - callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, videoMT); - - expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('tier7_test_15s'); - expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); - }) - }); - - describe('checkAdUnitSetupHook', function () { - let results; - const callbackFn = function (adUnits) { - results = adUnits; - }; - - beforeEach(function () { - logWarnStub = sinon.stub(utils, 'logWarn'); - results = null; - }); - - afterEach(function() { - utils.logWarn.restore(); - }); - - it('removes an incorrectly setup adpod adunit - required fields are missing', function() { - const adUnits = [{ - code: 'test1', - mediaTypes: { - video: { - context: ADPOD - } - } - }, { - code: 'test2', - mediaTypes: { - video: { - context: 'instream' - } - } - }]; - - checkAdUnitSetupHook(callbackFn, adUnits); - - expect(results).to.deep.equal([{ - code: 'test2', - mediaTypes: { - video: { - context: 'instream' - } - } - }]); - expect(logWarnStub.calledOnce).to.equal(true); - }); - - it('removes an incorrectly setup adpod adunit - required fields are using invalid values', function() { - const adUnits = [{ - code: 'test1', - mediaTypes: { - video: { - context: ADPOD, - durationRangeSec: [-5, 15, 30, 45], - adPodDurationSec: 300 - } - } - }]; - - checkAdUnitSetupHook(callbackFn, adUnits); - - expect(results).to.deep.equal([]); - expect(logWarnStub.calledOnce).to.equal(true); - - adUnits[0].mediaTypes.video.durationRangeSec = [15, 30, 45]; - adUnits[0].mediaTypes.video.adPodDurationSec = 0; - - checkAdUnitSetupHook(callbackFn, adUnits); - - expect(results).to.deep.equal([]); - expect(logWarnStub.calledTwice).to.equal(true); - }); - - it('removes an incorrectly setup adpod adunit - attempting to use multi-format adUnit', function() { - const adUnits = [{ - code: 'multi_test1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]] - }, - video: { - context: 'adpod', - playerSize: [[300, 250]], - durationRangeSec: [15, 30, 45], - adPodDurationSec: 300 - } - } - }]; - - checkAdUnitSetupHook(callbackFn, adUnits); - - expect(results).to.deep.equal([]); - expect(logWarnStub.calledOnce).to.equal(true); - }); - - it('accepts mixed set of adunits', function() { - const adUnits = [{ - code: 'test3', - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 300]], - adPodDurationSec: 360, - durationRangeSec: [15, 30, 45], - requireExactDuration: true - } - } - }, { - code: 'test4', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }]; - - checkAdUnitSetupHook(callbackFn, adUnits); - - expect(results).to.deep.equal(adUnits); - expect(logWarnStub.called).to.equal(false); - }); - }); - - describe('checkVideoBidSetupHook', function () { - let callbackResult; - let bailResult; - const callbackFn = { - call: function(context, bid) { - callbackResult = bid; - }, - bail: function(result) { - bailResult = result; - } - } - const adpodTestBid = { - video: { - context: ADPOD, - durationSeconds: 15, - durationBucket: 15 - }, - meta: { - primaryCatId: 'testCategory_123' - }, - vastXml: 'test XML here' - }; - const adUnitNoExact = { - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 400]], - durationRangeSec: [15, 45], - requireExactDuration: false, - adPodDurationSec: 300 - } - } - }; - const adUnitWithExact = { - mediaTypes: { - video: { - context: ADPOD, - playerSize: [[300, 400]], - durationRangeSec: [15, 30, 45, 60], - requireExactDuration: true, - adPodDurationSec: 300 - } - } - }; - - beforeEach(function() { - callbackResult = null; - bailResult = null; - config.setConfig({ - cache: { - url: 'https://test.cache.url/endpoint' - }, - adpod: { - brandCategoryExclusion: true - } - }); - logWarnStub = sinon.stub(utils, 'logWarn'); - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function() { - config.resetConfig(); - logWarnStub.restore(); - logErrorStub.restore(); - }) - - it('redirects to original function for non-adpod type video bids', function() { - const bannerTestBid = { - mediaType: 'video' - }; - checkVideoBidSetupHook(callbackFn, bannerTestBid, {}, {}, 'instream'); - expect(callbackResult).to.deep.equal(bannerTestBid); - expect(bailResult).to.be.null; - expect(logErrorStub.called).to.equal(false); - }); - - it('returns true when adpod bid is properly setup', function() { - config.setConfig({ - cache: { - url: 'https://test.cache.url/endpoint' - }, - adpod: { - brandCategoryExclusion: false - } - }); - - const goodBid = utils.deepClone(adpodTestBid); - goodBid.meta.primaryCatId = undefined; - checkVideoBidSetupHook(callbackFn, goodBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); - expect(callbackResult).to.be.null; - expect(bailResult).to.equal(true); - expect(logErrorStub.called).to.equal(false); - }); - - it('returns true when adpod bid is missing iab category while brandCategoryExclusion in config is false', function() { - const goodBid = utils.deepClone(adpodTestBid); - checkVideoBidSetupHook(callbackFn, goodBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); - expect(callbackResult).to.be.null; - expect(bailResult).to.equal(true); - expect(logErrorStub.called).to.equal(false); - }); - - it('returns false when a required property from an adpod bid is missing', function() { - function testInvalidAdpodBid(badTestBid, shouldErrorBeLogged) { - checkVideoBidSetupHook(callbackFn, badTestBid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); - expect(callbackResult).to.be.null; - expect(bailResult).to.equal(false); - expect(logErrorStub.called).to.equal(shouldErrorBeLogged); - } - - const noCatBid = utils.deepClone(adpodTestBid); - noCatBid.meta.primaryCatId = undefined; - testInvalidAdpodBid(noCatBid, false); - - const noContextBid = utils.deepClone(adpodTestBid); - delete noContextBid.video.context; - testInvalidAdpodBid(noContextBid, false); - - const wrongContextBid = utils.deepClone(adpodTestBid); - wrongContextBid.video.context = 'instream'; - testInvalidAdpodBid(wrongContextBid, false); - - const noDurationBid = utils.deepClone(adpodTestBid); - delete noDurationBid.video.durationSeconds; - testInvalidAdpodBid(noDurationBid, false); - - config.resetConfig(); - const noCacheUrlBid = utils.deepClone(adpodTestBid); - testInvalidAdpodBid(noCacheUrlBid, true); - }); - - describe('checkBidDuration', function() { - const basicBid = { - video: { - context: ADPOD, - durationSeconds: 30 - }, - meta: { - primaryCatId: 'testCategory_123' - }, - vastXml: '' - }; - - it('when requireExactDuration is true', function() { - const goodBid = utils.deepClone(basicBid); - checkVideoBidSetupHook(callbackFn, goodBid, adUnitWithExact, adUnitWithExact.mediaTypes.video, ADPOD); - - expect(callbackResult).to.be.null; - expect(goodBid.video.durationBucket).to.equal(30); - expect(bailResult).to.equal(true); - expect(logWarnStub.called).to.equal(false); - - const badBid = utils.deepClone(basicBid); - badBid.video.durationSeconds = 14; - checkVideoBidSetupHook(callbackFn, badBid, adUnitWithExact, adUnitWithExact.mediaTypes.video, ADPOD); - - expect(callbackResult).to.be.null; - expect(badBid.video.durationBucket).to.be.undefined; - expect(bailResult).to.equal(false); - expect(logWarnStub.calledOnce).to.equal(true); - }); - - it('when requireExactDuration is false and bids are bucketed properly', function() { - function testRoundingForGoodBId(bid, bucketValue) { - checkVideoBidSetupHook(callbackFn, bid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); - expect(callbackResult).to.be.null; - expect(bid.video.durationBucket).to.equal(bucketValue); - expect(bailResult).to.equal(true); - expect(logWarnStub.called).to.equal(false); - } - - const goodBid45 = utils.deepClone(basicBid); - goodBid45.video.durationSeconds = 45; - testRoundingForGoodBId(goodBid45, 45); - - const goodBid30 = utils.deepClone(basicBid); - goodBid30.video.durationSeconds = 30; - testRoundingForGoodBId(goodBid30, 45); - - const goodBid10 = utils.deepClone(basicBid); - goodBid10.video.durationSeconds = 10; - testRoundingForGoodBId(goodBid10, 15); - - const goodBid16 = utils.deepClone(basicBid); - goodBid16.video.durationSeconds = 16; - testRoundingForGoodBId(goodBid16, 15); - - const goodBid47 = utils.deepClone(basicBid); - goodBid47.video.durationSeconds = 47; - testRoundingForGoodBId(goodBid47, 45); - }); - - it('when requireExactDuration is false and bid duration exceeds listed buckets', function() { - function testRoundingForBadBid(bid) { - checkVideoBidSetupHook(callbackFn, bid, adUnitNoExact, adUnitNoExact.mediaTypes.video, ADPOD); - expect(callbackResult).to.be.null; - expect(bid.video.durationBucket).to.be.undefined; - expect(bailResult).to.equal(false); - expect(logWarnStub.called).to.equal(true); - } - - const badBid100 = utils.deepClone(basicBid); - badBid100.video.durationSeconds = 100; - testRoundingForBadBid(badBid100); - - const badBid48 = utils.deepClone(basicBid); - badBid48.video.durationSeconds = 48; - testRoundingForBadBid(badBid48); - }); - }); - }); - - describe('adpodSetConfig', function () { - let logWarnStub; - beforeEach(function() { - logWarnStub = sinon.stub(utils, 'logWarn'); - }); - - afterEach(function () { - logWarnStub.restore(); - }); - - it('should log a warning when values other than numbers are used in setConfig', function() { - adpodSetConfig({ - bidQueueSizeLimit: '2', - bidQueueTimeDelay: '50' - }); - expect(logWarnStub.calledTwice).to.equal(true); - }); - - it('should log a warning when numbers less than or equal to zero are used in setConfig', function() { - adpodSetConfig({ - bidQueueSizeLimit: 0, - bidQueueTimeDelay: -2 - }); - expect(logWarnStub.calledTwice).to.equal(true); - }); - - it('should not log any warning when using a valid config', function() { - adpodSetConfig({ - bidQueueSizeLimit: 10 - }); - expect(logWarnStub.called).to.equal(false); - - adpodSetConfig({ - bidQueueTimeDelay: 100, - bidQueueSizeLimit: 20 - }); - expect(logWarnStub.called).to.equal(false); - }) - }); - - describe('adpod utils', function() { - it('should sort bids array', function() { - const bids = [{ - cpm: 10.12345, - adserverTargeting: { - hb_pb: '10.00', - }, - video: { - durationBucket: 15 - } - }, { - cpm: 15, - adserverTargeting: { - hb_pb: '15.00', - }, - video: { - durationBucket: 15 - } - }, { - cpm: 15.00, - adserverTargeting: { - hb_pb: '15.00', - }, - video: { - durationBucket: 30 - } - }, { - cpm: 5.45, - adserverTargeting: { - hb_pb: '5.00', - }, - video: { - durationBucket: 5 - } - }, { - cpm: 20.1234567, - adserverTargeting: { - hb_pb: '20.10', - }, - video: { - durationBucket: 60 - } - }] - bids.sort(sortByPricePerSecond); - const sortedBids = [{ - cpm: 15, - adserverTargeting: { - hb_pb: '15.00', - }, - video: { - durationBucket: 15 - } - }, { - cpm: 5.45, - adserverTargeting: { - hb_pb: '5.00', - }, - video: { - durationBucket: 5 - } - }, { - cpm: 10.12345, - adserverTargeting: { - hb_pb: '10.00', - }, - video: { - durationBucket: 15 - } - }, { - cpm: 15.00, - adserverTargeting: { - hb_pb: '15.00', - }, - video: { - durationBucket: 30 - } - }, { - cpm: 20.1234567, - adserverTargeting: { - hb_pb: '20.10', - }, - video: { - durationBucket: 60 - } - }] - expect(bids).to.include.deep.ordered.members(sortedBids); - }); - }) -}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index bcce942f47f..eed27cdd57b 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -52,24 +52,6 @@ const VIDEO_REQUEST = { 'ortb2Imp': { 'ext': { 'gpid': '12345/adunit-code' } }, }; -const ADPOD_REQUEST = { - 'bidder': 'adtelligent', - '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': [{ @@ -310,13 +292,6 @@ describe('adtelligentBidAdapter', () => { 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; @@ -454,12 +429,6 @@ describe('adtelligentBidAdapter', () => { nobidServerResponseCheck(); }); - - 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('outstream setup', () => { const videoBids = spec.interpretResponse({ body: SERVER_OUSTREAM_VIDEO_RESPONSE }, { adapterRequest: outstreamVideoBidderRequest }); it('should return renderer with expected outstream params config', () => { diff --git a/test/spec/modules/adtrgtmeBidAdapter_spec.js b/test/spec/modules/adtrgtmeBidAdapter_spec.js index 95c40d66bb1..7b6e808dcaa 100644 --- a/test/spec/modules/adtrgtmeBidAdapter_spec.js +++ b/test/spec/modules/adtrgtmeBidAdapter_spec.js @@ -562,7 +562,7 @@ describe('Adtrgtme Bid Adapter:', () => { }); expect(data.device).to.deep.equal({ - dnt: 0, + dnt: 0, // DNT deprecated by W3C; Prebid no longer supports DNT ua: navigator.userAgent, ip: undefined }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 2c5d279c7bb..9ac4e762d9b 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -536,185 +536,6 @@ describe('AppNexusAdapter', function () { playback_method: 2 }); }); - - it('should duplicate adpod placements into batches and set correct maxduration', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - - // 300 / 15 = 20 total - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - expect(payload1.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload1.tags[0].video.maxduration).to.equal(30); - - expect(payload2.tags[0]).to.deep.equal(payload1.tags[1]); - expect(payload2.tags[0].video.maxduration).to.equal(30); - }); - - it('should round down adpod placements when numbers are uneven', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 123, - durationRangeSec: [45], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(2); - }); - - it('should duplicate adpod placements when requireExactDuration is set', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - requireExactDuration: true, - } - } - } - ); - - // 20 total placements with 15 max impressions = 2 requests - const request = spec.buildRequests([bidRequest]); - expect(request.length).to.equal(2); - - // 20 spread over 2 requests = 15 in first request, 5 in second - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(5); - - // 10 placements should have max/min at 15 - // 10 placemenst should have max/min at 30 - const payload1tagsWith15 = payload1.tags.filter(tag => tag.video.maxduration === 15); - const payload1tagsWith30 = payload1.tags.filter(tag => tag.video.maxduration === 30); - expect(payload1tagsWith15.length).to.equal(10); - expect(payload1tagsWith30.length).to.equal(5); - - // 5 placemenst with min/max at 30 were in the first request - // so 5 remaining should be in the second - const payload2tagsWith30 = payload2.tags.filter(tag => tag.video.maxduration === 30); - expect(payload2tagsWith30.length).to.equal(5); - }); - - it('should set durations for placements when requireExactDuration is set and numbers are uneven', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 105, - durationRangeSec: [15, 30, 60], - requireExactDuration: true, - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - expect(payload.tags.length).to.equal(7); - - const tagsWith15 = payload.tags.filter(tag => tag.video.maxduration === 15); - const tagsWith30 = payload.tags.filter(tag => tag.video.maxduration === 30); - const tagsWith60 = payload.tags.filter(tag => tag.video.maxduration === 60); - expect(tagsWith15.length).to.equal(3); - expect(tagsWith30.length).to.equal(3); - expect(tagsWith60.length).to.equal(1); - }); - - it('should break adpod request into batches', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 225, - durationRangeSec: [5], - } - } - } - ); - - const request = spec.buildRequests([bidRequest]); - const payload1 = JSON.parse(request[0].data); - const payload2 = JSON.parse(request[1].data); - const payload3 = JSON.parse(request[2].data); - - expect(payload1.tags.length).to.equal(15); - expect(payload2.tags.length).to.equal(15); - expect(payload3.tags.length).to.equal(15); - }); - - it('should contain hb_source value for adpod', function () { - const bidRequest = Object.assign({}, - bidRequests[0], - { - params: { placementId: '14542875' } - }, - { - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 300, - durationRangeSec: [15, 30], - } - } - } - ); - const request = spec.buildRequests([bidRequest])[0]; - const payload = JSON.parse(request.data); - expect(payload.tags[0].hb_source).to.deep.equal(7); - }); } // VIDEO it('sends bid request to ENDPOINT via POST', function () { @@ -817,21 +638,6 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].hb_source).to.deep.equal(1); }); - it('adds brand_category_exclusion to request when set', function () { - const bidRequest = Object.assign({}, bidRequests[0]); - sinon - .stub(config, 'getConfig') - .withArgs('adpod.brandCategoryExclusion') - .returns(true); - - const request = spec.buildRequests([bidRequest]); - const payload = JSON.parse(request.data); - - expect(payload.brand_category_uniqueness).to.equal(true); - - config.getConfig.restore(); - }); - it('adds auction level keywords and ortb2 keywords to request when set', function () { const bidRequest = Object.assign({}, bidRequests[0]); sinon @@ -2024,46 +1830,6 @@ describe('AppNexusAdapter', function () { expect(result[0]).to.have.property('vastImpUrl'); expect(result[0]).to.have.property('mediaType', 'video'); }); - - it('handles adpod responses', function () { - const response = { - 'tags': [{ - 'uuid': '84ab500420319d', - 'ads': [{ - 'ad_type': 'video', - 'brand_category_id': 10, - 'cpm': 0.500000, - 'notify_url': 'imptracker.com', - 'rtb': { - 'video': { - 'asset_url': 'https://sample.vastURL.com/here/adpod', - 'duration_ms': 30000, - } - }, - 'viewability': { - 'config': '' - } - }] - }] - }; - - const bidderRequest = { - bids: [{ - bidId: '84ab500420319d', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - }; - - const result = spec.interpretResponse({ body: response }, { bidderRequest }); - expect(result[0]).to.have.property('vastUrl'); - expect(result[0].video.context).to.equal('adpod'); - expect(result[0].video.durationSeconds).to.equal(30); - }); } if (FEATURES.NATIVE) { @@ -2377,33 +2143,6 @@ describe('AppNexusAdapter', function () { bidderRequest.bids[0].renderer.options ); }); - - it('should add deal_priority and deal_code', function () { - const responseWithDeal = deepClone(response); - responseWithDeal.tags[0].ads[0].ad_type = 'video'; - responseWithDeal.tags[0].ads[0].deal_priority = 5; - responseWithDeal.tags[0].ads[0].deal_code = '123'; - responseWithDeal.tags[0].ads[0].rtb.video = { - duration_ms: 1500, - player_width: 640, - player_height: 340, - }; - - const bidderRequest = { - bids: [{ - bidId: '3db3773286ee59', - adUnitCode: 'code', - mediaTypes: { - video: { - context: 'adpod' - } - } - }] - } - const result = spec.interpretResponse({ body: responseWithDeal }, { bidderRequest }); - expect(Object.keys(result[0].appnexus)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); - expect(result[0].video.dealTier).to.equal(5); - }); } it('should add advertiser id', function () { diff --git a/test/spec/modules/bidViewabilityIO_spec.js b/test/spec/modules/bidViewabilityIO_spec.js index 947d9227ca5..f76a0c6230b 100644 --- a/test/spec/modules/bidViewabilityIO_spec.js +++ b/test/spec/modules/bidViewabilityIO_spec.js @@ -4,6 +4,9 @@ import * as utils from 'src/utils.js'; import * as sinon from 'sinon'; import { expect } from 'chai'; import { EVENTS } from 'src/constants.js'; +import { EVENT_TYPE_VIEWABLE, TRACKER_METHOD_IMG } from 'src/eventTrackers.js'; +import * as bidViewability from '../../../modules/bidViewability.js'; +import adapterManager from '../../../src/adapterManager.js'; describe('#bidViewabilityIO', function() { const makeElement = (id) => { @@ -101,6 +104,71 @@ describe('#bidViewabilityIO', function() { }); }) + describe('viewability pixels', function() { + let sandbox; + let triggerPixelSpy; + const mockObserver = { unobserve: sinon.spy() }; + const mockEntry = { target: makeElement('pixel_target_id') }; + + const VIEWABILITY_PIXEL_URLS = [ + 'https://io-viewable-1.com/pixel', + 'https://io-viewable-2.com/track' + ]; + + const bidWithEventTrackers = { + adUnitCode: 'banner_id', + mediaType: 'banner', + width: 728, + height: 90, + eventtrackers: VIEWABILITY_PIXEL_URLS.map(url => ({ event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url })) + }; + + beforeEach(function() { + sandbox = sinon.createSandbox(); + triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); + }); + + afterEach(function() { + sandbox.restore(); + }); + + it('fires viewability pixels when markViewed callback runs with bid that has eventTrackers (EVENT_TYPE_VIEWABLE)', function() { + const func = bidViewabilityIO.markViewed(bidWithEventTrackers, mockEntry, mockObserver); + func(); + expect(triggerPixelSpy.callCount).to.equal(VIEWABILITY_PIXEL_URLS.length); + VIEWABILITY_PIXEL_URLS.forEach((url, i) => { + expect(triggerPixelSpy.getCall(i).args[0]).to.equal(url); + }); + }); + + it('does not fire pixels when bid has empty eventTrackers', function() { + const bidWithEmptyTrackers = { ...banner_bid, eventtrackers: [] }; + const func = bidViewabilityIO.markViewed(bidWithEmptyTrackers, mockEntry, mockObserver); + func(); + expect(triggerPixelSpy.callCount).to.equal(0); + }); + + it('should call onBidViewable', () => { + sandbox.stub(adapterManager, 'callBidViewableBidder'); + const bid = { + bidder: 'mockBidder', + ...banner_bid + } + bidViewabilityIO.markViewed(bid, mockEntry, mockObserver)(); + sinon.assert.calledWith(adapterManager.callBidViewableBidder, 'mockBidder', bid); + }); + + it('should call the triggerBilling function if the viewable bid has deferBilling set to true', function() { + sandbox.stub(adapterManager, 'triggerBilling'); + const bid = { + ...banner_bid, + deferBilling: true + } + bidViewabilityIO.markViewed(bid, mockEntry, mockObserver)(); + sinon.assert.called(adapterManager.triggerBilling); + }); + }) + describe('viewCallbackFactory tests', function() { let sandbox; diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 3c64d1e6781..11257c0d03a 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -8,6 +8,7 @@ import * as prebidGlobal from 'src/prebidGlobal.js'; import { EVENTS } from 'src/constants.js'; import adapterManager, { gdprDataHandler, uspDataHandler } from 'src/adapterManager.js'; import parse from 'url-parse'; +import { EVENT_TYPE_VIEWABLE, TRACKER_METHOD_IMG } from 'src/eventTrackers.js'; const GPT_SLOT = { getAdUnitPath() { @@ -23,6 +24,12 @@ const EVENT_OBJ = { slot: GPT_SLOT } +const VIEWABILITY_PIXEL_URLS = [ + 'https://domain-1.com/end-point?a=1', + 'https://domain-2.com/end-point/', + 'https://domain-3.com/end-point?a=1' +]; + const PBJS_WINNING_BID = { 'adUnitCode': '/harshad/Jan/2021/', 'bidderCode': 'pubmatic', @@ -39,11 +46,7 @@ const PBJS_WINNING_BID = { 'creativeId': 'id', 'netRevenue': true, 'currency': 'USD', - 'vurls': [ - 'https://domain-1.com/end-point?a=1', - 'https://domain-2.com/end-point/', - 'https://domain-3.com/end-point?a=1' - ] + 'eventtrackers': VIEWABILITY_PIXEL_URLS.map(url => ({ event: EVENT_TYPE_VIEWABLE, method: TRACKER_METHOD_IMG, url })) }; describe('#bidViewability', function() { @@ -55,22 +58,6 @@ describe('#bidViewability', function() { pbjsWinningBid = Object.assign({}, PBJS_WINNING_BID); }); - describe('isBidAdUnitCodeMatchingSlot', function() { - it('match found by GPT Slot getAdUnitPath', function() { - expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); - }); - - it('match found by GPT Slot getSlotElementId', function() { - pbjsWinningBid.adUnitCode = 'DIV-1'; - expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(true); - }); - - it('match not found', function() { - pbjsWinningBid.adUnitCode = 'DIV-10'; - expect(bidViewability.isBidAdUnitCodeMatchingSlot(pbjsWinningBid, gptSlot)).to.equal(false); - }); - }); - describe('getMatchingWinningBidForGPTSlot', function() { let winningBidsArray; let sandbox @@ -89,159 +76,44 @@ describe('#bidViewability', function() { sandbox.restore(); }) - it('should find a match by using customMatchFunction provided in config', function() { - // Needs config to be passed with customMatchFunction - const bidViewabilityConfig = { - customMatchFunction(bid, slot) { - return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; + it('should find a match by using customGptSlotMatching provided in config', function() { + config.setConfig({ + customGptSlotMatching: slot => { + return (adUnitCode) => ('AD-' + slot.getAdUnitPath()) === adUnitCode; } - }; + }); const newWinningBid = Object.assign({}, PBJS_WINNING_BID, { adUnitCode: 'AD-' + PBJS_WINNING_BID.adUnitCode }); // Needs pbjs.getWinningBids to be implemented with match winningBidsArray.push(newWinningBid); - const wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + const wb = bidViewability.getMatchingWinningBidForGPTSlot(gptSlot); expect(wb).to.deep.equal(newWinningBid); + config.resetConfig(); }); - it('should NOT find a match by using customMatchFunction provided in config', function() { - // Needs config to be passed with customMatchFunction - const bidViewabilityConfig = { - customMatchFunction(bid, slot) { - return ('AD-' + slot.getAdUnitPath()) === bid.adUnitCode; - } - }; - // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach - const wb = bidViewability.getMatchingWinningBidForGPTSlot(bidViewabilityConfig, gptSlot); + it('should NOT find a match when customGptSlotMatching is set and no winning bid matches', function() { + config.setConfig({ + customGptSlotMatching: slot => (adUnitCode) => ('AD-' + slot.getAdUnitPath()) === adUnitCode + }); + // winningBidsArray is empty in beforeEach, so no bid matches + const wb = bidViewability.getMatchingWinningBidForGPTSlot(gptSlot); expect(wb).to.equal(null); + config.resetConfig(); }); it('should find a match by using default matching function', function() { - // Needs config to be passed without customMatchFunction - // Needs pbjs.getWinningBids to be implemented with match + // No customGptSlotMatching in config; pbjs.getWinningBids returns matching bid winningBidsArray.push(PBJS_WINNING_BID); - const wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + const wb = bidViewability.getMatchingWinningBidForGPTSlot(gptSlot); expect(wb).to.deep.equal(PBJS_WINNING_BID); }); it('should NOT find a match by using default matching function', function() { - // Needs config to be passed without customMatchFunction - // Needs pbjs.getWinningBids to be implemented without match; winningBidsArray is set to empty in beforeEach - const wb = bidViewability.getMatchingWinningBidForGPTSlot({}, gptSlot); + // No customGptSlotMatching; winningBidsArray is empty in beforeEach + const wb = bidViewability.getMatchingWinningBidForGPTSlot(gptSlot); expect(wb).to.equal(null); }); }); - describe('fireViewabilityPixels', function() { - let sandbox; - let triggerPixelSpy; - - beforeEach(function() { - sandbox = sinon.createSandbox(); - triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); - }); - - afterEach(function() { - sandbox.restore(); - }); - - it('DO NOT fire pixels if NOT mentioned in module config', function() { - const moduleConfig = {}; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - expect(triggerPixelSpy.callCount).to.equal(0); - }); - - it('fire pixels if mentioned in module config', function() { - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0]).to.equal(url); - }); - }); - - it('USP: should include the us_privacy key when USP Consent is available', function () { - const uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); - uspDataHandlerStub.returns('1YYY'); - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0].indexOf(url)).to.equal(0); - const testurl = parse(call.args[0]); - const queryObject = utils.parseQS(testurl.query); - expect(queryObject.us_privacy).to.equal('1YYY'); - }); - uspDataHandlerStub.restore(); - }); - - it('USP: should not include the us_privacy key when USP Consent is not available', function () { - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0].indexOf(url)).to.equal(0); - const testurl = parse(call.args[0]); - const queryObject = utils.parseQS(testurl.query); - expect(queryObject.us_privacy).to.equal(undefined); - }); - }); - - it('GDPR: should include the GDPR keys when GDPR Consent is available', function() { - const gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ - gdprApplies: true, - consentString: 'consent', - addtlConsent: 'moreConsent' - }); - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0].indexOf(url)).to.equal(0); - const testurl = parse(call.args[0]); - const queryObject = utils.parseQS(testurl.query); - expect(queryObject.gdpr).to.equal('1'); - expect(queryObject.gdpr_consent).to.equal('consent'); - expect(queryObject.addtl_consent).to.equal('moreConsent'); - }); - gdprDataHandlerStub.restore(); - }); - - it('GDPR: should not include the GDPR keys when GDPR Consent is not available', function () { - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0].indexOf(url)).to.equal(0); - const testurl = parse(call.args[0]); - const queryObject = utils.parseQS(testurl.query); - expect(queryObject.gdpr).to.equal(undefined); - expect(queryObject.gdpr_consent).to.equal(undefined); - expect(queryObject.addtl_consent).to.equal(undefined); - }); - }); - - it('GDPR: should only include the GDPR keys for GDPR Consent fields with values', function () { - const gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ - gdprApplies: true, - consentString: 'consent' - }); - const moduleConfig = { firePixels: true }; - bidViewability.fireViewabilityPixels(moduleConfig, PBJS_WINNING_BID); - PBJS_WINNING_BID.vurls.forEach((url, i) => { - const call = triggerPixelSpy.getCall(i); - expect(call.args[0].indexOf(url)).to.equal(0); - const testurl = parse(call.args[0]); - const queryObject = utils.parseQS(testurl.query); - expect(queryObject.gdpr).to.equal('1'); - expect(queryObject.gdpr_consent).to.equal('consent'); - expect(queryObject.addtl_consent).to.equal(undefined); - }); - gdprDataHandlerStub.restore(); - }) - }); - describe('impressionViewableHandler', function() { let sandbox; let triggerPixelSpy; @@ -288,7 +160,7 @@ describe('#bidViewability', function() { winningBidsArray.push(PBJS_WINNING_BID); bidViewability.impressionViewableHandler(moduleConfig, EVENT_OBJ); // fire pixels should be called - PBJS_WINNING_BID.vurls.forEach((url, i) => { + VIEWABILITY_PIXEL_URLS.forEach((url, i) => { const call = triggerPixelSpy.getCall(i); expect(call.args[0]).to.equal(url); }); diff --git a/test/spec/modules/categoryTranslation_spec.js b/test/spec/modules/categoryTranslation_spec.js deleted file mode 100644 index 6c3bc41c037..00000000000 --- a/test/spec/modules/categoryTranslation_spec.js +++ /dev/null @@ -1,109 +0,0 @@ -import { getAdserverCategoryHook, initTranslation, storage } from 'modules/categoryTranslation.js'; -import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { expect } from 'chai'; -import { server } from '../../mocks/xhr.js'; - -describe('category translation', function () { - let getLocalStorageStub; - - beforeEach(function () { - getLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); - }); - - afterEach(function() { - getLocalStorageStub.restore(); - config.resetConfig(); - }); - - it('should translate iab category to adserver category', function () { - config.setConfig({ - 'adpod': { - 'brandCategoryExclusion': true - } - }); - getLocalStorageStub.returns(JSON.stringify({ - 'mapping': { - 'iab-1': { - 'id': 1, - 'name': 'sample' - } - } - })); - const bid = { - meta: { - primaryCatId: 'iab-1' - } - } - getAdserverCategoryHook(sinon.spy(), 'code', bid); - expect(bid.meta.adServerCatId).to.equal(1); - }); - - it('should set adserverCatId to undefined if not found in mapping file', function() { - config.setConfig({ - 'adpod': { - 'brandCategoryExclusion': true - } - }); - getLocalStorageStub.returns(JSON.stringify({ - 'mapping': { - 'iab-1': { - 'id': 1, - 'name': 'sample' - } - } - })); - const bid = { - meta: { - primaryCatId: 'iab-2' - } - } - getAdserverCategoryHook(sinon.spy(), 'code', bid); - expect(bid.meta.adServerCatId).to.equal(undefined); - }); - - it('should not make ajax call to update mapping file if data found in localstorage and is not expired', function () { - const clock = sinon.useFakeTimers(utils.timestamp()); - getLocalStorageStub.returns(JSON.stringify({ - lastUpdated: utils.timestamp(), - mapping: { - 'iab-1': '1' - } - })); - initTranslation(); - expect(server.requests.length).to.equal(0); - clock.restore(); - }); - - it('should make ajax call to update mapping file if data found in localstorage is expired', function () { - const clock = sinon.useFakeTimers(utils.timestamp()); - getLocalStorageStub.returns(JSON.stringify({ - lastUpdated: utils.timestamp() - 2 * 24 * 60 * 60 * 1000, - mapping: { - 'iab-1': '1' - } - })); - initTranslation(); - expect(server.requests.length).to.equal(1); - clock.restore(); - }); - - it('should use default mapping file if publisher has not defined in config', function () { - getLocalStorageStub.returns(null); - initTranslation('http://sample.com', 'somekey'); - expect(server.requests.length).to.equal(1); - expect(server.requests[0].url).to.equal('http://sample.com/'); - }); - - it('should use publisher defined mapping file', function () { - config.setConfig({ - 'brandCategoryTranslation': { - 'translationFile': 'http://sample.com' - } - }); - getLocalStorageStub.returns(null); - initTranslation('http://sample.com', 'somekey'); - expect(server.requests.length).to.equal(2); - expect(server.requests[0].url).to.equal('http://sample.com/'); - }); -}); diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index d9742de3a5c..99acf6ec971 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -495,78 +495,4 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); }); - - describe('FLEDGE', function () { - it('should properly build a request when FLEDGE is enabled', async function () { - const bidderRequest = { - paapi: { - enabled: true - } - }; - const bids = [ - { - adUnitCode: 'banner', - auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa1', - bidId: '2e56e1af51ccc1', - bidder: 'ccx', - bidderRequestId: '17e7b9f58accc1', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - placementId: 609 - }, - sizes: [[300, 250]], - transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb1', - ortb2Imp: { - ext: { - ae: 1 - } - } - } - ]; - - const ortbRequest = spec.buildRequests(bids, await addFPDToBidderRequest(bidderRequest)); - const data = JSON.parse(ortbRequest.data); - expect(data.imp[0].ext.ae).to.equal(1); - }); - - it('should properly build a request when FLEDGE is disabled', async function () { - const bidderRequest = { - paapi: { - enabled: false - } - }; - const bids = [ - { - adUnitCode: 'banner', - auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa2', - bidId: '2e56e1af51ccc2', - bidder: 'ccx', - bidderRequestId: '17e7b9f58accc2', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - params: { - placementId: 610 - }, - sizes: [[300, 250]], - transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb2', - ortb2Imp: { - ext: { - ae: 1 - } - } - } - ]; - - const ortbRequest = spec.buildRequests(bids, await addFPDToBidderRequest(bidderRequest)); - const data = JSON.parse(ortbRequest.data); - expect(data.imp[0].ext.ae).to.be.undefined; - }); - }); }); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 3ba309f029f..54150aab322 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -3,6 +3,7 @@ import sinon from 'sinon'; import { spec, storage } from 'modules/concertBidAdapter.js'; import { hook } from 'src/hook.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; +import * as adUnits from 'src/utils/adUnits'; describe('ConcertAdapter', function () { let bidRequests; @@ -86,7 +87,7 @@ describe('ConcertAdapter', function () { } sandbox = sinon.createSandbox(); - sandbox.stub(document, 'getElementById').withArgs('desktop_leaderboard_variable').returns(element) + sandbox.stub(adUnits, 'getAdUnitElement').returns(element) }); afterEach(function () { diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 3f960825cd4..82479617769 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -17,6 +17,7 @@ import * as utils from '../../../src/utils.js'; import * as ajax from '../../../src/ajax.js'; import { ADPOD, BANNER, VIDEO } from '../../../src/mediaTypes.js'; import * as winDimensions from '../../../src/utils/winDimensions.js'; +import * as adUnits from 'src/utils/adUnits'; const BIDDER_CODE = 'connatix'; @@ -88,7 +89,7 @@ describe('connatixBidAdapter', function () { let getBoundingClientRectStub; let topWinMock; let querySelectorStub; - let getElementByIdStub; + let getElementStub; let sandbox; beforeEach(() => { @@ -105,7 +106,7 @@ describe('connatixBidAdapter', function () { }; querySelectorStub = sandbox.stub(window.top.document, 'querySelector'); - getElementByIdStub = sandbox.stub(document, 'getElementById'); + getElementStub = sandbox.stub(adUnits, 'getAdUnitElement'); sandbox.stub(winDimensions, 'getWinDimensions').callsFake(() => ( { document: { @@ -143,7 +144,7 @@ describe('connatixBidAdapter', function () { }); querySelectorStub.withArgs('#validElement').returns(element); - getElementByIdStub.returns(null); + getElementStub.returns(null); const result = connatixDetectViewability(bid); @@ -171,6 +172,7 @@ describe('connatixBidAdapter', function () { height: 250 }); + const getElementByIdStub = sandbox.stub(document, 'getElementById'); getElementByIdStub.withArgs('validElement').returns(element); const result = connatixDetectViewability(bid); @@ -201,7 +203,7 @@ describe('connatixBidAdapter', function () { }); querySelectorStub.withArgs('#invalidElement').returns(null); - getElementByIdStub.withArgs('adUnitCode123').returns(element); + getElementStub.returns(element); const result = connatixDetectViewability(bid); @@ -231,7 +233,7 @@ describe('connatixBidAdapter', function () { }); // The fallback should use the adUnitCode to find the element - getElementByIdStub.withArgs('adUnitCode123').returns(element); + getElementStub.returns(element); const result = connatixDetectViewability(bid); diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index 3d04b0f833c..a4c2a02f0b2 100644 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1872,70 +1872,6 @@ describe('The Criteo bidding adapter', function () { expect(ortbRequest.imp[0].ext?.rwdd).to.equal(0); }); - it('should properly build a request when FLEDGE is enabled', async function () { - const bidderRequest = { - paapi: { - enabled: true - } - }; - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123 - }, - ortb2Imp: { - ext: { - igs: { - ae: 1 - } - } - } - }, - ]; - - const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; - expect(ortbRequest.imp[0].ext.igs.ae).to.equal(1); - }); - - it('should properly build a request when FLEDGE is disabled', async function () { - const bidderRequest = { - paapi: { - enabled: false - }, - }; - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123 - }, - ortb2Imp: { - ext: { - igs: { - ae: 1 - } - } - } - }, - ]; - - const ortbRequest = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)).data; - expect(ortbRequest.imp[0].ext.igs?.ae).to.be.undefined; - }); - it('should properly transmit the pubid and slot uid if available', async function () { const bidderRequest = { ortb2: { @@ -2462,154 +2398,6 @@ describe('The Criteo bidding adapter', function () { }); } - it('should properly parse a bid response with FLEDGE auction configs', async function () { - const 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', - }; - const 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 bidRequests = [ - { - bidId: 'test-bidId', - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - bidFloor: 1, - 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 response = { - ext: { - igi: [{ - impid: 'test-bidId', - igs: [{ - impid: 'test-bidId', - bidId: 'test-bidId', - config: auctionConfig1 - }] - }, { - impid: 'test-bidId-2', - igs: [{ - impid: 'test-bidId-2', - bidId: 'test-bidId-2', - config: auctionConfig2 - }] - }] - }, - }; - const request = spec.buildRequests(bidRequests, await addFPDToBidderRequest(bidderRequest)); - const interpretedResponse = spec.interpretResponse({ body: response }, request); - expect(interpretedResponse).to.have.property('bids'); - expect(interpretedResponse).to.have.property('paapi'); - expect(interpretedResponse.bids).to.have.lengthOf(0); - expect(interpretedResponse.paapi).to.have.lengthOf(2); - expect(interpretedResponse.paapi[0]).to.deep.equal({ - bidId: 'test-bidId', - impid: 'test-bidId', - config: auctionConfig1, - }); - expect(interpretedResponse.paapi[1]).to.deep.equal({ - bidId: 'test-bidId-2', - impid: 'test-bidId-2', - config: auctionConfig2, - }); - }); - [{ hasBidResponseLevelPafData: true, hasBidResponseBidLevelPafData: true, diff --git a/test/spec/modules/cwireBidAdapter_spec.js b/test/spec/modules/cwireBidAdapter_spec.js index 1a514d33155..6943618cf51 100644 --- a/test/spec/modules/cwireBidAdapter_spec.js +++ b/test/spec/modules/cwireBidAdapter_spec.js @@ -6,6 +6,7 @@ import * as utils from "src/utils.js"; import sinon, { stub } from "sinon"; import { config } from "../../../src/config.js"; import * as autoplayLib from "../../../libraries/autoplayDetection/autoplay.js"; +import * as adUnits from 'src/utils/adUnits'; describe("C-WIRE bid adapter", () => { config.setConfig({ debug: true }); @@ -101,8 +102,8 @@ describe("C-WIRE bid adapter", () => { describe("buildRequests reads adUnit offsetWidth and offsetHeight", function () { beforeEach(function () { - const documentStub = sandbox.stub(document, "getElementById"); - documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + const documentStub = sandbox.stub(adUnits, "getAdUnitElement"); + documentStub.returns({ offsetWidth: 200, offsetHeight: 250, getBoundingClientRect() { @@ -115,11 +116,9 @@ describe("C-WIRE bid adapter", () => { const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); - const el = document.getElementById(`${bidRequest.adUnitCode}`); logInfo(JSON.stringify(payload)); - expect(el).to.exist; expect(payload.slots[0].cwExt.dimensions.width).to.equal(200); expect(payload.slots[0].cwExt.dimensions.height).to.equal(250); expect(payload.slots[0].cwExt.style.maxHeight).to.not.exist; @@ -131,8 +130,8 @@ describe("C-WIRE bid adapter", () => { }); describe("buildRequests reads style attributes", function () { beforeEach(function () { - const documentStub = sandbox.stub(document, "getElementById"); - documentStub.withArgs(`${bidRequests[0].adUnitCode}`).returns({ + const documentStub = sandbox.stub(adUnits, "getAdUnitElement"); + documentStub.returns({ style: { maxWidth: "400px", maxHeight: "350px", @@ -147,11 +146,8 @@ describe("C-WIRE bid adapter", () => { const request = spec.buildRequests([bidRequest], bidderRequest); const payload = JSON.parse(request.data); - const el = document.getElementById(`${bidRequest.adUnitCode}`); - logInfo(JSON.stringify(payload)); - expect(el).to.exist; expect(payload.slots[0].cwExt.style.maxWidth).to.eq("400px"); expect(payload.slots[0].cwExt.style.maxHeight).to.eq("350px"); }); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index 39737272bed..ee8089b7a10 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -111,8 +111,8 @@ describe('bid interceptor', () => { }); describe('rule', () => { - function matchingRule({ replace, options, paapi }) { - setRules({ when: {}, then: replace, options: options, paapi }); + function matchingRule({ replace, options }) { + setRules({ when: {}, then: replace, options: options }); return interceptor.match({}); } @@ -172,48 +172,6 @@ describe('bid interceptor', () => { }); }); - describe('paapi', () => { - it('should accept literals', () => { - const mockConfig = [ - { config: { paapi: 1 } }, - { config: { 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; - }); - - Object.entries({ - 'literal': (cfg) => [cfg], - 'function': (cfg) => () => [cfg] - }).forEach(([t, makeConfigs]) => { - describe(`when paapi is defined as a ${t}`, () => { - it('should wrap top-level configs in "config"', () => { - const cfg = { decisionLogicURL: 'example' }; - expect(matchingRule({ paapi: makeConfigs(cfg) }).paapi({})).to.eql([{ - config: cfg - }]) - }); - - Object.entries({ - 'config': { config: 1 }, - 'igb': { igb: 1 }, - 'config and igb': { config: 1, igb: 2 } - }).forEach(([t, cfg]) => { - it(`should not wrap configs that define top-level ${t}`, () => { - expect(matchingRule({ paapi: makeConfigs(cfg) }).paapi({})).to.eql([cfg]); - }) - }) - }) - }) - }) - describe('.options', () => { it('should include default rule options', () => { const optDef = { someOption: 'value' }; @@ -231,17 +189,16 @@ describe('bid interceptor', () => { }); describe('intercept()', () => { - let done, addBid, addPaapiConfig; + let done, addBid; function intercept(args = {}) { const bidRequest = { bids: args.bids || [] }; - return interceptor.intercept(Object.assign({ bidRequest, done, addBid, addPaapiConfig }, args)); + return interceptor.intercept(Object.assign({ bidRequest, done, addBid }, args)); } beforeEach(() => { done = sinon.spy(); addBid = sinon.spy(); - addPaapiConfig = sinon.spy(); }); describe('on no match', () => { @@ -304,20 +261,6 @@ describe('bid interceptor', () => { }); }); - it('should call addPaapiConfigs when provided', () => { - const mockPaapiConfigs = [ - { config: { paapi: 1 } }, - { config: { 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 }, @@ -357,7 +300,7 @@ describe('Debugging config', () => { }); describe('bidderBidInterceptor', () => { - let next, interceptBids, onCompletion, interceptResult, done, addBid, wrapCallback, addPaapiConfig, wrapped, bidderBidInterceptor; + let next, interceptBids, onCompletion, interceptResult, done, addBid, wrapCallback, wrapped, bidderBidInterceptor; function interceptorArgs({ spec = {}, bids = [], bidRequest = {}, ajax = {}, cbs = {} } = {}) { return [next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, Object.assign({ onCompletion }, cbs)]; @@ -381,7 +324,6 @@ describe('bidderBidInterceptor', () => { interceptBids = sinon.stub().callsFake((opts) => { done = opts.done; addBid = opts.addBid; - addPaapiConfig = opts.addPaapiConfig; return interceptResult; }); onCompletion = sinon.spy(); @@ -400,15 +342,6 @@ describe('bidderBidInterceptor', () => { expect(onBid.calledWith(sinon.match.same(bid))).to.be.true; }); - it('should pass addPaapiConfig that triggers onPaapi', () => { - const onPaapi = sinon.stub().callsFake(() => { - expect(wrapped).to.be.true; - }); - bidderBidInterceptor(...interceptorArgs({ cbs: { onPaapi } })); - addPaapiConfig({ paapi: 'config' }, { bidId: 'bidId' }); - sinon.assert.calledWith(onPaapi, { paapi: 'config', bidId: 'bidId' }) - }) - describe('with no remaining bids', () => { it('should pass a done callback that triggers onCompletion', () => { bidderBidInterceptor(...interceptorArgs()); diff --git a/test/spec/modules/dmdIdSystem_spec.js b/test/spec/modules/dmdIdSystem_spec.js deleted file mode 100644 index c85ec522440..00000000000 --- a/test/spec/modules/dmdIdSystem_spec.js +++ /dev/null @@ -1,101 +0,0 @@ -import * as utils from '../../../src/utils.js'; -import { server } from 'test/mocks/xhr.js'; -import { dmdIdSubmodule } from 'modules/dmdIdSystem.js'; - -describe('Dmd ID System', function () { - let logErrorStub; - const config = { - params: { - api_key: '33344ffjddk22k22k222k22234k', - api_url: 'https://aix.hcn.health/api/v1/auths' - } - }; - - beforeEach(function () { - if (utils.logError.restore && utils.logError.restore.sinon) { - utils.logError.restore(); - } - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - if (logErrorStub && logErrorStub.restore) { - logErrorStub.restore(); - } - }); - - it('should log an error if no configParams were passed into getId', function () { - dmdIdSubmodule.getId(); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should log an error if configParams doesnot have api_key passed to getId', function () { - dmdIdSubmodule.getId({ params: {} }); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should log an error if configParams has invalid api_key passed into getId', function () { - dmdIdSubmodule.getId({ params: { api_key: 123 } }); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('should not log an error if configParams has valid api_key passed into getId', function () { - dmdIdSubmodule.getId({ params: { api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c' } }); - expect(logErrorStub.calledOnce).to.be.false; - }); - - it('should return undefined if empty value passed into decode', function () { - expect(dmdIdSubmodule.decode()).to.be.undefined; - }); - - it('should return undefined if invalid dmd-dgid passed into decode', function () { - expect(dmdIdSubmodule.decode(123)).to.be.undefined; - }); - - it('should return dmdId if valid dmd-dgid passed into decode', function () { - const data = { 'dmdId': 'U12345' }; - expect(dmdIdSubmodule.decode('U12345')).to.deep.equal(data); - }); - - it('should return cacheObj if cacheObj is passed into getId', function () { - const data = { 'dmdId': 'U12345' }; - expect(dmdIdSubmodule.getId(config, {}, { cookie: 'dmd-dgid' })).to.deep.equal({ cookie: 'dmd-dgid' }); - expect(server.requests.length).to.eq(0); - }); - - it('Should invoke callback with response from API call', function () { - const callbackSpy = sinon.spy(); - const domain = utils.getWindowLocation().href; - const callback = dmdIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.method).to.eq('GET'); - expect(request.requestHeaders['x-domain']).to.be.eq(domain); - expect(request.url).to.eq(config.params.api_url); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ dgid: 'U12345' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal('U12345'); - }); - - it('Should log error if API response is not valid', function () { - const callbackSpy = sinon.spy(); - const domain = utils.getWindowLocation().href; - const callback = dmdIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.method).to.eq('GET'); - expect(request.requestHeaders['x-domain']).to.be.eq(domain); - expect(request.url).to.eq(config.params.api_url); - request.respond(400, { 'Content-Type': 'application/json' }, undefined); - expect(logErrorStub.calledOnce).to.be.true; - }); - - it('Should log error if API call throws error', function () { - const callbackSpy = sinon.spy(); - const callback = dmdIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.url).to.eq(config.params.api_url); - request.error(); - expect(logErrorStub.calledOnce).to.be.true; - }); -}); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js deleted file mode 100644 index 0c65bbefc7e..00000000000 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ /dev/null @@ -1,350 +0,0 @@ -import { expect } from 'chai'; -import { adpodUtils } from 'modules/freeWheelAdserverVideo.js'; -import { auctionManager } from 'src/auctionManager.js'; -import { config } from 'src/config.js'; -import { server } from 'test/mocks/xhr.js'; - -describe('freeWheel adserver module', function() { - let amStub; - let amGetAdUnitsStub; - - before(function () { - const adUnits = [{ - code: 'preroll_1', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 60, - durationRangeSec: [15, 30], - requireExactDuration: true - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 14542875, - } - } - ] - }, { - code: 'midroll_1', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 60, - durationRangeSec: [15, 30], - requireExactDuration: true - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 14542875, - } - } - ] - }]; - - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); - amGetAdUnitsStub.returns(adUnits); - amStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - beforeEach(function () { - config.setConfig({ - adpod: { - brandCategoryExclusion: false, - deferCaching: false - } - }); - }) - - afterEach(function() { - config.resetConfig(); - }); - - after(function () { - amGetAdUnitsStub.restore(); - amStub.restore(); - }); - - it('should return targeting for all adunits', function() { - amStub.returns(getBidsReceived()); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(3); - }); - - it('should return targeting for passed adunit code', function() { - amStub.returns(getBidsReceived()); - let targeting; - adpodUtils.getTargeting({ - codes: ['preroll_1'], - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - expect(targeting['preroll_1']).to.exist; - expect(targeting['midroll_1']).to.not.exist; - }); - - it('should only use adpod bids', function() { - const bannerBid = [{ - 'ad': 'creative', - 'cpm': '1.99', - 'width': 300, - 'height': 250, - 'requestId': '1', - 'creativeId': 'some-id', - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 360, - 'bidderCode': 'appnexus', - 'statusMessage': 'Bid available', - 'adId': '28f24ced14586c', - 'adUnitCode': 'preroll_1' - }]; - amStub.returns(getBidsReceived().concat(bannerBid)); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(3); - }); - - it('should return unique category bids when competitive exclusion is enabled', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: false - } - }); - amStub.returns([ - createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395'), - createBid(15, 'preroll_1', 30, '15.00_395_30s', '123', '395'), - createBid(15, 'midroll_1', 60, '15.00_406_60s', '123', '406'), - createBid(10, 'preroll_1', 30, '10.00_395_30s', '123', '395') - ]); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(2); - }); - - it('should only select bids less than adpod duration', function() { - amStub.returns([ - createBid(10, 'preroll_1', 90, '10.00_395_90s', '123', '395'), - createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), - createBid(15, 'midroll_1', 90, '15.00_406_90s', '123', '406') - ]); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - expect(targeting['preroll_1']).to.be.empty; - expect(targeting['midroll_1']).to.be.empty; - }); - - it('should select bids when deferCaching is enabled', function() { - config.setConfig({ - adpod: { - deferCaching: true - } - }); - amStub.returns(getBidsReceived()); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - server.requests[0].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ 'responses': getBidsReceived().slice(0, 4) }) - ); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['midroll_1'].length).to.equal(3); - }); - - it('should prioritize bids with deal', function() { - config.setConfig({ - adpod: { - deferCaching: true, - prioritizeDeals: true - } - }); - - const tier6Bid = createBid(10, 'preroll_1', 15, 'tier6_395_15s', '123', '395'); - tier6Bid['video']['dealTier'] = 'tier6' - - const tier7Bid = createBid(11, 'preroll_1', 45, 'tier7_395_15s', '123', '395'); - tier7Bid['video']['dealTier'] = 'tier7' - - const bidsReceived = [ - tier6Bid, - tier7Bid, - createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), - ] - amStub.returns(bidsReceived); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - server.requests[0].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ 'responses': bidsReceived.slice(1) }) - ); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['preroll_1']).to.deep.include({ 'hb_pb_cat_dur': 'tier6_395_15s' }); - expect(targeting['preroll_1']).to.deep.include({ 'hb_pb_cat_dur': 'tier7_395_15s' }); - expect(targeting['preroll_1']).to.deep.include({ 'hb_cache_id': '123' }); - }); - - it('should apply minDealTier to bids if configured', function() { - config.setConfig({ - adpod: { - deferCaching: true, - prioritizeDeals: true, - dealTier: { - 'appnexus': { - prefix: 'tier', - minDealTier: 5 - } - } - } - }); - - const tier2Bid = createBid(10, 'preroll_1', 15, 'tier2_395_15s', '123', '395'); - tier2Bid['video']['dealTier'] = 2 - tier2Bid['adserverTargeting']['hb_pb'] = '10.00' - - const tier7Bid = createBid(11, 'preroll_1', 45, 'tier7_395_15s', '123', '395'); - tier7Bid['video']['dealTier'] = 7 - tier7Bid['adserverTargeting']['hb_pb'] = '11.00' - - const bid = createBid(15, 'preroll_1', 15, '15.00_395_90s', '123', '395'); - bid['adserverTargeting']['hb_pb'] = '15.00' - - const bidsReceived = [ - tier2Bid, - tier7Bid, - bid - ] - amStub.returns(bidsReceived); - let targeting; - adpodUtils.getTargeting({ - callback: function(errorMsg, targetingResult) { - targeting = targetingResult; - } - }); - - server.requests[0].respond( - 200, - { 'Content-Type': 'text/plain' }, - JSON.stringify({ 'responses': [tier7Bid, bid] }) - ); - - expect(targeting['preroll_1'].length).to.equal(3); - expect(targeting['preroll_1']).to.deep.include({ 'hb_pb_cat_dur': 'tier7_395_15s' }); - expect(targeting['preroll_1']).to.deep.include({ 'hb_pb_cat_dur': '15.00_395_90s' }); - expect(targeting['preroll_1']).to.not.include({ 'hb_pb_cat_dur': 'tier2_395_15s' }); - expect(targeting['preroll_1']).to.deep.include({ 'hb_cache_id': '123' }); - }) -}); - -function getBidsReceived() { - return [ - createBid(10, 'preroll_1', 15, '10.00_395_15s', '123', '395'), - createBid(15, 'preroll_1', 15, '15.00_395_15s', '123', '395'), - createBid(15, 'midroll_1', 30, '15.00_406_30s', '123', '406'), - createBid(5, 'midroll_1', 5, '5.00_406_5s', '123', '406'), - createBid(20, 'midroll_1', 60, '20.00_406_60s', '123', '406'), - ] -} - -function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, industry) { - return { - 'bidderCode': 'appnexus', - 'width': 640, - 'height': 360, - 'statusMessage': 'Bid available', - 'adId': '28f24ced14586c', - 'mediaType': 'video', - 'source': 'client', - 'requestId': '28f24ced14586c', - 'cpm': cpm, - 'creativeId': 97517771, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 3600, - 'adUnitCode': adUnitCode, - 'video': { - 'context': 'adpod', - 'durationBucket': durationBucket - }, - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'vastUrl': 'http://some-vast-url.com', - 'vastImpUrl': 'http://some-vast-imp-url.com', - 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', - 'responseTimestamp': 1548442460888, - 'requestTimestamp': 1548442460827, - 'bidder': 'appnexus', - 'timeToRespond': 61, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '640x360', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '28f24ced14586c', - 'hb_pb': '5.00', - 'hb_size': '640x360', - 'hb_source': 'client', - 'hb_format': 'video', - 'hb_pb_cat_dur': priceIndustryDuration, - 'hb_cache_id': uuid - }, - 'customCacheKey': `${priceIndustryDuration}_${uuid}`, - 'meta': { - 'primaryCatId': 'iab-1', - 'adServerCatId': industry - }, - 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' - } -} diff --git a/test/spec/modules/gamAdpod_spec.js b/test/spec/modules/gamAdpod_spec.js deleted file mode 100644 index bd32a23c956..00000000000 --- a/test/spec/modules/gamAdpod_spec.js +++ /dev/null @@ -1,257 +0,0 @@ -import { auctionManager } from '../../../src/auctionManager.js'; -import { config } from '../../../src/config.js'; -import { gdprDataHandler, uspDataHandler } from '../../../src/consentHandler.js'; -import parse from 'url-parse'; -import { buildAdpodVideoUrl } from '../../../modules/gamAdpod.js'; -import { expect } from 'chai/index.js'; -import * as utils from '../../../src/utils.js'; -import { server } from '../../mocks/xhr.js'; -import * as adpod from 'modules/adpod.js'; - -describe('gamAdpod', function () { - let amStub; - let amGetAdUnitsStub; - - before(function () { - const adUnits = [{ - code: 'adUnitCode-1', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 60, - durationRangeSec: [15, 30], - requireExactDuration: true - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 14542875, - } - } - ] - }]; - - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); - amGetAdUnitsStub.returns(adUnits); - amStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - beforeEach(function () { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: false - } - }); - }) - - afterEach(function() { - config.resetConfig(); - }); - - after(function () { - amGetAdUnitsStub.restore(); - amStub.restore(); - }); - - function getBidsReceived() { - return [ - createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), - ] - } - - function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { - return { - 'bidderCode': 'appnexus', - 'width': 640, - 'height': 360, - 'statusMessage': 'Bid available', - 'adId': '28f24ced14586c', - 'mediaType': 'video', - 'source': 'client', - 'requestId': '28f24ced14586c', - 'cpm': cpm, - 'creativeId': 97517771, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 3600, - 'adUnitCode': adUnitCode, - 'video': { - 'context': 'adpod', - 'durationBucket': durationBucket - }, - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'vastUrl': 'http://some-vast-url.com', - 'vastImpUrl': 'http://some-vast-imp-url.com', - 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', - 'responseTimestamp': 1548442460888, - 'requestTimestamp': 1548442460827, - 'bidder': 'appnexus', - 'timeToRespond': 61, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '640x360', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '28f24ced14586c', - 'hb_pb': hbpb, - 'hb_size': '640x360', - 'hb_source': 'client', - 'hb_format': 'video', - 'hb_pb_cat_dur': priceIndustryDuration, - 'hb_cache_id': uuid - }, - 'customCacheKey': `${priceIndustryDuration}_${uuid}`, - 'meta': { - 'primaryCatId': 'iab-1', - 'adServerCatId': label - }, - 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' - } - } - - it('should return masterTag url', function() { - amStub.returns(getBidsReceived()); - const uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); - uspDataHandlerStub.returns('1YYY'); - const gdprDataHandlerStub = sinon.stub(gdprDataHandler, 'getConsentData'); - gdprDataHandlerStub.returns({ - gdprApplies: true, - consentString: 'consent', - addtlConsent: 'moreConsent' - }); - let url; - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - expect(queryParams).to.have.property('env', 'vp'); - expect(queryParams).to.have.property('gdfp_req', '1'); - expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'vast'); - expect(queryParams).to.have.property('sz', '640x480'); - 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('gdpr', '1'); - expect(queryParams).to.have.property('gdpr_consent', 'consent'); - expect(queryParams).to.have.property('addtl_consent', 'moreConsent'); - - const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); - expect(custParams).to.have.property('hb_cache_id', '123'); - expect(custParams).to.have.property('hb_pb_cat_dur', '15.00_395_15s,15.00_406_30s,10.00_395_15s'); - uspDataHandlerStub.restore(); - gdprDataHandlerStub.restore(); - } - }); - - it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: false, - } - }); - function getBids() { - const bids = [ - createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), - ]; - bids.forEach((bid) => { - delete bid.meta; - }); - return bids; - } - amStub.returns(getBids()); - let url; - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - expect(queryParams).to.have.property('env', 'vp'); - expect(queryParams).to.have.property('gdfp_req', '1'); - expect(queryParams).to.have.property('iu', 'my/adUnit'); - expect(queryParams).to.have.property('output', 'xml_vast3'); - expect(queryParams).to.have.property('sz', '640x480'); - expect(queryParams).to.have.property('unviewed_position_start', '1'); - expect(queryParams).to.have.property('url'); - expect(queryParams).to.have.property('cust_params'); - - const custParams = utils.parseQS(decodeURIComponent(queryParams.cust_params)); - expect(custParams).to.have.property('hb_cache_id', '123'); - expect(custParams).to.have.property('hb_pb_cat_dur', '10.00_15s,15.00_15s,15.00_30s'); - } - }); - - it('should handle error when cache fails', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: true - } - }); - amStub.returns(getBidsReceived()); - - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - server.requests[0].respond(503, { - 'Content-Type': 'plain/text', - }, 'The server could not save anything at the moment.'); - - function handleResponse(err, masterTag) { - expect(masterTag).to.be.null; - expect(err).to.be.an('error'); - } - }); -}) diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index b3d0c20f3d4..e18ce39109a 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -72,7 +72,7 @@ describe('GmosspAdapter', 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('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&im_uid=h.0a4749e7ffe09fa6&shared_id=1111&idl_env=1111&url=https%3A%2F%2Fhoge.com' + '&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&dnt=0&'); + expect(requests[0].data).to.equal('tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&im_uid=h.0a4749e7ffe09fa6&shared_id=1111&idl_env=1111&url=https%3A%2F%2Fhoge.com' + '&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&'); }); it('should use fallback if refererInfo.referer in bid request is empty and im_uid ,shared_id, idl_env cookie is empty', function () { @@ -86,7 +86,7 @@ describe('GmosspAdapter', function () { bidRequests[0].userId.idl_env = ''; const requests = spec.buildRequests(bidRequests, bidderRequest); - const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&dnt=0&'; + const result = 'tid=791e9d84-af92-4903-94da-24c7426d9d0c&bid=2b84475b5b636e&ver=$prebid.version$&sid=123456&ref=' + encodeURIComponent(document.referrer) + '&cur=JPY&'; expect(requests[0].data).to.equal(result); }); }); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index e9063b746aa..78e169aa970 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -183,9 +183,8 @@ describe('GPT pre-auction module', () => { it('should use the customGptSlotMatching function if one is given', () => { config.setConfig({ - gptPreAuction: { - customGptSlotMatching: slot => - adUnitCode => adUnitCode.toUpperCase() === slot.getAdUnitPath().toUpperCase() + customGptSlotMatching: slot => { + return (adUnitCode) => adUnitCode.toUpperCase() === slot.getAdUnitPath().toUpperCase(); } }); @@ -194,6 +193,7 @@ describe('GPT pre-auction module', () => { appendGptSlots([adUnit]); expect(adUnit.ortb2Imp.ext.data.adserver).to.be.an('object'); expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: 'slotCode1' }); + config.resetConfig(); }); }); @@ -208,27 +208,14 @@ describe('GPT pre-auction module', () => { expect(_currentConfig).to.be.an('object').that.is.empty; }); - it('should accept custom functions in config', () => { - config.setConfig({ - gptPreAuction: { - customGptSlotMatching: () => 'customGptSlot', - } - }); - - expect(_currentConfig.enabled).to.equal(true); - expect(_currentConfig.customGptSlotMatching).to.a('function'); - expect(_currentConfig.customGptSlotMatching()).to.equal('customGptSlot'); - }); - it('should check that custom functions in config are type function', () => { config.setConfig({ gptPreAuction: { - customGptSlotMatching: 12345, + customPreAuction: 12345, } }); expect(_currentConfig).to.deep.equal({ enabled: true, - customGptSlotMatching: false, customPreAuction: false, useDefaultPreAuction: true }); diff --git a/test/spec/modules/growthCodeAnalyticsAdapter_spec.js b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js index 266bc104fd8..e871ec4c16d 100644 --- a/test/spec/modules/growthCodeAnalyticsAdapter_spec.js +++ b/test/spec/modules/growthCodeAnalyticsAdapter_spec.js @@ -22,7 +22,6 @@ describe('growthCode analytics adapter', () => { 'bidResponse', 'setTargeting', 'requestBids', - 'addAdUnits', 'noBid', 'bidWon', 'bidderDone'] diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index db043a88238..9a509542d8e 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -100,39 +100,6 @@ describe('gumgumAdapter', function () { describe('buildRequests', function () { const sizesArray = [[300, 250], [300, 600]]; - const id5Eid = { - source: 'id5-sync.com', - uids: [{ - id: 'uid-string', - ext: { - linkType: 2 - } - }] - }; - const pubProvidedIdEids = [ - { - uids: [ - { - ext: { - stype: 'ppuid', - }, - id: 'aac4504f-ef89-401b-a891-ada59db44336', - }, - ], - source: 'audigent.com', - }, - { - uids: [ - { - ext: { - stype: 'ppuid', - }, - id: 'y-zqTHmW9E2uG3jEETC6i6BjGcMhPXld2F~A', - }, - ], - source: 'crwdcntrl.net', - }, - ]; const bidderRequest = { ortb2: { site: { @@ -169,7 +136,38 @@ describe('gumgumAdapter', function () { sizes: sizesArray } }, - userIdAsEids: [id5Eid, ...pubProvidedIdEids], + userId: { + id5id: { + uid: 'uid-string', + ext: { + linkType: 2 + } + } + }, + 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', @@ -229,139 +227,13 @@ describe('gumgumAdapter', function () { 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(pubProvidedIdEids)); - }); - it('should filter pubProvidedId entries by allowed sources', function () { - const filteredRequest = { - ...bidRequests[0], - userIdAsEids: [ - { - source: 'audigent.com', - uids: [{ id: 'ppid-1', ext: { stype: 'ppuid' } }] - }, - { - source: 'sonobi.com', - uids: [{ id: 'ppid-2', ext: { stype: 'ppuid' } }] - } - ] - }; - const bidRequest = spec.buildRequests([filteredRequest])[0]; - const pubProvidedIds = JSON.parse(bidRequest.data.pubProvidedId); - expect(pubProvidedIds.length).to.equal(1); - expect(pubProvidedIds[0].source).to.equal('audigent.com'); - }); - it('should not set pubProvidedId when all sources are filtered out', function () { - const filteredRequest = { - ...bidRequests[0], - userIdAsEids: [{ - source: 'sonobi.com', - uids: [{ id: 'ppid-2', ext: { stype: 'ppuid' } }] - }] - }; - const bidRequest = spec.buildRequests([filteredRequest])[0]; - expect(bidRequest.data.pubProvidedId).to.equal(undefined); + 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]; - expect(bidRequest.data.id5Id).to.equal(id5Eid.uids[0].id); - expect(bidRequest.data.id5IdLinkType).to.equal(id5Eid.uids[0].ext.linkType); - }); - it('should use bidderRequest.ortb2.user.ext.eids when bid-level eids are not available', function () { - const request = { ...bidRequests[0], userIdAsEids: undefined }; - const fakeBidderRequest = { - ...bidderRequest, - ortb2: { - ...bidderRequest.ortb2, - user: { - ext: { - eids: [{ - source: 'liveramp.com', - uids: [{ - id: 'fallback-idl-env' - }] - }] - } - } - } - }; - const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; - expect(bidRequest.data.idl_env).to.equal('fallback-idl-env'); - }); - it('should prioritize bidderRequest.ortb2.user.ext.eids over bid-level eids', function () { - const request = { - ...bidRequests[0], - userIdAsEids: [{ - source: 'liveramp.com', - uids: [{ id: 'bid-level-idl-env' }] - }] - }; - const fakeBidderRequest = { - ...bidderRequest, - ortb2: { - ...bidderRequest.ortb2, - user: { - ext: { - eids: [{ - source: 'liveramp.com', - uids: [{ id: 'ortb2-level-idl-env' }] - }] - } - } - } - }; - const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; - expect(bidRequest.data.idl_env).to.equal('ortb2-level-idl-env'); - }); - it('should keep identity output consistent for prebid10 ortb2 eids input', function () { - const request = { ...bidRequests[0], userIdAsEids: undefined }; - const fakeBidderRequest = { - ...bidderRequest, - ortb2: { - ...bidderRequest.ortb2, - user: { - ext: { - eids: [ - { - source: 'uidapi.com', - uids: [{ id: 'uid2-token', atype: 3 }] - }, - { - source: 'liveramp.com', - uids: [{ id: 'idl-envelope', atype: 1 }] - }, - { - source: 'adserver.org', - uids: [{ id: 'tdid-value', atype: 1, ext: { rtiPartner: 'TDID' } }] - }, - { - source: 'id5-sync.com', - uids: [{ id: 'id5-value', atype: 1, ext: { linkType: 2 } }] - }, - { - source: 'audigent.com', - uids: [{ id: 'ppid-1', atype: 1, ext: { stype: 'ppuid' } }] - }, - { - source: 'sonobi.com', - uids: [{ id: 'ppid-2', atype: 1, ext: { stype: 'ppuid' } }] - } - ] - } - } - } - }; - const bidRequest = spec.buildRequests([request], fakeBidderRequest)[0]; - - // Expected identity payload shape from legacy GumGum request fields. - expect(bidRequest.data.uid2).to.equal('uid2-token'); - expect(bidRequest.data.idl_env).to.equal('idl-envelope'); - expect(bidRequest.data.tdid).to.equal('tdid-value'); - expect(bidRequest.data.id5Id).to.equal('id5-value'); - expect(bidRequest.data.id5IdLinkType).to.equal(2); - const pubProvidedId = JSON.parse(bidRequest.data.pubProvidedId); - expect(pubProvidedId.length).to.equal(1); - expect(pubProvidedId[0].source).to.equal('audigent.com'); + expect(bidRequest.data.id5Id).to.equal(bidRequests[0].userId.id5id.uid); + expect(bidRequest.data.id5IdLinkType).to.equal(bidRequests[0].userId.id5id.ext.linkType); }); it('should set pubId param if found', function () { @@ -426,266 +298,6 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request], bidderRequest)[0]; expect(bidRequest.data).to.have.property('curl', 'http://pub.com/news'); }); - - describe('content metadata extraction', function() { - it('should extract all site.content fields', function() { - const ortb2WithContent = { - site: { - content: { - id: 'content-id-123', - episode: 5, - title: 'Test Episode Title', - series: 'Test Series', - season: 'Season 2', - genre: 'Comedy', - contentrating: 'PG-13', - userrating: '4.5', - context: 1, - livestream: 1, - len: 1800, - language: 'en', - url: 'https://example.com/content', - cattax: 6, - prodq: 2, - qagmediarating: 1, - keywords: 'keyword1,keyword2,keyword3', - cat: ['IAB1-1', 'IAB1-2', 'IAB1-3'], - producer: { - id: 'producer-123', - name: 'Test Producer' - }, - channel: { - id: 'channel-123', - name: 'Test Channel', - domain: 'testchannel.com' - }, - network: { - name: 'Test Network' - } - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ...bidderRequest, ortb2: ortb2WithContent })[0]; - - expect(bidRequest.data.itype).to.equal('site'); - expect(bidRequest.data.cid).to.equal('content-id-123'); - expect(bidRequest.data.cepisode).to.equal(5); - expect(bidRequest.data.ctitle).to.equal('Test Episode Title'); - expect(bidRequest.data.cseries).to.equal('Test Series'); - expect(bidRequest.data.cseason).to.equal('Season 2'); - expect(bidRequest.data.cgenre).to.equal('Comedy'); - expect(bidRequest.data.crating).to.equal('PG-13'); - expect(bidRequest.data.cur).to.equal('4.5'); - expect(bidRequest.data.cctx).to.equal(1); - expect(bidRequest.data.clive).to.equal(1); - expect(bidRequest.data.clen).to.equal(1800); - expect(bidRequest.data.clang).to.equal('en'); - expect(bidRequest.data.curl).to.equal('https://example.com/content'); - expect(bidRequest.data.cattax).to.equal(6); - expect(bidRequest.data.cprodq).to.equal(2); - expect(bidRequest.data.cqag).to.equal(1); - expect(bidRequest.data.ckw).to.equal('keyword1,keyword2,keyword3'); - expect(bidRequest.data.ccat).to.equal('IAB1-1,IAB1-2,IAB1-3'); - expect(bidRequest.data.cpid).to.equal('producer-123'); - expect(bidRequest.data.cpname).to.equal('Test Producer'); - expect(bidRequest.data.cchannelid).to.equal('channel-123'); - expect(bidRequest.data.cchannel).to.equal('Test Channel'); - expect(bidRequest.data.cchanneldomain).to.equal('testchannel.com'); - expect(bidRequest.data.cnetwork).to.equal('Test Network'); - }); - - it('should extract app.content fields when site.content is not present', function() { - const ortb2WithAppContent = { - app: { - content: { - id: 'app-content-id', - title: 'App Content Title', - series: 'App Series', - url: 'https://example.com/app-content' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2: ortb2WithAppContent })[0]; - - expect(bidRequest.data.itype).to.equal('app'); - expect(bidRequest.data.cid).to.equal('app-content-id'); - expect(bidRequest.data.ctitle).to.equal('App Content Title'); - expect(bidRequest.data.cseries).to.equal('App Series'); - expect(bidRequest.data.curl).to.equal('https://example.com/app-content'); - }); - - it('should prioritize site.content over app.content', function() { - const ortb2WithBoth = { - site: { - content: { - id: 'site-content-id', - title: 'Site Content' - } - }, - app: { - content: { - id: 'app-content-id', - title: 'App Content' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2: ortb2WithBoth })[0]; - - expect(bidRequest.data.itype).to.equal('site'); - expect(bidRequest.data.cid).to.equal('site-content-id'); - expect(bidRequest.data.ctitle).to.equal('Site Content'); - }); - - it('should handle keywords as an array', function() { - const ortb2 = { - site: { - content: { - keywords: ['keyword1', 'keyword2', 'keyword3'] - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.ckw).to.equal('keyword1,keyword2,keyword3'); - }); - - it('should handle keywords as a string', function() { - const ortb2 = { - site: { - content: { - keywords: 'keyword1,keyword2,keyword3' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.ckw).to.equal('keyword1,keyword2,keyword3'); - }); - - it('should not include content params when content object is missing', function() { - const ortb2 = { - site: { - page: 'https://example.com' - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data).to.not.have.property('cid'); - expect(bidRequest.data).to.not.have.property('ctitle'); - expect(bidRequest.data).to.not.have.property('curl'); - }); - - it('should not include undefined or null content fields', function() { - const ortb2 = { - site: { - content: { - id: 'content-123', - title: undefined, - series: null, - season: '' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.cid).to.equal('content-123'); - expect(bidRequest.data).to.not.have.property('ctitle'); - expect(bidRequest.data).to.not.have.property('cseries'); - expect(bidRequest.data).to.not.have.property('cseason'); - }); - - it('should handle zero values for numeric fields', function() { - const ortb2 = { - site: { - content: { - episode: 0, - context: 0, - livestream: 0, - len: 0, - cattax: 0, - prodq: 0, - qagmediarating: 0 - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.cepisode).to.equal(0); - expect(bidRequest.data.cctx).to.equal(0); - expect(bidRequest.data.clive).to.equal(0); - expect(bidRequest.data.clen).to.equal(0); - expect(bidRequest.data.cattax).to.equal(0); - expect(bidRequest.data.cprodq).to.equal(0); - expect(bidRequest.data.cqag).to.equal(0); - }); - - it('should handle empty arrays for cat', function() { - const ortb2 = { - site: { - content: { - cat: [] - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data).to.not.have.property('ccat'); - }); - - it('should handle partial producer data', function() { - const ortb2 = { - site: { - content: { - producer: { - id: 'producer-id-only' - } - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.cpid).to.equal('producer-id-only'); - expect(bidRequest.data).to.not.have.property('cpname'); - }); - - it('should handle episode as string', function() { - const ortb2 = { - site: { - content: { - episode: 'S01E05' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ortb2 })[0]; - - expect(bidRequest.data.cepisode).to.equal('S01E05'); - }); - - it('should not override existing curl from irisid extraction', function() { - const ortb2 = { - site: { - content: { - url: 'https://content-url.com' - } - } - }; - const request = { ...bidRequests[0] }; - const bidRequest = spec.buildRequests([request], { ...bidderRequest, ortb2 })[0]; - - expect(bidRequest.data.curl).to.equal('https://content-url.com'); - }); - }); it('should not set the iriscat param when not found', function () { const request = { ...bidRequests[0] } const bidRequest = spec.buildRequests([request])[0]; @@ -722,26 +334,6 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.have.property('gpid'); expect(bidRequest.data.gpid).to.equal('/17037559/jeusol/jeusol_D_1'); }); - it('should set ae value to 1 for PAAPI', function () { - const req = { - ...bidRequests[0], - ortb2Imp: { - ext: { - ae: 1, - data: { - adserver: { - name: 'test', - adslot: 123456 - } - } - } - } - } - const bidRequest = spec.buildRequests([req])[0]; - expect(bidRequest.data).to.have.property('ae'); - expect(bidRequest.data.ae).to.equal(true); - }); - it('should set the global placement id (gpid) if in gpid property', function () { const gpid = 'abc123' const req = { ...bidRequests[0], ortb2Imp: { ext: { data: {}, gpid } } } @@ -1006,7 +598,7 @@ describe('gumgumAdapter', function () { 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(pubProvidedIdEids)); + expect(bidRequest.data.pubProvidedId).to.equal(JSON.stringify(bidRequests[0].userId.pubProvidedId)); }); it('should add gdpr consent parameters if gdprConsent is present', function () { @@ -1106,40 +698,14 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.uspConsent).to.eq(uspConsentObj.uspConsent); }); it('should add a tdid parameter if request contains unified id from TradeDesk', function () { - const tdidEid = { - source: 'adserver.org', - uids: [{ - id: 'tradedesk-id', - ext: { - rtiPartner: 'TDID' - } - }] - }; - const request = Object.assign({}, bidRequests[0], { userIdAsEids: [...bidRequests[0].userIdAsEids, tdidEid] }); - const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.tdid).to.eq(tdidEid.uids[0].id); - }); - it('should add a tdid parameter when TDID uid is not the first uid in adserver.org', function () { - const tdidEid = { - source: 'adserver.org', - uids: [ - { - id: 'non-tdid-first', - ext: { - rtiPartner: 'NOT_TDID' - } - }, - { - id: 'tradedesk-id', - ext: { - rtiPartner: 'TDID' - } - } - ] - }; - const request = Object.assign({}, bidRequests[0], { userIdAsEids: [tdidEid] }); + const unifiedId = { + 'userId': { + 'tdid': 'tradedesk-id' + } + } + const request = Object.assign(unifiedId, bidRequests[0]); const bidRequest = spec.buildRequests([request])[0]; - expect(bidRequest.data.tdid).to.eq('tradedesk-id'); + expect(bidRequest.data.tdid).to.eq(unifiedId.userId.tdid); }); it('should not add a tdid parameter if unified id is not found', function () { const request = spec.buildRequests(bidRequests)[0]; @@ -1147,8 +713,7 @@ describe('gumgumAdapter', function () { }); it('should send IDL envelope ID if available', function () { const idl_env = 'abc123'; - const idlEid = { source: 'liveramp.com', uids: [{ id: idl_env }] }; - const request = { ...bidRequests[0], userIdAsEids: [idlEid] }; + const request = { ...bidRequests[0], userId: { idl_env } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('idl_env'); @@ -1162,8 +727,7 @@ describe('gumgumAdapter', function () { }); it('should add a uid2 parameter if request contains uid2 id', function () { const uid2 = { id: 'sample-uid2' }; - const uid2Eid = { source: 'uidapi.com', uids: [{ id: uid2.id }] }; - const request = { ...bidRequests[0], userIdAsEids: [uid2Eid] }; + const request = { ...bidRequests[0], userId: { uid2 } }; const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('uid2'); diff --git a/test/spec/modules/hadronAnalyticsAdapter_spec.js b/test/spec/modules/hadronAnalyticsAdapter_spec.js index 68e5bc3aecb..ec1092fa441 100644 --- a/test/spec/modules/hadronAnalyticsAdapter_spec.js +++ b/test/spec/modules/hadronAnalyticsAdapter_spec.js @@ -12,7 +12,7 @@ describe('Hadron analytics adapter', () => { options: { partnerId: 12349, eventsToTrack: ['auctionInit', 'auctionEnd', 'bidWon', - 'bidderDone', 'requestBids', 'addAdUnits', 'setTargeting', 'adRenderFailed', + 'bidderDone', 'requestBids', 'setTargeting', 'adRenderFailed', 'bidResponse', 'bidTimeout', 'bidRequested', 'bidAdjustment', 'nonExistingEvent' ], } diff --git a/test/spec/modules/intersectionRtdProvider_spec.js b/test/spec/modules/intersectionRtdProvider_spec.js deleted file mode 100644 index 9fa934fd4f3..00000000000 --- a/test/spec/modules/intersectionRtdProvider_spec.js +++ /dev/null @@ -1,164 +0,0 @@ -import { config as _config, config } from 'src/config.js'; -import { expect } from 'chai'; -import * as events from 'src/events.js'; -import * as prebidGlobal from 'src/prebidGlobal.js'; -import { intersectionSubmodule } from 'modules/intersectionRtdProvider.js'; -import * as utils from 'src/utils.js'; -import { getGlobal } from 'src/prebidGlobal.js'; -import 'src/prebid.js'; - -describe('Intersection RTD Provider', function () { - let sandbox; - let placeholder; - const pbjs = getGlobal(); - const adUnit = { - code: 'ad-slot-1', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [ - { - bidder: 'fake' - } - ] - }; - const providerConfig = { name: 'intersection', waitForIt: true }; - const rtdConfig = { realTimeData: { auctionDelay: 200, dataProviders: [providerConfig] } } - describe('IntersectionObserver not supported', function() { - beforeEach(function() { - sandbox = sinon.createSandbox(); - }); - afterEach(function() { - sandbox.restore(); - sandbox = undefined; - }); - it('init should return false', function () { - sandbox.stub(window, 'IntersectionObserver').value(undefined); - expect(intersectionSubmodule.init({})).is.false; - }); - }); - describe('IntersectionObserver supported', function() { - beforeEach(function() { - sandbox = sinon.createSandbox(); - placeholder = createDiv(); - append(); - const __config = {}; - sandbox.stub(_config, 'getConfig').callsFake(function (path) { - return utils.deepAccess(__config, path); - }); - sandbox.stub(_config, 'setConfig').callsFake(function (obj) { - utils.mergeDeep(__config, obj); - }); - sandbox.stub(window, 'IntersectionObserver').callsFake(function (cb) { - return { - observe(el) { - cb([ - { - target: el, - intersectionRatio: 1, - isIntersecting: true, - time: Date.now(), - boundingClientRect: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0 }, - intersectionRect: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0 }, - rootRect: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0, x: 0, y: 0 } - } - ]); - }, - unobserve() {}, - disconnect() {} - }; - }); - }); - afterEach(function() { - sandbox.restore(); - remove(); - sandbox = undefined; - placeholder = undefined; - pbjs.removeAdUnit(); - }); - it('init should return true', function () { - expect(intersectionSubmodule.init({})).is.true; - }); - it('should set intersection. (request with "adUnitCodes")', function(done) { - pbjs.addAdUnits([utils.deepClone(adUnit)]); - config.setConfig(rtdConfig); - const onDone = sandbox.stub(); - const requestBidObject = { adUnitCodes: [adUnit.code] }; - intersectionSubmodule.init({}); - intersectionSubmodule.getBidRequestData( - requestBidObject, - onDone, - providerConfig - ); - setTimeout(function() { - expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); - done(); - }, 200); - }); - it('should set intersection. (request with "adUnits")', function(done) { - config.setConfig(rtdConfig); - const onDone = sandbox.stub(); - const requestBidObject = { adUnits: [utils.deepClone(adUnit)] }; - intersectionSubmodule.init(); - intersectionSubmodule.getBidRequestData( - requestBidObject, - onDone, - providerConfig - ); - setTimeout(function() { - expect(requestBidObject.adUnits[0].bids[0]).to.have.property('intersection'); - done(); - }, 200); - }); - it('should set intersection. (request all)', function(done) { - pbjs.addAdUnits([utils.deepClone(adUnit)]); - config.setConfig(rtdConfig); - const onDone = sandbox.stub(); - const requestBidObject = {}; - intersectionSubmodule.init({}); - intersectionSubmodule.getBidRequestData( - requestBidObject, - onDone, - providerConfig - ); - setTimeout(function() { - expect(pbjs.adUnits[0].bids[0]).to.have.property('intersection'); - done(); - }, 200); - }); - it('should call done due timeout', function(done) { - config.setConfig(rtdConfig); - remove(); - const onDone = sandbox.stub(); - const requestBidObject = { adUnits: [utils.deepClone(adUnit)] }; - intersectionSubmodule.init({}); - intersectionSubmodule.getBidRequestData( - requestBidObject, - onDone, - { ...providerConfig, test: 1 } - ); - setTimeout(function() { - sinon.assert.calledOnce(onDone); - expect(requestBidObject.adUnits[0].bids[0]).to.not.have.property('intersection'); - done(); - }, 300); - }); - }); - function createDiv() { - const div = document.createElement('div'); - div.id = adUnit.code; - return div; - } - function append() { - if (placeholder) { - document.body.appendChild(placeholder); - } - } - function remove() { - if (placeholder && placeholder.parentElement) { - placeholder.parentElement.removeChild(placeholder); - } - } -}); diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js index b4e6dd8629b..af29701aa22 100644 --- a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -161,7 +161,6 @@ describe('Invisibly Analytics Adapter test suite', function () { REQUEST_BIDS: { call: 'request', }, - ADD_AD_UNITS: { call: 'addAdUnits' }, AD_RENDER_FAILED: { call: 'adRenderFailed' }, INVALID_EVENT: { mockKey: 'this event should not emit', @@ -447,28 +446,6 @@ describe('Invisibly Analytics Adapter test suite', function () { sinon.assert.callCount(invisiblyAdapter.track, 1); }); - // spec for add ad units event - it('add ad units event', function () { - invisiblyAdapter.enableAnalytics(MOCK.config); - events.emit(EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); - invisiblyAdapter.flush(); - - const invisiblyEvents = JSON.parse( - requests[0].requestBody.substring(0) - ); - expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal( - 'https://api.pymx5.com/v1/sites/events' - ); - expect(invisiblyEvents.event_data.pageViewId).to.exist; - expect(invisiblyEvents.event_data.ver).to.equal(1); - expect(invisiblyEvents.event_type).to.equal('PREBID_addAdUnits'); - expect(invisiblyEvents.event_data.call).to.equal( - MOCK.ADD_AD_UNITS.call - ); - sinon.assert.callCount(invisiblyAdapter.track, 1); - }); - // spec for ad render failed event it('ad render failed event', function () { invisiblyAdapter.enableAnalytics(MOCK.config); @@ -540,7 +517,6 @@ describe('Invisibly Analytics Adapter test suite', function () { EVENTS.BIDDER_DONE, EVENTS.SET_TARGETING, EVENTS.REQUEST_BIDS, - EVENTS.ADD_AD_UNITS, EVENTS.AD_RENDER_FAILED ]).to.beTrackedBy(invisiblyAdapter.track); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index d6a5d624547..837225f1844 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, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo, getDivIdFromAdUnitCode } from '../../../modules/ixBidAdapter.js'; +import { spec, storage, FEATURE_TOGGLES, LOCAL_STORAGE_FEATURE_TOGGLES_KEY, REQUESTED_FEATURE_TOGGLES, combineImps, bidToVideoImp, bidToNativeImp, deduplicateImpExtFields, removeSiteIDs, addDeviceInfo, getDivIdFromAdUnit } from '../../../modules/ixBidAdapter.js'; import { deepAccess, deepClone } from '../../../src/utils.js'; import * as ajaxLib from 'src/ajax.js'; import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; @@ -884,52 +884,6 @@ 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' - } - }, - paapi: { - enabled: true - }, - }; - - 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' - } - }, - paapi: { - enabled: true - } - }; - const DEFAULT_IDENTITY_RESPONSE = { IdentityIp: { responsePending: false, @@ -1055,10 +1009,8 @@ describe('IndexexchangeAdapter', function () { id: `uid_id_${i}`, }] }; - eids.push(newEid); } - return eids; } @@ -3638,78 +3590,6 @@ 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 request has paapi.enabled = true and ext.ae = 1', 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(); - }); - - it('impression should have paapi extension when passed', function () { - const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); - bid.ortb2Imp.ext.ae = 1 - bid.ortb2Imp.ext.paapi = { - requestedSize: { - width: 300, - height: 250 - } - } - const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; - const impression = extractPayload(requestBidFloor).imp[0]; - expect(impression.ext.paapi.requestedSize.width).to.equal(300); - expect(impression.ext.paapi.requestedSize.height).to.equal(250); - }); - }); - describe('integration through exchangeId and externalId', function () { const expectedExchangeId = 123456; // create banner bids with externalId but no siteId as bidder param @@ -4355,106 +4235,6 @@ describe('IndexexchangeAdapter', function () { 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.paapi = { enabled: 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.paapi).to.deep.equal(expectedOutput); - }); - - it('should correctly interpret response without auction configs', () => { - const result = spec.interpretResponse(serverResponseWithoutFledgeConfigs, bidderRequestWithFledgeEnabled); - expect(result.paapi).to.be.undefined; - }); - - it('should handle malformed auction configs gracefully', () => { - const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); - expect(result.paapi).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.paapi).to.be.undefined; - expect(result.length).to.be.greaterThan(0); - }); - }); - describe('interpretResponse when server response is empty', function() { let serverResponseWithoutBody; let serverResponseWithoutSeatbid; @@ -4489,7 +4269,6 @@ describe('IndexexchangeAdapter', function () { }); }); }); - describe('bidrequest consent', function () { it('should have consent info if gdprApplies and consentString exist', function () { const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); @@ -5448,15 +5227,15 @@ describe('IndexexchangeAdapter', function () { const el = document.createElement('div'); el.id = adUnitCode; document.body.appendChild(el); - expect(getDivIdFromAdUnitCode(adUnitCode)).to.equal(adUnitCode); + expect(getDivIdFromAdUnit(adUnitCode, { code: adUnitCode })).to.equal(adUnitCode); document.body.removeChild(el); }); it('retrieves divId from GPT once and caches result', () => { const adUnitCode = 'div-ad2'; const stub = sinon.stub(gptUtils, 'getGptSlotInfoForAdUnitCode').returns({ divId: 'gpt-div' }); - const first = getDivIdFromAdUnitCode(adUnitCode); - const second = getDivIdFromAdUnitCode(adUnitCode); + const first = getDivIdFromAdUnit(adUnitCode, {}); + const second = getDivIdFromAdUnit(adUnitCode, {}); expect(first).to.equal('gpt-div'); expect(second).to.equal('gpt-div'); expect(stub.calledOnce).to.be.true; diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index 8f6dc319c01..4de51a7b860 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -1932,55 +1932,6 @@ describe('kargo adapter tests', function() { advertiserDomains: ['https://foo.com', 'https://bar.com'] }); }); - - it('should return paapi 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; - } - } - } - - const 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 paapi - expect(result.paapi).to.have.lengthOf(3); - - const expectedBidIds = ['1', '3', '5']; // Expected bidIDs - result.paapi.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() { diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index 2db52ba4cac..56b7df292b8 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -3,6 +3,7 @@ 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'; +import * as adUnits from 'src/utils/adUnits'; const events = require('src/events'); const utils = require('src/utils'); @@ -323,7 +324,7 @@ describe('Livewrapped analytics adapter', function () { } sandbox.stub(events, 'getEvents').returns([]); sandbox.stub(utils, 'timestamp').returns(1519149562416); - sandbox.stub(document, 'getElementById').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); clock = sandbox.useFakeTimers(1519767013781); setConfig({ diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 47351f68304..489cdf834da 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -36,11 +36,6 @@ describe('LogicadAdapter', function () { } }] }], - ortb2Imp: { - ext: { - ae: 1 - } - }, ortb2: { device: { sua: { @@ -197,9 +192,6 @@ describe('LogicadAdapter', function () { stack: [] }, auctionStart: 1563337198010, - paapi: { - enabled: true - } }; const serverResponse = { body: { @@ -227,48 +219,6 @@ 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: @@ -339,10 +289,6 @@ 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'); @@ -407,13 +353,6 @@ 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('paapi'); - expect(paapiInterpretedResponse.paapi[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); - // native const nativeRequest = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; const interpretedResponseForNative = spec.interpretResponse(nativeServerResponse, nativeRequest); diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js index 5de70f83982..71087fbdcb3 100755 --- a/test/spec/modules/luceadBidAdapter_spec.js +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -2,7 +2,6 @@ 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', () => { @@ -174,12 +173,5 @@ describe('Lucead Adapter', () => { const result = spec.interpretResponse(serverResponse, bidRequest); expect(Object.keys(result.bids[0].meta)).to.include.members(['advertiserDomains']); }); - - it('should support enable_pa = false', function () { - serverResponse.body.enable_pa = false; - const result = spec.interpretResponse(serverResponse, bidRequest); - expect(result).to.be.an('array'); - expect(result[0].cpm).to.be.greaterThan(0); - }); }); }); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index c67d6593696..aedf8a5c2d1 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -25,7 +25,6 @@ const { BID_WON, BID_TIMEOUT, BILLABLE_EVENT, - SEAT_NON_BID, PBS_ANALYTICS, BID_REJECTED } = EVENTS; @@ -169,7 +168,7 @@ const MOCK = { getStatusCode: () => 1, metrics }, - SEAT_NON_BID: { + PBS_ANALYTICS: { auctionId: '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', seatnonbid: [{ seat: 'rubicon', @@ -2359,7 +2358,7 @@ describe('magnite analytics adapter', function () { accountId: 1001 } }); - seatnonbid = utils.deepClone(MOCK.SEAT_NON_BID); + seatnonbid = utils.deepClone(MOCK.PBS_ANALYTICS); }); it('adds seatnonbid info to bids array', () => { diff --git a/test/spec/modules/marsmediaBidAdapter_spec.js b/test/spec/modules/marsmediaBidAdapter_spec.js index 4dc07ecaa8b..86fd4275786 100644 --- a/test/spec/modules/marsmediaBidAdapter_spec.js +++ b/test/spec/modules/marsmediaBidAdapter_spec.js @@ -1,8 +1,8 @@ import { spec } from 'modules/marsmediaBidAdapter.js'; import * as utils from 'src/utils.js'; -import * as dnt from 'libraries/dnt/index.js'; import { config } from 'src/config.js'; import { internal, resetWinDimensions } from '../../../src/utils.js'; +import * as adUnits from 'src/utils/adUnits'; var marsAdapter = spec; @@ -73,7 +73,7 @@ describe('marsmedia adapter tests', function () { ]; sandbox = sinon.createSandbox(); - sandbox.stub(document, 'getElementById').withArgs('Unit-Code').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); @@ -399,15 +399,10 @@ describe('marsmedia adapter tests', function () { expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); - it('dnt is correctly set to 1', function () { - var dntStub = sinon.stub(dnt, 'getDNT').returns(1); - + it('dnt is always 0', function () { var bidRequest = marsAdapter.buildRequests(this.defaultBidRequestList, this.defaultBidderRequest); - - dntStub.restore(); - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.device.dnt).to.equal(1); + expect(openrtbRequest.device.dnt).to.equal(0); }); it('supports string video sizes', function () { diff --git a/test/spec/modules/mediaforceBidAdapter_spec.js b/test/spec/modules/mediaforceBidAdapter_spec.js index 887acb3fe8d..fae28156b08 100644 --- a/test/spec/modules/mediaforceBidAdapter_spec.js +++ b/test/spec/modules/mediaforceBidAdapter_spec.js @@ -1,7 +1,6 @@ import { assert } from 'chai'; import { spec, resolveFloor } from 'modules/mediaforceBidAdapter.js'; import * as utils from '../../../src/utils.js'; -import { getDNT } from 'libraries/dnt/index.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; describe('mediaforce bid adapter', function () { @@ -126,7 +125,7 @@ describe('mediaforce bid adapter', function () { ] }; - const dnt = getDNT() ? 1 : 0; + const dnt = 0; // DNT deprecated by W3C; Prebid no longer supports DNT const secure = window.location.protocol === 'https:' ? 1 : 0; const pageUrl = window.location.href; const timeout = 1500; diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 15c83e9a265..6368a1abfca 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -6,6 +6,7 @@ import { config } from '../../../src/config.js'; import { server } from '../../mocks/xhr.js'; import { resetWinDimensions } from '../../../src/utils.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; +import * as adUnits from 'src/utils/adUnits'; getGlobal().version = getGlobal().version || 'version'; const VALID_BID_REQUEST = [{ @@ -1979,17 +1980,13 @@ describe('Media.net bid adapter', function () { beforeEach(function () { getGlobal().medianetGlobals = {}; - const documentStub = sandbox.stub(document, 'getElementById'); const boundingRect = { top: 50, left: 50, bottom: 100, right: 100 }; - documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ - getBoundingClientRect: () => boundingRect - }); - documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + sandbox.stub(adUnits, 'getAdUnitElement').returns({ getBoundingClientRect: () => boundingRect }); const windowSizeStub = sandbox.stub(spec, 'getWindowSize'); @@ -2066,19 +2063,6 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERIDASEIDS); }); - it('should have valid payload when PAAPI is enabled', function () { - const bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, { ...VALID_AUCTIONDATA, paapi: { enabled: 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 () { - const bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, { ...VALID_AUCTIONDATA, paapi: { enabled: true } }); - const 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.clearPageMeta(); @@ -2102,14 +2086,14 @@ describe('Media.net bid adapter', function () { }); describe('slot visibility', function () { - let documentStub; + let elementStub; beforeEach(function () { const windowSizeStub = sandbox.stub(spec, 'getWindowSize'); windowSizeStub.returns({ w: 1000, h: 1000 }); - documentStub = sandbox.stub(document, 'getElementById'); + elementStub = sandbox.stub(adUnits, 'getAdUnitElement'); }); it('slot visibility should be 2 and ratio 0 when ad unit is BTF', function () { const boundingRect = { @@ -2118,10 +2102,7 @@ describe('Media.net bid adapter', function () { bottom: 1050, right: 1050 }; - documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ - getBoundingClientRect: () => boundingRect - }); - documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + elementStub.returns({ getBoundingClientRect: () => boundingRect }); @@ -2137,10 +2118,7 @@ describe('Media.net bid adapter', function () { bottom: 1050, right: 1050 }; - documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ - getBoundingClientRect: () => boundingRect - }); - documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + elementStub.returns({ getBoundingClientRect: () => boundingRect }); const bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); @@ -2155,10 +2133,7 @@ describe('Media.net bid adapter', function () { bottom: 1050, right: 1050 }; - documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ - getBoundingClientRect: () => boundingRect - }); - documentStub.withArgs('div-gpt-ad-1460505748561-0').returns({ + elementStub.returns({ getBoundingClientRect: () => boundingRect }); const bidReq = spec.buildRequests(VALID_BID_REQUEST, VALID_AUCTIONDATA); @@ -2183,12 +2158,9 @@ describe('Media.net bid adapter', function () { bottom: 1050, right: 1050 }; - documentStub.withArgs(divId).returns({ + elementStub.returns({ getBoundingClientRect: () => boundingRect - }); - documentStub.withArgs('div-gpt-ad-1460505748561-123').returns({ - getBoundingClientRect: () => boundingRect - }); + }) const bidRequest = [{ ...VALID_BID_REQUEST[0], adUnitCode: code }] const bidReq = spec.buildRequests(bidRequest, VALID_AUCTIONDATA); @@ -2254,32 +2226,6 @@ describe('Media.net bid adapter', function () { const bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); expect(bids).to.deep.equal(validBids); }); - - it('should return paapi if PAAPI response is received', function() { - const response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); - expect(response).to.have.property('bids'); - expect(response).to.have.property('paapi'); - expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); - }); - - it('should return paapi if openRTB PAAPI response received', function () { - const response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); - expect(response).to.have.property('bids'); - expect(response).to.have.property('paapi'); - expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) - }); - - it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id', function() { - const bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, { ...VALID_AUCTIONDATA, paapi: { enabled: true } }); - const bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); - expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) - }); - - it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id for openRTB response', function() { - const bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, { ...VALID_AUCTIONDATA, paapi: { enabled: true } }); - const bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); - expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) - }); }); describe('onTimeout', function () { diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index 88c3c56ba03..d269a99d74d 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { spec, storage } from 'modules/mgidBidAdapter.js'; import { version } from 'package.json'; import * as utils from '../../../src/utils.js'; -import { getDNT } from 'libraries/dnt/index.js'; import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync.js'; import { config } from '../../../src/config.js'; @@ -23,7 +22,7 @@ describe('Mgid bid adapter', function () { }); const screenHeight = screen.height; const screenWidth = screen.width; - const dnt = getDNT() ? 1 : 0; + const dnt = 0; // DNT deprecated by W3C; Prebid no longer supports DNT const language = navigator.language ? 'language' : 'userLanguage'; let lang = navigator[language].split('-')[0]; if (lang.length !== 2 && lang.length !== 3) { diff --git a/test/spec/modules/msftBidAdapter_spec.js b/test/spec/modules/msftBidAdapter_spec.js index 9f7d757d897..2f5f1014594 100644 --- a/test/spec/modules/msftBidAdapter_spec.js +++ b/test/spec/modules/msftBidAdapter_spec.js @@ -210,7 +210,6 @@ describe('msftBidAdapter', function () { const bidderRequest = Object.assign({}, testBidderRequest, { bids: bidRequests }); - debugger;// eslint-disable-line no-debugger const request = spec.buildRequests(bidRequests, bidderRequest)[0]; expect(request.method).to.equal('POST'); const data = request.data; diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index bdbcc617588..1ec6ed7c80b 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -3,6 +3,7 @@ import * as utils from 'src/utils.js'; import { spec } from 'modules/omsBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as winDimensions from 'src/utils/winDimensions.js'; +import * as adUnits from 'src/utils/adUnits'; const URL = 'https://rt.marphezis.com/hb'; @@ -83,7 +84,7 @@ describe('omsBidAdapter', function () { }]; sandbox = sinon.createSandbox(); - sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); sandbox.stub(winDimensions, 'getWinDimensions').returns(win); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index edfd6e8b754..8acdae76265 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -768,38 +768,6 @@ describe('onetag', function () { 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 () { - const bidderRequest = { - 'bidderCode': 'onetag', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - 'paapi': { - 'enabled': true - } - }; - const 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.paapi.enabled); - }); - it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { - const bidderRequest = { - 'bidderCode': 'onetag', - 'auctionId': '1d1a030790a475', - 'bidderRequestId': '22edbae2733bf6', - 'timeout': 3000, - paapi: { - enabled: false - } - }; - const 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.paapi.enabled); - }); it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { const bidderRequest = { 'bidderCode': 'onetag', @@ -821,13 +789,7 @@ describe('onetag', function () { 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.paapi).to.be.an('array').that.is.not.empty; for (let i = 0; i < interpretedResponse.length; i++) { const dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 2f44f7fb17b..2f382b11908 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -3,6 +3,7 @@ import * as utils from 'src/utils.js'; import { spec } from 'modules/onomagicBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as winDimensions from 'src/utils/winDimensions.js'; +import * as adUnits from 'src/utils/adUnits'; const URL = 'https://bidder.onomagic.com/hb'; @@ -61,7 +62,7 @@ describe('onomagicBidAdapter', function() { sandbox = sinon.createSandbox(); sandbox.stub(winDimensions, 'getWinDimensions').returns(win); - sandbox.stub(document, 'getElementById').withArgs('adunit-code').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); sandbox.stub(utils, 'getWindowTop').returns(win); sandbox.stub(utils, 'getWindowSelf').returns(win); }); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 19382760a2a..c801ecd2bfc 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -4,7 +4,6 @@ import { newBidder } from 'src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; -import * as dnt from 'libraries/dnt/index.js'; // load modules that register ORTB processors import 'src/prebid.js' import 'modules/currency.js'; @@ -13,7 +12,6 @@ import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; -import 'modules/paapi.js'; import { deepClone } from 'src/utils.js'; import { version } from 'package.json'; @@ -1053,32 +1051,7 @@ describe('OpenxRtbAdapter', function () { }); context('do not track (DNT)', function() { - let doNotTrackStub; - - beforeEach(function () { - doNotTrackStub = sinon.stub(dnt, 'getDNT'); - }); - afterEach(function() { - doNotTrackStub.restore(); - }); - - it('when there is a do not track, should send a dnt', async function () { - doNotTrackStub.returns(1); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(1); - }); - - it('when there is not do not track, don\'t send dnt', async function () { - doNotTrackStub.returns(0); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(0); - }); - - it('when there is no defined do not track, don\'t send dnt', async function () { - doNotTrackStub.returns(null); - + it('always sends dnt as 0', async function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); expect(request[0].data.device.dnt).to.equal(0); }); @@ -1230,18 +1203,6 @@ describe('OpenxRtbAdapter', function () { expect(request[0].data).to.not.have.any.keys('user'); }); }); - - context('FLEDGE', function() { - it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes, { - ...mockBidderRequest, - paapi: { - enabled: true - } - }); - expect(request[0].data.imp[0].ext.ae).to.equal(2); - }); - }); }); context('banner', function () { @@ -1889,104 +1850,6 @@ describe('OpenxRtbAdapter', function () { }); }); } - - context('when the response contains FLEDGE interest groups config', function() { - let response; - - beforeEach(function () { - sinon.stub(config, 'getConfig') - .withArgs('fledgeEnabled') - .returns(true); - - bidRequestConfigs = [{ - bidder: 'openx', - params: { - unit: '12345678', - delDomain: 'test-del-domain' - }, - adUnitCode: 'adunit-code', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - }, - }, - bidId: 'test-bid-id', - bidderRequestId: 'test-bidder-request-id', - auctionId: 'test-auction-id' - }]; - - bidRequest = spec.buildRequests(bidRequestConfigs, { refererInfo: {} })[0]; - - bidResponse = { - seatbid: [{ - bid: [{ - impid: 'test-bid-id', - price: 2, - w: 300, - h: 250, - crid: 'test-creative-id', - dealid: 'test-deal-id', - adm: 'test-ad-markup' - }] - }], - cur: 'AUS', - ext: { - fledge_auction_configs: { - 'test-bid-id': { - seller: 'codinginadtech.com', - interestGroupBuyers: ['somedomain.com'], - sellerTimeout: 0, - perBuyerSignals: { - 'somedomain.com': { - base_bid_micros: 0.1, - disallowed_advertiser_ids: [ - '1234', - '2345' - ], - multiplier: 1.3, - use_bid_multiplier: true, - win_reporting_id: '1234567asdf' - } - } - } - } - } - }; - - response = spec.interpretResponse({ body: bidResponse }, bidRequest); - }); - - afterEach(function () { - config.getConfig.restore(); - }); - - it('should return FLEDGE auction_configs alongside bids', function () { - expect(response).to.have.property('bids'); - expect(response).to.have.property('paapi'); - expect(response.paapi.length).to.equal(1); - expect(response.paapi[0].bidId).to.equal('test-bid-id'); - }); - - it('should inject ortb2Imp in auctionSignals', function () { - const auctionConfig = response.paapi[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', - }, - secure: 1 - } - } - }); - }) - }); }); describe('user sync', function () { diff --git a/test/spec/modules/optableBidAdapter_spec.js b/test/spec/modules/optableBidAdapter_spec.js deleted file mode 100644 index ef9daeca74e..00000000000 --- a/test/spec/modules/optableBidAdapter_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/optableBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory.js'; - -describe('optableBidAdapter', function() { - const adapter = newBidder(spec); - - describe('isBidRequestValid', function() { - const validBid = { - bidder: 'optable', - params: { site: '123' }, - }; - - it('should return true when required params are present', function() { - expect(spec.isBidRequestValid(validBid)).to.be.true; - }); - - it('should return false when site is missing', function() { - const invalidBid = { ...validBid }; - delete invalidBid.params.site; - expect(spec.isBidRequestValid(invalidBid)).to.be.false; - }); - }); - - describe('buildRequests', function() { - const validBid = { - bidder: 'optable', - params: { - site: '123', - }, - }; - - const bidderRequest = { - bidderRequestId: 'bid123', - refererInfo: { - domain: 'example.com', - page: 'https://example.com/page', - ref: 'https://referrer.com' - }, - }; - - it('should include site as tagid in imp', function() { - const request = spec.buildRequests([validBid], bidderRequest); - expect(request.url).to.equal('https://ads.optable.co/ca/ortb2/v1/ssp/bid'); - expect(request.method).to.equal('POST'); - expect(request.data.imp[0].tagid).to.equal('123') - }); - }); - - describe('buildPAAPIConfigs', () => { - function makeRequest({ bidId, site = 'mockSite', ae = 1 }) { - return { - bidId, - params: { - site - }, - ortb2Imp: { - ext: { ae } - } - } - } - it('should generate auction configs for ae requests', () => { - const configs = spec.buildPAAPIConfigs([ - makeRequest({ bidId: 'bid1', ae: 1 }), - makeRequest({ bidId: 'bid2', ae: 0 }), - makeRequest({ bidId: 'bid3', ae: 1 }), - ]); - expect(configs.map(cfg => cfg.bidId)).to.eql(['bid1', 'bid3']); - configs.forEach(cfg => sinon.assert.match(cfg.config, { - seller: 'https://ads.optable.co', - decisionLogicURL: `https://ads.optable.co/ca/paapi/v1/ssp/decision-logic.js?origin=mockSite`, - interestGroupBuyers: ['https://ads.optable.co'] - })) - }) - }) - - describe('interpretResponse', function() { - const validBid = { - bidder: 'optable', - params: { - site: '123', - }, - }; - - const bidderRequest = { - bidderRequestId: 'bid123', - refererInfo: { - domain: 'example.com', - page: 'https://example.com/page', - ref: 'https://referrer.com' - }, - }; - - const response = { - body: { - ext: { - optable: { - fledge: { - auctionconfigs: [ - { impid: 'bid123', seller: 'https://ads.optable.co' }, - ] - } - } - } - } - }; - - it('maps paapi from ext.optable.fledge.auctionconfigs', function() { - const request = spec.buildRequests([validBid], bidderRequest); - const result = spec.interpretResponse(response, request); - expect(result.paapi).to.deep.equal([ - { bidId: 'bid123', config: { seller: 'https://ads.optable.co' } } - ]); - }); - }); -}); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 6a04584280d..9e6092f2cad 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -3154,15 +3154,6 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('none'); }); - it('should handle fledge requests', function () { - const bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); - const bidRequests = JSON.parse(JSON.stringify(validBidRequests)); - deepSetValue(bidRequests[0], 'ortb2Imp.ext.ae', 1); - bidderRequest.fledgeEnabled = true; - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); - expect(payload.imp[0].ext.ae).to.equal(1); - }); it('Single request: should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { var specMock = utils.deepClone(spec); config.setConfig({ 'ozone': { 'singleRequest': true } }); @@ -3424,22 +3415,6 @@ describe('ozone Adapter', function () { const bid = result[0]; expect(bid.mediaType).to.equal('video'); }); - it('should handle fledge response', function () { - const req = spec.buildRequests(validBidRequests, validBidderRequest); - const objResp = JSON.parse(JSON.stringify(validResponse)); - objResp.body.ext = { - igi: [{ - 'impid': '1', - 'igb': [{ - 'origin': 'https://paapi.dsp.com', - 'pbs': '{"key": "value"}' - }] - }] - }; - const result = spec.interpretResponse(objResp, req); - expect(result).to.be.an('object'); - expect(result.fledgeAuctionConfigs[0]['impid']).to.equal('1'); - }); it('should add labels in the adserver request if they are present in the auction response', function () { const request = spec.buildRequests(validBidRequestsMulti, validBidderRequest); const validres = JSON.parse(JSON.stringify(validResponse2Bids)); diff --git a/test/spec/modules/paapiForGpt_spec.js b/test/spec/modules/paapiForGpt_spec.js deleted file mode 100644 index de4517e1333..00000000000 --- a/test/spec/modules/paapiForGpt_spec.js +++ /dev/null @@ -1,216 +0,0 @@ -import { - getPAAPISizeHook, - onAuctionConfigFactory, - setPAAPIConfigFactory, setTargetingHookFactory, - slotConfigurator -} from 'modules/paapiForGpt.js'; -import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; -import 'modules/appnexusBidAdapter.js'; -import 'modules/rubiconBidAdapter.js'; -import { deepSetValue } from '../../../src/utils.js'; -import { config } from 'src/config.js'; - -describe('paapiForGpt module', () => { - let sandbox, fledgeAuctionConfig; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - fledgeAuctionConfig = { - seller: 'bidder', - mock: 'config' - }; - }); - afterEach(() => { - sandbox.restore(); - }); - - describe('slotConfigurator', () => { - let setGptConfig; - function mockGptSlot(auPath) { - return { - setConfig: sinon.stub(), - getAdUnitPath: () => auPath - } - } - beforeEach(() => { - setGptConfig = slotConfigurator(); - }); - - Object.entries({ - 'single slot': [mockGptSlot('mock/gpt/au')], - 'multiple slots': [mockGptSlot('mock/gpt/au'), mockGptSlot('mock/gpt/au2')] - }).forEach(([t, gptSlots]) => { - describe(`when ad unit code matches ${t}`, () => { - it('should set GPT slot config', () => { - setGptConfig('au', gptSlots, [fledgeAuctionConfig]); - gptSlots.forEach(slot => { - sinon.assert.calledWith(slot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: fledgeAuctionConfig, - }] - }); - }) - }); - describe('when reset = true', () => { - it('should reset GPT slot config', () => { - setGptConfig('au', gptSlots, [fledgeAuctionConfig]); - gptSlots.forEach(slot => slot.setConfig.resetHistory()); - setGptConfig('au', gptSlots, [], true); - gptSlots.forEach(slot => { - sinon.assert.calledWith(slot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: null - }] - }); - }) - }); - - it('should reset only sellers with no fresh config', () => { - setGptConfig('au', gptSlots, [{ seller: 's1' }, { seller: 's2' }]); - gptSlots.forEach(slot => slot.setConfig.resetHistory()); - setGptConfig('au', gptSlots, [{ seller: 's1' }], true); - gptSlots.forEach(slot => { - sinon.assert.calledWith(slot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: { seller: 's1' } - }, { - configKey: 's2', - auctionConfig: null - }] - }) - }) - }); - - it('should not reset sellers that were already reset', () => { - setGptConfig('au', gptSlots, [{ seller: 's1' }]); - setGptConfig('au', gptSlots, [], true); - gptSlots.forEach(slot => slot.setConfig.resetHistory()); - setGptConfig('au', gptSlots, [], true); - gptSlots.forEach(slot => sinon.assert.notCalled(slot.setConfig)); - }) - - it('should keep track of configuration history by ad unit', () => { - setGptConfig('au1', gptSlots, [{ seller: 's1' }]); - setGptConfig('au1', gptSlots, [{ seller: 's2' }], false); - setGptConfig('au2', gptSlots, [{ seller: 's3' }]); - gptSlots.forEach(slot => slot.setConfig.resetHistory()); - setGptConfig('au1', gptSlots, [], true); - gptSlots.forEach(slot => { - sinon.assert.calledWith(slot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: null - }, { - configKey: 's2', - auctionConfig: null - }] - }); - }) - }) - }); - }) - }) - }); - describe('setTargeting hook', () => { - let setPaapiConfig, setTargetingHook, next; - beforeEach(() => { - setPaapiConfig = sinon.stub() - setTargetingHook = setTargetingHookFactory(setPaapiConfig); - next = sinon.stub(); - }); - function expectFilters(...filters) { - expect(setPaapiConfig.args.length).to.eql(filters.length) - filters.forEach(filter => { - sinon.assert.calledWith(setPaapiConfig, filter, 'mock-matcher') - }) - } - function runHook(adUnit) { - setTargetingHook(next, adUnit, 'mock-matcher'); - sinon.assert.calledWith(next, adUnit, 'mock-matcher'); - } - it('should invoke with no filters when adUnit is undef', () => { - runHook(); - expectFilters(undefined); - }); - it('should invoke once when adUnit is a string', () => { - runHook('mock-au'); - expectFilters({ adUnitCode: 'mock-au' }) - }); - it('should invoke once per ad unit when an array', () => { - runHook(['au1', 'au2']); - expectFilters({ adUnitCode: 'au1' }, { adUnitCode: 'au2' }); - }) - }) - describe('setPAAPIConfigForGpt', () => { - let getPAAPIConfig, setGptConfig, getSlots, setPAAPIConfigForGPT; - beforeEach(() => { - getPAAPIConfig = sinon.stub(); - setGptConfig = sinon.stub(); - getSlots = sinon.stub().callsFake((codes) => Object.fromEntries(codes.map(code => [code, ['mock-slot']]))) - setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig, getSlots); - }); - - 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); - }) - }); - - it('passes customSlotMatching to getSlots', () => { - getPAAPIConfig.returns({ au1: {} }); - setPAAPIConfigForGPT('mock-filters', 'mock-custom-matching'); - sinon.assert.calledWith(getSlots, ['au1'], 'mock-custom-matching'); - }) - - 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' }] - }, - au2: { - componentAuctions: [{ seller: 's3' }] - }, - au3: null - } - getPAAPIConfig.returns(cfg); - setPAAPIConfigForGPT('mock-filters'); - sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); - Object.entries(cfg).forEach(([au, config]) => { - sinon.assert.calledWith(setGptConfig, au, ['mock-slot'], config?.componentAuctions ?? [], true); - }) - }); - }); - - describe('getPAAPISizeHook', () => { - let next; - beforeEach(() => { - next = sinon.stub(); - next.bail = sinon.stub(); - }); - - it('should pick largest supported size over larger unsupported size', () => { - getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); - sinon.assert.calledWith(next.bail, [300, 600]); - }); - - Object.entries({ - 'present': [], - 'supported': [[123, 4], [321, 5]], - 'defined': undefined, - }).forEach(([t, sizes]) => { - it(`should defer to next when no size is ${t}`, () => { - getPAAPISizeHook(next, sizes); - sinon.assert.calledWith(next, sizes); - }) - }) - }) -}); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js deleted file mode 100644 index 5cd76c59dc3..00000000000 --- a/test/spec/modules/paapi_spec.js +++ /dev/null @@ -1,2014 +0,0 @@ -import { expect } from 'chai'; -import { config } from '../../../src/config.js'; -import adapterManager from '../../../src/adapterManager.js'; -import * as utils from '../../../src/utils.js'; -import { deepAccess, deepClone } from '../../../src/utils.js'; -import { hook } from '../../../src/hook.js'; -import 'modules/appnexusBidAdapter.js'; -import 'modules/rubiconBidAdapter.js'; -import { - adAuctionHeadersHook, - addPaapiConfigHook, - addPaapiData, - ASYNC_SIGNALS, AsyncPAAPIParam, buildPAAPIParams, - buyersToAuctionConfigs, - getPAAPIConfig, - getPAAPISize, - IGB_TO_CONFIG, - mergeBuyers, NAVIGATOR_APIS, - onAuctionInit, - parallelPaapiProcessing, - parseExtIgi, - parseExtPrebidFledge, - partitionBuyers, - partitionBuyersByBidder, - registerSubmodule, - reset, - setImpExtAe, - setResponsePaapiConfigs -} from 'modules/paapi.js'; -import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; -import { getGlobal } from '../../../src/prebidGlobal.js'; -import { auctionManager } from '../../../src/auctionManager.js'; -import { stubAuctionIndex } from '../../helpers/indexStub.js'; -import { AuctionIndex } from '../../../src/auctionIndex.js'; -import { buildActivityParams } from '../../../src/activities/params.js'; - -describe('paapi module', () => { - let sandbox; - before(reset); - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - afterEach(() => { - sandbox.restore(); - reset(); - }); - - describe(`using paapi configuration`, () => { - let getPAAPISizeStub; - - function getPAAPISizeHook(next, sizes) { - next.bail(getPAAPISizeStub(sizes)); - } - - before(() => { - getPAAPISize.before(getPAAPISizeHook, 100); - }); - - after(() => { - getPAAPISize.getHooks({ hook: getPAAPISizeHook }).remove(); - }); - - beforeEach(() => { - getPAAPISizeStub = sinon.stub(); - }); - - describe('adAuctionHeadersHook', () => { - let bidderRequest, ajax; - beforeEach(() => { - ajax = sinon.stub(); - bidderRequest = { paapi: {} } - }) - function getWrappedAjax() { - let wrappedAjax; - const next = sinon.stub().callsFake((spec, bids, br, ajax) => { - wrappedAjax = ajax; - }); - adAuctionHeadersHook(next, {}, [], bidderRequest, ajax); - return wrappedAjax; - } - describe('when PAAPI is enabled', () => { - beforeEach(() => { - bidderRequest.paapi.enabled = true; - }); - [ - undefined, - {}, - { adAuctionHeaders: true } - ].forEach(options => - it(`should set adAuctionHeaders = true (when options are ${JSON.stringify(options)})`, () => { - getWrappedAjax()('url', {}, 'data', options); - sinon.assert.calledWith(ajax, 'url', {}, 'data', sinon.match({ adAuctionHeaders: true })); - })); - - it('should respect adAuctionHeaders: false', () => { - getWrappedAjax()('url', {}, 'data', { adAuctionHeaders: false }); - sinon.assert.calledWith(ajax, 'url', {}, 'data', sinon.match({ adAuctionHeaders: false })); - }) - }); - it('should not alter ajax when paapi is not enabled', () => { - expect(getWrappedAjax()).to.equal(ajax); - }) - }) - - describe('getPAAPIConfig', function () { - let nextFnSpy, auctionConfig, paapiConfig; - before(() => { - config.setConfig({ paapi: { enabled: true } }); - }); - beforeEach(() => { - auctionConfig = { - seller: 'bidder', - mock: 'config' - }; - paapiConfig = { - config: auctionConfig - }; - 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' }; - addPaapiConfigHook(nextFnSpy, request, paapiConfig); - sinon.assert.calledWith(nextFnSpy, request, paapiConfig); - }); - - describe('igb', () => { - let igb1, igb2, buyerAuctionConfig; - beforeEach(() => { - igb1 = { - origin: 'buyer.1' - }; - igb2 = { - origin: 'buyer.2' - }; - buyerAuctionConfig = { - seller: 'seller', - decisionLogicURL: 'seller-decision-logic' - }; - config.mergeConfig({ - paapi: { - componentSeller: { - auctionConfig: buyerAuctionConfig - } - } - }); - }); - - function addIgb(request, igb) { - addPaapiConfigHook(nextFnSpy, Object.assign({ auctionId }, request), { igb }); - } - - it('should be collected into an auction config', () => { - addIgb({ adUnitCode: 'au1' }, igb1); - addIgb({ adUnitCode: 'au1' }, igb2); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); - const buyerConfig = getPAAPIConfig({ auctionId }).au1.componentAuctions[0]; - sinon.assert.match(buyerConfig, { - interestGroupBuyers: [igb1.origin, igb2.origin], - ...buyerAuctionConfig - }); - }); - - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = { 'fpd': 1 }; - ortb2Imp = { 'fpd': 2 }; - }); - - function getBuyerAuctionConfig() { - addIgb({ adUnitCode: 'au1', ortb2, ortb2Imp }, igb1); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); - return getPAAPIConfig({ auctionId }).au1.componentAuctions[0]; - } - - it('should be added to auction config', () => { - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: { - ortb2, - ortb2Imp - } - }); - }); - - it('should not override existing perBuyerSignals', () => { - const original = { - ortb2: { - fpd: 'original' - } - }; - igb1.pbs = { - prebid: deepClone(original) - }; - sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { - prebid: original - }); - }); - }); - }); - - describe('should collect auction configs', () => { - let cf1, cf2; - beforeEach(() => { - cf1 = { ...auctionConfig, id: 1, seller: 'b1' }; - cf2 = { ...auctionConfig, id: 2, seller: 'b2' }; - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au1' }, { config: cf1 }); - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au2' }, { config: cf2 }); - events.emit(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(EVENTS.AUCTION_END, { auctionId }); - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au' }, paapiConfig); - expect(getPAAPIConfig({ auctionId })).to.eql({}); - }); - - describe('FPD', () => { - let ortb2, ortb2Imp; - beforeEach(() => { - ortb2 = { fpd: 1 }; - ortb2Imp = { fpd: 2 }; - }); - - function getComponentAuctionConfig() { - addPaapiConfigHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - ortb2: { fpd: 1 }, - ortb2Imp: { fpd: 2 } - }, paapiConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); - return getPAAPIConfig({ auctionId }).au1.componentAuctions[0]; - } - - it('should be added to auctionSignals', () => { - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: { ortb2, ortb2Imp } - }); - }); - it('should not override existing auctionSignals', () => { - auctionConfig.auctionSignals = { prebid: { ortb2: { fpd: 'original' } } }; - sinon.assert.match(getComponentAuctionConfig().auctionSignals, { - prebid: { - ortb2: { fpd: 'original' }, - ortb2Imp - } - }); - }); - - it('should be added to perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer.1', 'buyer.2']; - const pbs = getComponentAuctionConfig().perBuyerSignals; - sinon.assert.match(pbs, { - 'buyer.1': { prebid: { ortb2, ortb2Imp } }, - 'buyer.2': { prebid: { ortb2, ortb2Imp } } - }); - }); - - it('should not override existing perBuyerSignals', () => { - auctionConfig.interestGroupBuyers = ['buyer']; - const original = { - prebid: { - ortb2: { - fpd: 'original' - } - } - }; - auctionConfig.perBuyerSignals = { - buyer: deepClone(original) - }; - sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); - }); - }); - - 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(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au'] }); - submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { au: null })); - }); - it('is invoked with relevant configs', () => { - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au1' }, paapiConfig); - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au2' }, paapiConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); - submods.forEach(submod => { - sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { - au1: { componentAuctions: [auctionConfig] }, - au2: { componentAuctions: [auctionConfig] }, - au3: 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'); - }); - }); - - 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', () => { - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au' }, paapiConfig); - events.emit(EVENTS.AUCTION_END, payload); - 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); - }); - }); - }); - }); - }); - }); - - describe('requestedSize', () => { - let adUnit; - beforeEach(() => { - adUnit = { - code: 'au', - }; - }); - - function getConfig() { - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: adUnit.code }, paapiConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit] }); - return getPAAPIConfig()[adUnit.code]; - } - - Object.entries({ - 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { - adUnit.ortb2Imp = { - ext: { - paapi: { - requestedSize: { - width: 123, - height: 321 - } - } - } - }; - }, - 'largest size'() { - getPAAPISizeStub.returns([123, 321]); - } - }).forEach(([t, setup]) => { - describe(`should be set from ${t}`, () => { - beforeEach(setup); - - it('without overriding component auctions, if set', () => { - auctionConfig.requestedSize = { width: '1px', height: '2px' }; - expect(getConfig().componentAuctions[0].requestedSize).to.eql({ - width: '1px', - height: '2px' - }); - }); - - it('on component auction, if missing', () => { - expect(getConfig().componentAuctions[0].requestedSize).to.eql({ - width: 123, - height: 321 - }); - }); - - it('on top level auction', () => { - expect(getConfig().requestedSize).to.eql({ - width: 123, - height: 321, - }); - }); - }); - }); - }); - }); - - 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 = { ...auctionConfig, auctionId, adUnitCode }; - configs[auctionId][adUnitCode] = cfg; - addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode }, { config: cfg }); - }); - events.emit(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]])); - let adUnits; - - 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); - }); - - beforeEach(() => { - getPAAPISizeStub = sinon.stub(); - adUnits = [{ - 'code': '/19968336/header-bid-tag1', - 'mediaTypes': { - 'banner': { - 'sizes': [[728, 90]] - }, - }, - 'bids': [ - { - 'bidder': 'appnexus', - }, - { - 'bidder': 'rubicon', - }, - ] - }]; - }); - - afterEach(function () { - config.resetConfig(); - }); - - describe('makeBidRequests', () => { - before(() => { - NAVIGATOR_APIS.forEach(method => { - if (navigator[method] == null) { - navigator[method] = () => null; - after(() => { - delete navigator[method]; - }) - } - }) - }); - beforeEach(() => { - NAVIGATOR_APIS.forEach(method => { - sandbox.stub(navigator, method) - }) - }); - - function mark() { - return Object.fromEntries( - adapterManager.makeBidRequests( - adUnits, - Date.now(), - utils.getUniqueIdentifierStr(), - function callback() { - }, - [] - ).map(b => [b.bidderCode, b]) - ); - } - - async function testAsyncParams(bidderRequest) { - for (const method of NAVIGATOR_APIS) { - navigator[method].returns('result'); - expect(await bidderRequest.paapi[method]('arg').resolve()).to.eql('result'); - sinon.assert.calledWith(navigator[method], 'arg'); - } - } - - async function expectFledgeFlags(...enableFlags) { - const bidRequests = mark(); - expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); - if (bidRequests.appnexus.paapi?.enabled) { - await testAsyncParams(bidRequests.appnexus) - } - bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - - expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); - if (bidRequests.rubicon.paapi?.enabled) { - testAsyncParams(bidRequests.rubicon); - } - - bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - - Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { - if (bid.ortb2Imp?.ext?.ae) { - sinon.assert.match(bid.ortb2Imp.ext.igs, { - ae: bid.ortb2Imp.ext.ae, - biddable: 1 - }); - } - }); - } - - describe('with setConfig()', () => { - it('should set paapi.enabled correctly per bidder', async function () { - config.setConfig({ - bidderSequence: 'fixed', - paapi: { - enabled: true, - bidders: ['appnexus'], - defaultForSlots: 1, - } - }); - await expectFledgeFlags({ enabled: true, ae: 1 }, { enabled: false, ae: 0 }); - }); - - it('should set paapi.enabled correctly for all bidders', async function () { - config.setConfig({ - bidderSequence: 'fixed', - paapi: { - enabled: true, - defaultForSlots: 1, - } - }); - await expectFledgeFlags({ enabled: true, ae: 1 }, { enabled: true, ae: 1 }); - }); - - Object.entries({ - 'not set': { - cfg: {}, - componentSeller: false - }, - 'set': { - cfg: { - componentSeller: { - auctionConfig: { - decisionLogicURL: 'publisher.example' - } - } - }, - componentSeller: true - } - }).forEach(([t, { cfg, componentSeller }]) => { - it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1, - ...cfg - } - }); - Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); - }); - }); - }); - }); - describe('addPaapiData', () => { - function getEnrichedAdUnits() { - const next = sinon.stub(); - addPaapiData(next, adUnits); - sinon.assert.calledWith(next, adUnits); - return adUnits; - } - - function getImpExt() { - const next = sinon.stub(); - addPaapiData(next, adUnits); - sinon.assert.calledWith(next, adUnits); - return { - global: adUnits[0].ortb2Imp?.ext, - ...Object.fromEntries(adUnits[0].bids.map(bid => [bid.bidder, bid.ortb2Imp?.ext])) - } - } - - it('should not override pub-defined ext.ae', () => { - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1, - } - }); - Object.assign(adUnits[0], { ortb2Imp: { ext: { ae: 0 } } }); - sinon.assert.match(getImpExt(), { - global: { - ae: 0, - }, - rubicon: undefined, - appnexus: undefined - }); - }); - - it('should override per-bidder when excluded via paapi.bidders', () => { - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1, - bidders: ['rubicon'] - } - }) - sinon.assert.match(getImpExt(), { - global: { - ae: 1, - igs: { - ae: 1, - biddable: 1 - } - }, - rubicon: undefined, - appnexus: { - ae: 0, - igs: { - ae: 0, - biddable: 0 - } - } - }) - }) - - it('should populate ext.igs when request has ext.ae', () => { - config.setConfig({ - paapi: { - enabled: true - } - }); - Object.assign(adUnits[0], { ortb2Imp: { ext: { ae: 3 } } }); - sinon.assert.match(getImpExt(), { - global: { - ae: 3, - igs: { - ae: 3, - biddable: 1 - } - }, - rubicon: undefined, - appnexus: undefined, - }); - }); - - it('should not override pub-defined ext.igs', () => { - config.setConfig({ - paapi: { - enabled: true - } - }); - Object.assign(adUnits[0], { ortb2Imp: { ext: { ae: 1, igs: { biddable: 0 } } } }); - sinon.assert.match(getImpExt(), { - global: { - ae: 1, - igs: { - ae: 1, - biddable: 0 - } - }, - rubicon: undefined, - appnexus: undefined - }) - }); - - it('should fill ext.ae from ext.igs, if defined', () => { - config.setConfig({ - paapi: { - enabled: true - } - }); - Object.assign(adUnits[0], { ortb2Imp: { ext: { igs: {} } } }); - sinon.assert.match(getImpExt(), { - global: { - ae: 1, - igs: { - ae: 1, - biddable: 1 - } - }, - appnexus: undefined, - rubicon: undefined - }) - }); - - describe('ortb2Imp.ext.paapi.requestedSize', () => { - beforeEach(() => { - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1, - } - }); - }); - - it('should default to value returned by getPAAPISize', () => { - getPAAPISizeStub.returns([123, 321]); - expect(getImpExt().global.paapi).to.eql({ - requestedSize: { - width: 123, - height: 321 - } - }); - }); - - it('should not be overridden, if provided by the pub', () => { - adUnits[0].ortb2Imp = { - ext: { - paapi: { - requestedSize: { - width: '123px', - height: '321px' - } - } - } - }; - expect(getImpExt().global.paapi).to.eql({ - requestedSize: { - width: '123px', - height: '321px' - } - }) - sinon.assert.notCalled(getPAAPISizeStub); - }); - - it('should not be set if adUnit has no banner sizes', () => { - adUnits[0].mediaTypes = { - video: {} - }; - expect(getImpExt().global?.paapi?.requestedSize).to.not.exist; - }); - }); - }); - }); - }); - - describe('igb', () => { - let igb1, igb2; - const buyer1 = 'https://buyer1.example'; - const buyer2 = 'https://buyer2.example'; - beforeEach(() => { - igb1 = { - origin: buyer1, - cur: 'EUR', - maxbid: 1, - pbs: { - signal: 1 - }, - ps: { - priority: 1 - } - }; - igb2 = { - origin: buyer2, - cur: 'USD', - maxbid: 2, - pbs: { - signal: 2 - }, - ps: { - priority: 2 - } - }; - }); - - describe('mergeBuyers', () => { - it('should merge multiple igb into a partial auction config', () => { - sinon.assert.match(mergeBuyers([igb1, igb2]), { - interestGroupBuyers: [buyer1, buyer2], - perBuyerCurrencies: { - [buyer1]: 'EUR', - [buyer2]: 'USD' - }, - perBuyerSignals: { - [buyer1]: { - signal: 1 - }, - [buyer2]: { - signal: 2 - } - }, - perBuyerPrioritySignals: { - [buyer1]: { - priority: 1 - }, - [buyer2]: { - priority: 2 - } - }, - auctionSignals: { - prebid: { - perBuyerMaxbid: { - [buyer1]: 1, - [buyer2]: 2 - } - } - } - }); - }); - - Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { - it(`should not set ${configField} if ${igbField} is undefined`, () => { - delete igb1[igbField]; - expect(deepAccess(mergeBuyers([igb1, igb2]), configField)[buyer1]).to.not.exist; - }); - }); - - it('ignores igbs that have no origin', () => { - delete igb1.origin; - expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb2])); - }); - - it('ignores igbs with duplicate origin', () => { - igb2.origin = igb1.origin; - expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb1])); - }); - }); - - describe('partitionBuyers', () => { - it('should return a single partition when there are no duplicates', () => { - expect(partitionBuyers([igb1, igb2])).to.eql([[igb1, igb2]]); - }); - it('should ignore igbs that have no origin', () => { - delete igb1.origin; - expect(partitionBuyers([igb1, igb2])).to.eql([[igb2]]); - }); - it('should return a single partition when duplicates exist, but do not conflict', () => { - expect(partitionBuyers([igb1, igb2, deepClone(igb1)])).to.eql([[igb1, igb2]]); - }); - it('should return multiple partitions when there are conflicts', () => { - const igb3 = deepClone(igb1); - const igb4 = deepClone(igb1); - igb3.pbs.signal = 'conflict'; - igb4.ps.signal = 'conflict'; - expect(partitionBuyers([igb1, igb2, igb3, igb4])).to.eql([ - [igb1, igb2], - [igb3], - [igb4] - ]); - }); - }); - - describe('partitionBuyersByBidder', () => { - it('should split requests by bidder', () => { - expect(partitionBuyersByBidder([[{ bidder: 'a' }, igb1], [{ bidder: 'b' }, igb2]])).to.eql([ - [{ bidder: 'a' }, [igb1]], - [{ bidder: 'b' }, [igb2]] - ]); - }); - - it('accepts repeated buyers, if from different bidders', () => { - expect(partitionBuyersByBidder([ - [{ bidder: 'a', extra: 'data' }, igb1], - [{ bidder: 'b', more: 'data' }, igb1], - [{ bidder: 'a' }, igb2], - [{ bidder: 'b' }, igb2] - ])).to.eql([ - [{ bidder: 'a', extra: 'data' }, [igb1, igb2]], - [{ bidder: 'b', more: 'data' }, [igb1, igb2]] - ]); - }); - describe('buyersToAuctionConfig', () => { - let config, partitioners, merge, igbRequests; - beforeEach(() => { - config = { - auctionConfig: { - decisionLogicURL: 'mock-decision-logic' - } - }; - partitioners = { - compact: sinon.stub(), - expand: sinon.stub(), - }; - let i = 0; - merge = sinon.stub().callsFake(() => ({ config: i++ })); - igbRequests = [ - [{}, igb1], - [{}, igb2] - ]; - }); - - function toAuctionConfig(reqs = igbRequests) { - return buyersToAuctionConfigs(reqs, merge, config, partitioners); - } - - it('uses compact partitions by default, and returns an auction config for each one', () => { - partitioners.compact.returns([[{}, 1], [{}, 2]]); - const [cf1, cf2] = toAuctionConfig(); - sinon.assert.match(cf1[1], { - ...config.auctionConfig, - config: 0 - }); - sinon.assert.match(cf2[1], { - ...config.auctionConfig, - config: 1 - }); - sinon.assert.calledWith(partitioners.compact, igbRequests); - [1, 2].forEach(mockPart => sinon.assert.calledWith(merge, mockPart)); - }); - - it('uses per-bidder partition when config has separateAuctions', () => { - config.separateAuctions = true; - partitioners.expand.returns([]); - toAuctionConfig(); - sinon.assert.called(partitioners.expand); - }); - - it('does not return any auction config when configuration does not specify auctionConfig', () => { - delete config.auctionConfig; - expect(toAuctionConfig()).to.eql([]); - Object.values(partitioners).forEach(part => sinon.assert.notCalled(part)); - }); - - it('sets FPD in auction signals when partitioner returns it', () => { - const fpd = { - ortb2: { fpd: 1 }, - ortb2Imp: { fpd: 2 } - }; - partitioners.compact.returns([[{}], [fpd]]); - const [cf1, cf2] = toAuctionConfig(); - expect(cf1[1].auctionSignals?.prebid).to.not.exist; - expect(cf2[1].auctionSignals.prebid).to.eql(fpd); - }); - }); - }); - }); - - describe('getPAAPISize', () => { - before(() => { - getPAAPISize.getHooks().remove(); - }); - - Object.entries({ - 'ignores placeholders': { - in: [[1, 1], [0, 0], [3, 4]], - out: [3, 4] - }, - 'picks largest size by area': { - in: [[200, 100], [150, 150]], - out: [150, 150] - }, - 'can handle no sizes': { - in: [], - out: undefined - }, - 'can handle no input': { - in: undefined, - out: undefined - }, - 'can handle placeholder sizes': { - in: [[1, 1]], - out: undefined - } - }).forEach(([t, { in: input, out }]) => { - it(t, () => { - expect(getPAAPISize(input)).to.eql(out); - }); - }); - }); - - describe('buildPaapiParameters', () => { - let next, bidderRequest, spec, bids; - beforeEach(() => { - next = sinon.stub(); - spec = {}; - bidderRequest = { paapi: { enabled: true } }; - bids = []; - }); - - function runParamHook() { - return Promise.resolve(buildPAAPIParams(next, spec, bids, bidderRequest)); - } - - Object.entries({ - 'has no paapiParameters': () => null, - 'returns empty parameter map'() { - spec.paapiParameters = () => ({}) - }, - 'returns null parameter map'() { - spec.paapiParameters = () => null - }, - 'returns params, but PAAPI is disabled'() { - bidderRequest.paapi.enabled = false; - spec.paapiParameters = () => ({ param: new AsyncPAAPIParam() }) - } - }).forEach(([t, setup]) => { - it(`should do nothing if spec ${t}`, async () => { - setup(); - await runParamHook(); - sinon.assert.calledWith(next, spec, bids, bidderRequest); - }) - }) - - describe('when paapiParameters returns a map', () => { - let params; - beforeEach(() => { - spec.paapiParameters = sinon.stub().callsFake(() => params); - }); - it('should be invoked with bids & bidderRequest', async () => { - await runParamHook(); - sinon.assert.calledWith(spec.paapiParameters, bids, bidderRequest); - }); - it('should leave most things (including promises) untouched', async () => { - params = { - 'p1': 'scalar', - 'p2': Promise.resolve() - } - await runParamHook(); - expect(bidderRequest.paapi.params).to.eql(params); - }); - it('should resolve async PAAPI parameeters', async () => { - params = { - 'resolved': new AsyncPAAPIParam(() => Promise.resolve('value')), - } - await runParamHook(); - expect(bidderRequest.paapi.params).to.eql({ - 'resolved': 'value' - }) - }) - - it('should still call next if the resolution fails', async () => { - params = { - error: new AsyncPAAPIParam(() => Promise.reject(new Error())) - } - await runParamHook(); - sinon.assert.called(next); - expect(bidderRequest.paapi.params).to.not.exist; - }) - }) - }) - - describe('parallel PAAPI auctions', () => { - describe('parallellPaapiProcessing', () => { - let next, spec, bids, bidderRequest, restOfTheArgs, mockConfig, mockAuction, bidsReceived, bidderRequests, adUnitCodes, adUnits; - - beforeEach(() => { - next = sinon.stub(); - spec = { - code: 'mockBidder', - }; - bids = [{ - bidder: 'mockBidder', - bidId: 'bidId', - adUnitCode: 'au', - auctionId: 'aid', - ortb2: { - source: { - tid: 'aid' - }, - }, - mediaTypes: { - banner: { - sizes: [[123, 321]] - } - } - }]; - bidderRequest = { - auctionId: 'aid', - bidderCode: 'mockBidder', - paapi: { enabled: true }, - bids, - ortb2: { - source: { - tid: 'aid' - } - } - }; - restOfTheArgs = [{ more: 'args' }]; - mockConfig = { - seller: 'mock.seller', - decisionLogicURL: 'mock.seller/decisionLogic', - interestGroupBuyers: ['mock.buyer'] - } - mockAuction = {}; - bidsReceived = [{ adUnitCode: 'au', cpm: 1 }]; - adUnits = [{ code: 'au' }] - adUnitCodes = ['au']; - bidderRequests = [bidderRequest]; - sandbox.stub(auctionManager.index, 'getAuction').callsFake(() => mockAuction); - sandbox.stub(auctionManager.index, 'getAdUnit').callsFake((req) => bids.find(bid => bid.adUnitCode === req.adUnitCode)) - config.setConfig({ paapi: { enabled: true } }); - }); - - afterEach(() => { - sinon.assert.calledWith(next, spec, bids, bidderRequest, ...restOfTheArgs); - config.resetConfig(); - }); - - function startParallel() { - parallelPaapiProcessing(next, spec, bids, bidderRequest, ...restOfTheArgs); - onAuctionInit({ auctionId: 'aid' }) - } - - function endAuction() { - events.emit(EVENTS.AUCTION_END, { auctionId: 'aid', bidsReceived, bidderRequests, adUnitCodes, adUnits }) - } - - describe('should have no effect when', () => { - afterEach(() => { - expect(getPAAPIConfig({}, true)).to.eql({ au: null }); - }) - it('spec has no buildPAAPIConfigs', () => { - startParallel(); - }); - Object.entries({ - 'returns no configs': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); }, - 'throws': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => { throw new Error() }) }, - 'returns too little config': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{ bidId: 'bidId', config: { seller: 'mock.seller' } }]) }, - 'bidder is not paapi enabled': () => { - bidderRequest.paapi.enabled = false; - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{ config: mockConfig, bidId: 'bidId' }]) - }, - 'paapi module is not enabled': () => { - delete bidderRequest.paapi; - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{ config: mockConfig, bidId: 'bidId' }]) - }, - 'bidId points to missing bid': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{ config: mockConfig, bidId: 'missing' }]) } - }).forEach(([t, setup]) => { - it(`buildPAAPIConfigs ${t}`, () => { - setup(); - startParallel(); - }); - }); - }); - - function resolveConfig(auctionConfig) { - return Promise.all( - Object.entries(auctionConfig) - .map(([key, value]) => Promise.resolve(value).then(value => [key, value])) - ).then(result => Object.fromEntries(result)) - } - - describe('when buildPAAPIConfigs returns valid config', () => { - let builtCfg; - beforeEach(() => { - builtCfg = [{ bidId: 'bidId', config: mockConfig }]; - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); - }); - - it('should make async config available from getPAAPIConfig', () => { - startParallel(); - const actual = getPAAPIConfig(); - const promises = Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match((arg) => arg instanceof Promise)])) - sinon.assert.match(actual, { - au: sinon.match({ - ...promises, - requestedSize: { - width: 123, - height: 321 - }, - componentAuctions: [ - sinon.match({ - ...mockConfig, - ...promises, - requestedSize: { - width: 123, - height: 321 - } - }) - ] - }) - }); - }); - - it('should work when called multiple times for the same auction', () => { - startParallel(); - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); - startParallel(); - expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); - }); - - it('should hide TIDs from buildPAAPIConfigs', () => { - config.setConfig({ enableTIDs: false }); - startParallel(); - sinon.assert.calledWith( - spec.buildPAAPIConfigs, - sinon.match(bidRequests => bidRequests.every(req => req.auctionId == null)), - sinon.match(bidderRequest => bidderRequest.auctionId == null) - ); - }); - - it('should show TIDs when enabled', () => { - config.setConfig({ enableTIDs: true }); - startParallel(); - sinon.assert.calledWith( - spec.buildPAAPIConfigs, - sinon.match(bidRequests => bidRequests.every(req => req.auctionId === 'aid')), - sinon.match(bidderRequest => bidderRequest.auctionId === 'aid') - ) - }) - - it('should respect requestedSize from adapter', () => { - mockConfig.requestedSize = { width: 1, height: 2 }; - startParallel(); - sinon.assert.match(getPAAPIConfig().au, { - requestedSize: { - width: 123, - height: 321 - }, - componentAuctions: [sinon.match({ - requestedSize: { - width: 1, - height: 2 - } - })] - }) - }) - - it('should not accept multiple partial configs for the same bid/seller', () => { - builtCfg.push(builtCfg[0]) - startParallel(); - expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); - }); - it('should resolve top level config with auction signals', async () => { - startParallel(); - let config = getPAAPIConfig().au; - endAuction(); - config = await resolveConfig(config); - sinon.assert.match(config, { - auctionSignals: { - prebid: { bidfloor: 1 } - } - }) - }); - - describe('when adapter returns the rest of auction config', () => { - let configRemainder; - beforeEach(() => { - configRemainder = { - ...Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, { type: signal }])), - seller: 'mock.seller' - }; - }) - function returnRemainder() { - addPaapiConfigHook(sinon.stub(), bids[0], { config: configRemainder }); - } - it('should resolve component configs with values returned by adapters', async () => { - startParallel(); - let config = getPAAPIConfig().au.componentAuctions[0]; - returnRemainder(); - endAuction(); - config = await resolveConfig(config); - sinon.assert.match(config, configRemainder); - }); - - it('should pick first config that matches bidId/seller', async () => { - startParallel(); - let config = getPAAPIConfig().au.componentAuctions[0]; - returnRemainder(); - const expectedSignals = { ...configRemainder }; - configRemainder = { - ...configRemainder, - auctionSignals: { - this: 'should be ignored' - } - } - returnRemainder(); - endAuction(); - config = await resolveConfig(config); - sinon.assert.match(config, expectedSignals); - }); - - describe('should default to values returned from buildPAAPIConfigs when interpretResponse does not return', () => { - beforeEach(() => { - ASYNC_SIGNALS.forEach(signal => mockConfig[signal] = { default: signal }) - }); - Object.entries({ - 'returns no matching config'() { - }, - 'does not include values in response'() { - configRemainder = {}; - returnRemainder(); - } - }).forEach(([t, postResponse]) => { - it(t, async () => { - startParallel(); - let config = getPAAPIConfig().au.componentAuctions[0]; - postResponse(); - endAuction(); - config = await resolveConfig(config); - sinon.assert.match(config, mockConfig); - }); - }); - }); - - it('should resolve to undefined when no value is available', async () => { - startParallel(); - let config = getPAAPIConfig().au.componentAuctions[0]; - delete configRemainder.sellerSignals; - returnRemainder(); - endAuction(); - config = await resolveConfig(config); - expect(config.sellerSignals).to.be.undefined; - }); - - [ - { - start: { t: 'scalar', value: 'str' }, - end: { t: 'array', value: ['abc'] }, - should: { t: 'array', value: ['abc'] } - }, - { - start: { t: 'object', value: { a: 'b' } }, - end: { t: 'scalar', value: 'abc' }, - should: { t: 'scalar', value: 'abc' } - }, - { - start: { t: 'object', value: { outer: { inner: 'val' } } }, - end: { t: 'object', value: { outer: { other: 'val' } } }, - should: { t: 'merge', value: { outer: { inner: 'val', other: 'val' } } } - } - ].forEach(({ start, end, should }) => { - it(`when buildPAAPIConfigs returns ${start.t}, interpretResponse return ${end.t}, promise should resolve to ${should.t}`, async () => { - mockConfig.sellerSignals = start.value - startParallel(); - let config = getPAAPIConfig().au.componentAuctions[0]; - configRemainder.sellerSignals = end.value; - returnRemainder(); - endAuction(); - config = await resolveConfig(config); - expect(config.sellerSignals).to.eql(should.value); - }) - }) - - it('should make extra configs available', async () => { - startParallel(); - returnRemainder(); - configRemainder = { ...configRemainder, seller: 'other.seller' }; - returnRemainder(); - endAuction(); - let configs = getPAAPIConfig().au.componentAuctions; - configs = [await resolveConfig(configs[0]), configs[1]]; - expect(configs.map(cfg => cfg.seller)).to.eql(['mock.seller', 'other.seller']); - }); - - describe('submodule\'s onAuctionConfig', () => { - let onAuctionConfig; - beforeEach(() => { - onAuctionConfig = sinon.stub(); - registerSubmodule({ onAuctionConfig }) - }); - - Object.entries({ - 'parallel=true, some configs deferred': { - setup() { - config.mergeConfig({ paapi: { parallel: true } }) - }, - delayed: false, - }, - 'parallel=true, no deferred configs': { - setup() { - config.mergeConfig({ paapi: { parallel: true } }); - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); - }, - delayed: true - }, - 'parallel=false, some configs deferred': { - setup() { - config.mergeConfig({ paapi: { parallel: false } }) - }, - delayed: true - } - }).forEach(([t, { setup, delayed }]) => { - describe(`when ${t}`, () => { - beforeEach(() => { - mockAuction.requestsDone = Promise.resolve(); - setup(); - }); - - function expectInvoked(shouldBeInvoked) { - if (shouldBeInvoked) { - sinon.assert.calledWith(onAuctionConfig, 'aid', sinon.match(arg => arg.au.componentAuctions[0].seller === 'mock.seller')); - } else { - sinon.assert.notCalled(onAuctionConfig); - } - } - - it(`should invoke onAuctionConfig when ${delayed ? 'auction ends' : 'auction requests have started'}`, async () => { - startParallel(); - await mockAuction.requestsDone; - expectInvoked(!delayed); - onAuctionConfig.resetHistory(); - returnRemainder(); - endAuction(); - expectInvoked(delayed); - }) - }) - }) - }) - }); - }); - describe('when buildPAAPIConfigs returns igb', () => { - let builtCfg, igb, auctionConfig; - beforeEach(() => { - igb = { origin: 'mock.buyer' } - builtCfg = [{ bidId: 'bidId', igb }]; - spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); - auctionConfig = { - seller: 'mock.seller', - decisionLogicUrl: 'mock.seller/decisionLogic' - } - config.mergeConfig({ - paapi: { - componentSeller: { - auctionConfig - } - } - }) - bidderRequest.paapi.componentSeller = true; - }); - Object.entries({ - 'componentSeller not configured'() { - bidderRequest.paapi.componentSeller = false; - }, - 'buildPAAPIconfig returns nothing'() { - builtCfg = [] - }, - 'returned igb is not valid'() { - builtCfg = [{ bidId: 'bidId', igb: {} }]; - } - }).forEach(([t, setup]) => { - it(`should have no effect when ${t}`, () => { - setup(); - startParallel(); - expect(getPAAPIConfig()).to.eql({}); - }) - }) - - describe('when component seller is set up', () => { - it('should generate a deferred auctionConfig', () => { - startParallel(); - sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { - ...auctionConfig, - interestGroupBuyers: ['mock.buyer'], - }) - }); - - it('should use signal values from componentSeller.auctionConfig', async () => { - auctionConfig.auctionSignals = { test: 'signal' }; - config.mergeConfig({ - paapi: { componentSeller: { auctionConfig } } - }) - startParallel(); - endAuction(); - const cfg = await resolveConfig(getPAAPIConfig().au.componentAuctions[0]); - sinon.assert.match(cfg.auctionSignals, auctionConfig.auctionSignals); - }) - - it('should collate buyers', () => { - startParallel(); - startParallel(); - sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { - interestGroupBuyers: ['mock.buyer'] - }); - }); - - function returnIgb(igb) { - addPaapiConfigHook(sinon.stub(), bids[0], { igb }); - } - - it('should resolve to values from interpretResponse as well as buildPAAPIConfigs', async () => { - igb.cur = 'cur'; - igb.pbs = { over: 'ridden' } - startParallel(); - let cfg = getPAAPIConfig().au.componentAuctions[0]; - returnIgb({ - origin: 'mock.buyer', - pbs: { some: 'signal' } - }); - endAuction(); - cfg = await resolveConfig(cfg); - sinon.assert.match(cfg, { - perBuyerSignals: { - [igb.origin]: { some: 'signal' }, - }, - perBuyerCurrencies: { - [igb.origin]: 'cur' - } - }) - }); - - it('should not overwrite config once resolved', () => { - startParallel(); - returnIgb({ - origin: 'mock.buyer', - }); - endAuction(); - const cfg = getPAAPIConfig().au; - sinon.assert.match(cfg, Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match(arg => arg instanceof Promise)]))) - }) - - it('can resolve multiple igbs', async () => { - igb.cur = 'cur1'; - startParallel(); - spec.code = 'other'; - igb.origin = 'other.buyer' - igb.cur = 'cur2' - startParallel(); - let cfg = getPAAPIConfig().au.componentAuctions[0]; - returnIgb({ - origin: 'mock.buyer', - pbs: { signal: 1 } - }); - returnIgb({ - origin: 'other.buyer', - pbs: { signal: 2 } - }); - endAuction(); - cfg = await resolveConfig(cfg); - sinon.assert.match(cfg, { - perBuyerSignals: { - 'mock.buyer': { signal: 1 }, - 'other.buyer': { signal: 2 } - }, - perBuyerCurrencies: { - 'mock.buyer': 'cur1', - 'other.buyer': 'cur2' - } - }) - }) - - function startMultiple() { - startParallel(); - spec.code = 'other'; - igb.origin = 'other.buyer' - startParallel(); - } - - describe('when using separateAuctions=false', () => { - beforeEach(() => { - config.mergeConfig({ - paapi: { - componentSeller: { - separateAuctions: false - } - } - }) - }); - - it('should merge igb from different specs into a single auction config', () => { - startMultiple(); - sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { - interestGroupBuyers: ['mock.buyer', 'other.buyer'] - }); - }); - }) - - describe('when using separateAuctions=true', () => { - beforeEach(() => { - config.mergeConfig({ - paapi: { - componentSeller: { - separateAuctions: true - } - } - }) - }); - it('should generate an auction config for each bidder', () => { - startMultiple(); - const components = getPAAPIConfig().au.componentAuctions; - sinon.assert.match(components[0], { - interestGroupBuyers: ['mock.buyer'] - }) - sinon.assert.match(components[1], { - interestGroupBuyers: ['other.buyer'] - }) - }) - }) - }) - }) - }); - }); - - describe('ortb processors for fledge', () => { - it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = { ext: { ae: 1, igs: {} } }; - setImpExtAe(imp, {}, { bidderRequest: {} }); - expect(imp.ext.ae).to.not.exist; - expect(imp.ext.igs).to.not.exist; - }); - it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = { ext: { ae: 2, igs: { biddable: 0 } } }; - setImpExtAe(imp, {}, { bidderRequest: { paapi: { enabled: true } } }); - expect(imp.ext).to.eql({ - ae: 2, - igs: { - biddable: 0 - } - }); - }); - - describe('response parsing', () => { - function generateImpCtx(fledgeFlags) { - return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, { imp: { ext: { ae: fledgeEnabled } } }])); - } - - function extractResult(type, ctx) { - return Object.fromEntries( - Object.entries(ctx) - .map(([impid, ctx]) => [impid, ctx.paapiConfigs?.map(cfg => cfg[type].id)]) - .filter(([_, val]) => val != null) - ); - } - - Object.entries({ - 'parseExtPrebidFledge': { - parser: parseExtPrebidFledge, - responses: { - 'ext.prebid.fledge'(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } - } - }; - }, - } - }, - 'parseExtIgi': { - parser: parseExtIgi, - responses: { - 'ext.igi.igs'(configs) { - return { - ext: { - igi: [{ - igs: configs - }] - } - }; - }, - 'ext.igi.igs with impid on igi'(configs) { - return { - ext: { - igi: configs.map(cfg => { - const impid = cfg.impid; - delete cfg.impid; - return { - impid, - igs: [cfg] - }; - }) - } - }; - }, - 'ext.igi.igs with conflicting impid'(configs) { - return { - ext: { - igi: [{ - impid: 'conflict', - igs: configs - }] - } - }; - } - } - } - }).forEach(([t, { parser, responses }]) => { - describe(t, () => { - Object.entries(responses).forEach(([t, packageConfigs]) => { - describe(`when response uses ${t}`, () => { - function generateCfg(impid, ...ids) { - return ids.map((id) => ({ impid, config: { id } })); - } - - it('should collect auction 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))) - ); - parser({}, resp, ctx); - expect(extractResult('config', 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)); - parser({}, resp, ctx); - expect(extractResult('config', ctx.impContext)).to.eql({}); - }); - }); - }); - }); - }); - - describe('response ext.igi.igb', () => { - it('should collect igb by imp', () => { - const ctx = { - impContext: generateImpCtx({ e1: 1, e2: 1, d1: 0 }) - }; - const resp = { - ext: { - igi: [ - { - impid: 'e1', - igb: [ - { id: 1 }, - { id: 2 } - ] - }, - { - impid: 'e2', - igb: [ - { id: 3 } - ] - }, - { - impid: 'd1', - igb: [ - { id: 4 } - ] - } - ] - } - }; - parseExtIgi({}, resp, ctx); - expect(extractResult('igb', ctx.impContext)).to.eql({ - e1: [1, 2], - e2: [3], - }); - }); - }); - }); - - describe('setResponsePaapiConfigs', () => { - it('should set paapi configs/igb paired with their corresponding bid id', () => { - const ctx = { - impContext: { - 1: { - bidRequest: { bidId: 'bid1' }, - paapiConfigs: [{ config: { id: 1 } }, { config: { id: 2 } }] - }, - 2: { - bidRequest: { bidId: 'bid2' }, - paapiConfigs: [{ config: { id: 3 } }] - }, - 3: { - bidRequest: { bidId: 'bid3' } - }, - 4: { - bidRequest: { bidId: 'bid1' }, - paapiConfigs: [{ igb: { id: 4 } }] - } - } - }; - const resp = {}; - setResponsePaapiConfigs(resp, {}, ctx); - expect(resp.paapi).to.eql([ - { bidId: 'bid1', config: { id: 1 } }, - { bidId: 'bid1', config: { id: 2 } }, - { bidId: 'bid2', config: { id: 3 } }, - { bidId: 'bid1', igb: { id: 4 } } - ]); - }); - it('should not set paapi if no config or igb exists', () => { - const resp = {}; - setResponsePaapiConfigs(resp, {}, { - impContext: { - 1: { - paapiConfigs: [] - }, - 2: {} - } - }); - expect(resp).to.eql({}); - }); - }); - }); -}); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 5e8cd74ec2e..620a3d4fddf 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -23,17 +23,15 @@ import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/consentManagementGpp.js'; -import 'modules/paapi.js'; import * as redactor from 'src/activities/redactor.js'; import * as activityRules from 'src/activities/rules.js'; import { hook } from '../../../src/hook.js'; import { decorateAdUnitsWithNativeParams } from '../../../src/native.js'; import { auctionManager } from '../../../src/auctionManager.js'; import { stubAuctionIndex } from '../../helpers/indexStub.js'; -import { addPaapiConfig, registerBidder } from 'src/adapters/bidderFactory.js'; +import { registerBidder } from 'src/adapters/bidderFactory.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; import { addFPDToBidderRequest } from '../../helpers/fpd.js'; -import { deepSetValue } from '../../../src/utils.js'; import { ACTIVITY_TRANSMIT_UFPD } from '../../../src/activities/activities.js'; import { MODULE_TYPE_PREBID } from '../../../src/activities/modules.js'; import { @@ -3313,23 +3311,6 @@ describe('S2S Adapter', function () { expect(response).to.have.property('ttl', 60); }); - it('handles seatnonbid responses and emits SEAT_NON_BID', function () { - const original = CONFIG; - CONFIG.extPrebid = { returnallbidstatus: true }; - const nonbidResponse = { ...RESPONSE_OPENRTB, ext: { seatnonbid: [{}] } }; - config.setConfig({ CONFIG }); - CONFIG = original; - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const responding = deepClone(nonbidResponse); - Object.assign(responding.ext.seatnonbid, [{ auctionId: 2 }]) - server.requests[0].respond(200, {}, JSON.stringify(responding)); - const event = events.emit.thirdCall.args; - 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); - }); - it('emits the PBS_ANALYTICS event and captures seatnonbid responses', function () { const original = CONFIG; CONFIG.extPrebid = { returnallbidstatus: true }; @@ -3340,7 +3321,7 @@ describe('S2S Adapter', function () { const responding = deepClone(nonbidResponse); Object.assign(responding.ext.seatnonbid, [{ auctionId: 2 }]) server.requests[0].respond(200, {}, JSON.stringify(responding)); - const event = events.emit.getCall(3).args; + const event = events.emit.getCall(2).args; expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); expect(event[1].requestedBidders).to.deep.equal(['appnexus']); @@ -3734,108 +3715,6 @@ describe('S2S Adapter', function () { }); }); }); - describe('when the response contains ext.prebid.fledge', () => { - const AU = 'div-gpt-ad-1460505748561-0'; - const FLEDGE_RESP = { - ext: { - prebid: { - fledge: { - auctionconfigs: [ - { - impid: AU, - bidder: 'appnexus', - config: { - id: 1 - } - }, - { - impid: AU, - bidder: 'other', - config: { - id: 2 - } - } - ] - } - } - } - } - - let fledgeStub, request, bidderRequests; - - function fledgeHook(next, ...args) { - fledgeStub(...args); - } - - before(() => { - addPaapiConfig.before(fledgeHook); - }); - - after(() => { - addPaapiConfig.getHooks({ hook: fledgeHook }).remove(); - }) - - beforeEach(function () { - fledgeStub = sinon.stub(); - config.setConfig({ - s2sConfig: CONFIG, - }); - bidderRequests = deepClone(BID_REQUESTS); - bidderRequests.forEach(req => { - Object.assign(req, { - paapi: { - enabled: 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 }), sinon.match({ config: { id: 1 } })) - sinon.assert.calledWith(fledgeStub, sinon.match({ auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined }), sinon.match({ config: { id: 2 } })) - } - - it('calls addPaapiConfig 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; - expectFledgeCalls(); - }); - - it('calls addPaapiConfig 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; - expectFledgeCalls(); - }); - - it('wraps call in runWithBidder', () => { - let fail = false; - fledgeStub.callsFake(({ bidder }) => { - try { - expect(bidder).to.exist.and.to.eql(config.getCurrentBidder()); - } catch (e) { - fail = true; - } - }); - adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); - server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); - expect(fail).to.be.false; - }) - }); }); describe('bid won events', function () { diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index 50264b9251b..2f57022d229 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1888,13 +1888,13 @@ describe('addViewabilityToImp', () => { }); it('should add viewability to imp.ext when measurable', () => { - addViewabilityToImp(imp, 'Div1', { w: 300, h: 250 }); + addViewabilityToImp(imp, { adUnitCode: 'Div1' }, { w: 300, h: 250 }); expect(imp.ext).to.have.property('viewability'); }); it('should set viewability amount to "na" if not measurable (e.g., in iframe)', () => { const isIframeStub = sandbox.stub(utils, 'inIframe').returns(true); - addViewabilityToImp(imp, 'Div1', { w: 300, h: 250 }); + addViewabilityToImp(imp, { adUnitCode: 'Div1' }, { w: 300, h: 250 }); expect(imp.ext).to.have.property('viewability'); expect(imp.ext.viewability.amount).to.equal('na'); }); @@ -1902,13 +1902,13 @@ describe('addViewabilityToImp', () => { it('should not add viewability if element is not found', () => { document.getElementById.restore(); sandbox.stub(document, 'getElementById').returns(null); - addViewabilityToImp(imp, 'Div1', { w: 300, h: 250 }); + addViewabilityToImp(imp, { adUnitCode: 'Div1' }, { w: 300, h: 250 }); expect(imp.ext).to.not.have.property('viewability'); }); it('should create imp.ext if not present', () => { imp = {}; - addViewabilityToImp(imp, 'Div1', { w: 300, h: 250 }); + addViewabilityToImp(imp, { adUnitCode: 'Div1' }, { w: 300, h: 250 }); expect(imp.ext).to.exist; expect(imp.ext).to.have.property('viewability'); }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js deleted file mode 100644 index 7414999eb88..00000000000 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ /dev/null @@ -1,859 +0,0 @@ -import { expect } from 'chai'; -import { - QUANTCAST_DOMAIN, - QUANTCAST_TEST_DOMAIN, - QUANTCAST_NET_REVENUE, - QUANTCAST_TTL, - QUANTCAST_TEST_PUBLISHER, - QUANTCAST_PROTOCOL, - QUANTCAST_PORT, - spec as qcSpec, - storage -} from '../../../modules/quantcastBidAdapter.js'; -import { newBidder } from '../../../src/adapters/bidderFactory.js'; -import { parseUrl } from 'src/utils.js'; -import { config } from 'src/config.js'; -import { getGlobal } from '../../../src/prebidGlobal.js'; - -describe('Quantcast adapter', function () { - const quantcastAdapter = newBidder(qcSpec); - let bidRequest; - let bidderRequest; - - afterEach(function () { - getGlobal().bidderSettings = {}; - }); - beforeEach(function () { - getGlobal().bidderSettings = { - quantcast: { - storageAllowed: true - } - }; - bidRequest = { - bidder: 'quantcast', - bidId: '2f7b179d443f14', - auctionId: '595ffa73-d78a-46c9-b18e-f99548a5be6b', - bidderRequestId: '1cc026909c24c8', - placementCode: 'div-gpt-ad-1438287399331-0', - params: { - publisherId: QUANTCAST_TEST_PUBLISHER, // REQUIRED - Publisher ID provided by Quantcast - battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 - }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }; - - bidderRequest = { - refererInfo: { - page: 'http://example.com/hello.html', - ref: 'http://example.com/hello.html', - domain: 'example.com' - } - }; - - storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); - }); - - function setupVideoBidRequest(videoParams, mediaTypesParams) { - bidRequest.params = { - publisherId: 'test-publisher', // REQUIRED - Publisher ID provided by Quantcast - // Video object as specified in OpenRTB 2.5 - video: videoParams - }; - if (mediaTypesParams) { - bidRequest['mediaTypes'] = { - video: mediaTypesParams - } - } - }; - - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(quantcastAdapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - - describe('`isBidRequestValid`', function () { - it('should return `true` when bid has publisherId', function () { - const bidRequest = { - bidder: 'quantcast', - params: { - publisherId: 'my_publisher_id' - } - }; - - expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return `false` when bid has no publisherId', function () { - const bidRequest = { - bidder: 'quantcast', - params: { - } - }; - - expect(qcSpec.isBidRequestValid(bidRequest)).to.equal(false); - }); - }); - - describe('`buildRequests`', function () { - it('sends secure bid requests', function () { - const requests = qcSpec.buildRequests([bidRequest]); - const url = parseUrl(requests[0]['url']); - expect(url.protocol).to.equal('https'); - }); - - it('sends bid requests to Quantcast Canary Endpoint if `publisherId` is `test-publisher`', function () { - const requests = qcSpec.buildRequests([bidRequest]); - const url = parseUrl(requests[0]['url']); - expect(url.hostname).to.equal(QUANTCAST_TEST_DOMAIN); - }); - - it('sends bid requests to default endpoint for non standard publisher IDs', function () { - const modifiedBidRequest = Object.assign({}, bidRequest, { - params: Object.assign({}, bidRequest.params, { - publisherId: 'foo-bar', - }), - }); - const requests = qcSpec.buildRequests([modifiedBidRequest]); - expect(requests[0]['url']).to.equal( - `${QUANTCAST_PROTOCOL}://${QUANTCAST_DOMAIN}:${QUANTCAST_PORT}/qchb` - ); - }); - - it('sends bid requests to Quantcast Header Bidding Endpoints via POST', function () { - const requests = qcSpec.buildRequests([bidRequest]); - - expect(requests[0].method).to.equal('POST'); - }); - - const expectedBannerBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - banner: { - battr: [1, 2], - sizes: [{ width: 300, height: 250 }] - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - it('sends banner bid requests contains all the required parameters', function () { - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests[0].data).to.equal(JSON.stringify(expectedBannerBidRequest)); - }); - - it('supports deprecated banner format', function () { - bidRequest.sizes = bidRequest.mediaTypes.banner.sizes; - delete bidRequest.mediaTypes; - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests[0].data).to.equal(JSON.stringify(expectedBannerBidRequest)); - }); - - it('sends video bid requests containing all the required parameters', function () { - setupVideoBidRequest({ - mimes: ['video/mp4'], // required - minduration: 3, // optional - maxduration: 5, // optional - protocols: [3], // optional - startdelay: 1, // optional - linearity: 1, // optinal - battr: [1, 2], // optional - maxbitrate: 10, // optional - playbackmethod: [1], // optional - delivery: [1], // optional - api: [2, 3] // optional - }, { - context: 'instream', - playerSize: [600, 300] - }); - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedVideoBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - video: { - mimes: ['video/mp4'], - minduration: 3, - maxduration: 5, - protocols: [3], - startdelay: 1, - linearity: 1, - battr: [1, 2], - maxbitrate: 10, - playbackmethod: [1], - delivery: [1], - api: [2, 3], - w: 600, - h: 300 - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); - }); - - it('sends video bid requests containing all the required parameters from mediaTypes', function() { - setupVideoBidRequest(null, { - mimes: ['video/mp4'], // required - minduration: 3, // optional - maxduration: 5, // optional - protocols: [3], // optional - startdelay: 1, // optional - linearity: 1, // optinal - battr: [1, 2], // optional - maxbitrate: 10, // optional - playbackmethod: [1], // optional - delivery: [1], // optional - api: [2, 3], // optional - context: 'instream', - playerSize: [600, 300] - }); - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedVideoBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - video: { - mimes: ['video/mp4'], - minduration: 3, - maxduration: 5, - protocols: [3], - startdelay: 1, - linearity: 1, - battr: [1, 2], - maxbitrate: 10, - playbackmethod: [1], - delivery: [1], - api: [2, 3], - w: 600, - h: 300 - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); - }); - - it('overrides video parameters with parameters from adunit', function() { - setupVideoBidRequest({ - mimes: ['video/mp4'] - }, { - context: 'instream', - playerSize: [600, 300] - }); - bidRequest.mediaTypes.video.mimes = ['video/webm']; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedVideoBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - video: { - mimes: ['video/webm'], - w: 600, - h: 300 - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); - }); - - it('sends video bid request when no video parameters are given', function () { - setupVideoBidRequest(null, { - context: 'instream', - playerSize: [600, 300] - }); - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedVideoBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - video: { - w: 600, - h: 300 - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - expect(requests[0].data).to.equal(JSON.stringify(expectedVideoBidRequest)); - }); - - it('ignores unsupported video bid requests', function () { - bidRequest.mediaTypes = { - video: { - context: 'outstream', - playerSize: [[550, 310]] - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.be.empty; - }); - - it('parses multi-format bid request', function () { - bidRequest.mediaTypes = { - banner: { sizes: [[300, 250], [728, 90], [250, 250], [468, 60], [320, 50]] }, - native: { - image: { required: true, sizes: [150, 50] }, - title: { required: true, len: 80 }, - sponsoredBy: { required: true }, - clickUrl: { required: true }, - privacyLink: { required: false }, - body: { required: true }, - icon: { required: true, sizes: [50, 50] } - }, - video: { - context: 'outstream', - playerSize: [[550, 310]] - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [{ - banner: { - battr: [1, 2], - sizes: [ - { width: 300, height: 250 }, - { width: 728, height: 90 }, - { width: 250, height: 250 }, - { width: 468, height: 60 }, - { width: 320, height: 50 } - ] - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - }], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - uspSignal: 0, - coppa: 0, - prebidJsVersion: '$prebid.version$', - fpa: '' - }; - - expect(requests[0].data).to.equal(JSON.stringify(expectedBidRequest)); - }); - }); - - it('propagates GDPR consent string and signal', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString' - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - - expect(parsed.gdprSignal).to.equal(1); - expect(parsed.gdprConsent).to.equal('consentString'); - }); - - it('allows TCF v2 request when Quantcast has consent for purpose 1', function() { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - '11': true - } - }, - purpose: { - consents: { - '1': true - } - } - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - - expect(parsed.gdprSignal).to.equal(1); - expect(parsed.gdprConsent).to.equal('consentString'); - }); - - it('blocks TCF v2 request when no consent for Quantcast', function() { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - '11': false - } - }, - purpose: { - consents: { - '1': true - } - } - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.equal(undefined); - }); - - it('blocks TCF v2 request when no consent for purpose 1', function() { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - '11': true - } - }, - purpose: { - consents: { - '1': false - } - } - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.equal(undefined); - }); - - it('blocks TCF v2 request when Quantcast not allowed by publisher', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - '11': true - } - }, - purpose: { - consents: { - '1': true - } - }, - publisher: { - restrictions: { - '1': { - '11': 0 - } - } - } - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.equal(undefined); - }); - - it('blocks TCF v2 request when legitimate interest required', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendor: { - consents: { - '11': true - } - }, - purpose: { - consents: { - '1': true - } - }, - publisher: { - restrictions: { - '1': { - '11': 2 - } - } - } - }, - apiVersion: 2 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.equal(undefined); - }); - - it('propagates US Privacy/CCPA consent information', function () { - const bidderRequest = { uspConsent: 'consentString' } - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - expect(parsed.uspSignal).to.equal(1); - expect(parsed.uspConsent).to.equal('consentString'); - }); - - it('propagates Quantcast first-party cookie (fpa)', function() { - storage.setCookie('__qca', 'P0-TestFPA'); - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - expect(parsed.fpa).to.equal('P0-TestFPA'); - }); - - describe('propagates coppa', function() { - let sandbox; - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('propagates coppa as 1 if coppa param is set to true in the bid request', function () { - bidRequest.params = { - publisherId: 'test_publisher_id', - coppa: true - }; - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'coppa': true - }; - return config[key]; - }); - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - expect(JSON.parse(requests[0].data).coppa).to.equal(1); - }); - - it('propagates coppa as 0 if there is no coppa param or coppa is set to false in the bid request', function () { - const requestsWithoutCoppa = qcSpec.buildRequests([bidRequest], bidderRequest); - expect(JSON.parse(requestsWithoutCoppa[0].data).coppa).to.equal(0); - - bidRequest.params = { - publisherId: 'test_publisher_id', - coppa: false - }; - sandbox.stub(config, 'getConfig').callsFake((key) => { - const config = { - 'coppa': false - }; - return config[key]; - }); - const requestsWithFalseCoppa = qcSpec.buildRequests([bidRequest], bidderRequest); - expect(JSON.parse(requestsWithFalseCoppa[0].data).coppa).to.equal(0); - }); - }); - - describe('`interpretResponse`', function () { - // The sample response is from https://wiki.corp.qc/display/adinf/QCX - const body = { - bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', // Added this field. This is not used now but could be useful in troubleshooting later on. Specially for sites using iFrames - bids: [ - { - statusCode: 1, - placementCode: 'imp1', // Changing this to placementCode to be reflective - cpm: 4.5, - currency: 'USD', - ad: - '
Quantcast
', - creativeId: 1001, - width: 300, - height: 250, - meta: { - advertiserDomains: ['dailymail.com'] - } - } - ] - }; - - const response = { - body, - headers: {} - }; - - const videoBody = { - bidderCode: 'qcx', - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', - bids: [ - { - statusCode: 1, - placementCode: 'video1', - cpm: 4.5, - currency: 'USD', - videoUrl: 'https://vast.quantserve.com/vast?p=&r=&gdpr=&gdpr_consent=&rand=1337&d=H4sIAAAAAAAAAONi4mIQcrzFqGLi5OzibOzmpGtm4eyia-LoaqDraGRupOtobGJhYuni6GRiYLmLiYWrp5f_BBPDDybGScxcPs7-aRYmpmVVoVJgCSXBkozMYl0gKslI1S1Izk9JBQALkFy_YAAAAA&h=uRnsTjyXbOrXJtBQiaMn239i9GI', - width: 600, - height: 300 - } - ] - }; - - const videoResponse = { - body: videoBody, - headers: {} - }; - - it('should return an empty array if `serverResponse` is `undefined`', function () { - const interpretedResponse = qcSpec.interpretResponse(); - - expect(interpretedResponse.length).to.equal(0); - }); - - it('should return an empty array if the parsed response does NOT include `bids`', function () { - const interpretedResponse = qcSpec.interpretResponse({}); - - expect(interpretedResponse.length).to.equal(0); - }); - - it('should return an empty array if the parsed response has an empty `bids`', function () { - const interpretedResponse = qcSpec.interpretResponse({ bids: [] }); - - expect(interpretedResponse.length).to.equal(0); - }); - - it('should get correct bid response', function () { - const expectedResponse = { - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', - cpm: 4.5, - width: 300, - height: 250, - ad: - '
Quantcast
', - ttl: QUANTCAST_TTL, - creativeId: 1001, - netRevenue: QUANTCAST_NET_REVENUE, - currency: 'USD', - meta: { - advertiserDomains: ['dailymail.com'] - } - }; - const interpretedResponse = qcSpec.interpretResponse(response); - - expect(interpretedResponse[0]).to.deep.equal(expectedResponse); - }); - - it('should include dealId in bid response', function () { - response.body.bids[0].dealId = 'test-dealid'; - const expectedResponse = { - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', - cpm: 4.5, - width: 300, - height: 250, - ad: - '
Quantcast
', - ttl: QUANTCAST_TTL, - creativeId: 1001, - netRevenue: QUANTCAST_NET_REVENUE, - currency: 'USD', - dealId: 'test-dealid', - meta: { - advertiserDomains: ['dailymail.com'] - } - }; - const interpretedResponse = qcSpec.interpretResponse(response); - - expect(interpretedResponse[0]).to.deep.equal(expectedResponse); - }); - - it('should get correct bid response for instream video', function() { - const expectedResponse = { - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', - cpm: 4.5, - width: 600, - height: 300, - vastUrl: 'https://vast.quantserve.com/vast?p=&r=&gdpr=&gdpr_consent=&rand=1337&d=H4sIAAAAAAAAAONi4mIQcrzFqGLi5OzibOzmpGtm4eyia-LoaqDraGRupOtobGJhYuni6GRiYLmLiYWrp5f_BBPDDybGScxcPs7-aRYmpmVVoVJgCSXBkozMYl0gKslI1S1Izk9JBQALkFy_YAAAAA&h=uRnsTjyXbOrXJtBQiaMn239i9GI', - mediaType: 'video', - ttl: QUANTCAST_TTL, - creativeId: undefined, - ad: undefined, - netRevenue: QUANTCAST_NET_REVENUE, - currency: 'USD' - }; - const interpretedResponse = qcSpec.interpretResponse(videoResponse); - - expect(interpretedResponse[0]).to.deep.equal(expectedResponse); - }); - - it('handles no bid response', function () { - const body = { - bidderCode: 'qcx', // Renaming it to use CamelCase since that is what is used in the Prebid.js variable name - requestId: 'erlangcluster@qa-rtb002.us-ec.adtech.com-11417780270886458', // Added this field. This is not used now but could be useful in troubleshooting later on. Specially for sites using iFrames - bids: [] - }; - const response = { - body, - headers: {} - }; - const interpretedResponse = qcSpec.interpretResponse(response); - - expect(interpretedResponse.length).to.equal(0); - }); - - it('should return pixel url when available userSync available', function () { - const syncOptions = { - pixelEnabled: true - }; - const serverResponses = [ - { - body: { - userSync: { - url: 'http://quantcast.com/pixelUrl' - } - } - }, - { - body: { - - } - } - ]; - - const actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); - const expectedSync = { - type: 'image', - url: 'http://quantcast.com/pixelUrl' - }; - expect(actualSyncs.length).to.equal(1); - expect(actualSyncs[0]).to.deep.equal(expectedSync); - qcSpec.resetUserSync(); - }); - - it('should not return user syncs if done already', function () { - const syncOptions = { - pixelEnabled: true - }; - const serverResponses = [ - { - body: { - userSync: { - url: 'http://quantcast.com/pixelUrl' - } - } - }, - { - body: { - - } - } - ]; - - let actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); - const expectedSync = { - type: 'image', - url: 'http://quantcast.com/pixelUrl' - }; - expect(actualSyncs.length).to.equal(1); - expect(actualSyncs[0]).to.deep.equal(expectedSync); - - actualSyncs = qcSpec.getUserSyncs(syncOptions, serverResponses); - expect(actualSyncs.length).to.equal(0); - - qcSpec.resetUserSync(); - }); - }); -}); diff --git a/test/spec/modules/quantcastIdSystem_spec.js b/test/spec/modules/quantcastIdSystem_spec.js deleted file mode 100644 index 24710e63388..00000000000 --- a/test/spec/modules/quantcastIdSystem_spec.js +++ /dev/null @@ -1,405 +0,0 @@ -import { quantcastIdSubmodule, storage, firePixel, hasCCPAConsent, hasGDPRConsent, checkTCFv2 } from 'modules/quantcastIdSystem.js'; -import * as utils from 'src/utils.js'; -import { coppaDataHandler } from 'src/adapterManager'; -import { attachIdSystem } from '../../../modules/userId/index.js'; -import { createEidsArray } from '../../../modules/userId/eids.js'; -import { expect } from 'chai/index.mjs'; - -describe('QuantcastId module', function () { - beforeEach(function() { - sinon.stub(coppaDataHandler, 'getCoppa'); - sinon.stub(utils, 'triggerPixel'); - sinon.stub(window, 'addEventListener'); - }); - - afterEach(function () { - utils.triggerPixel.restore(); - coppaDataHandler.getCoppa.restore(); - window.addEventListener.restore(); - }); - - it('getId() should return a quantcast id when the Quantcast first party cookie exists', function () { - sinon.stub(storage, 'getCookie').returns('P0-TestFPA'); - const id = quantcastIdSubmodule.getId(); - expect(id).to.be.deep.equal({ id: { quantcastId: 'P0-TestFPA' } }); - storage.getCookie.restore(); - }); - - it('getId() should return an empty id when the Quantcast first party cookie is missing', function () { - const id = quantcastIdSubmodule.getId(); - expect(id).to.be.deep.equal({ id: undefined }); - }); -}); - -describe('QuantcastId fire pixel', function () { - beforeEach(function () { - storage.setCookie('__qca', '', 'Thu, 01 Jan 1970 00:00:00 GMT'); - sinon.stub(storage, 'setCookie'); - sinon.stub(utils, 'triggerPixel'); - }); - - afterEach(function () { - utils.triggerPixel.restore(); - storage.setCookie.restore(); - }); - - it('fpa should be set when not present on this call', function () { - firePixel('clientId'); - var urlString = utils.triggerPixel.getCall(0).args[0]; - var parsedUrl = utils.parseUrl(urlString); - var urlSearchParams = parsedUrl.search; - assert.equal(urlSearchParams.fpan, '1'); - assert.notEqual(urlSearchParams.fpa, null); - }); - - it('fpa should be extracted from the Quantcast first party cookie when present on this call', function () { - sinon.stub(storage, 'getCookie').returns('P0-TestFPA'); - firePixel('clientId'); - var urlString = utils.triggerPixel.getCall(0).args[0]; - var parsedUrl = utils.parseUrl(urlString); - var urlSearchParams = parsedUrl.search; - assert.equal(urlSearchParams.fpan, '0'); - assert.equal(urlSearchParams.fpa, 'P0-TestFPA'); - storage.getCookie.restore(); - }); - - it('function to trigger pixel is called once', function () { - firePixel('clientId'); - expect(utils.triggerPixel.calledOnce).to.equal(true); - }); - - it('function to trigger pixel is not called when client id is absent', function () { - firePixel(); - expect(utils.triggerPixel.calledOnce).to.equal(false); - }); -}); - -describe('Quantcast CCPA consent check', function() { - it('returns true when CCPA constent string is not present', function() { - expect(hasCCPAConsent()).to.equal(true); - }); - - it("returns true when notice_given or do-not-sell in CCPA constent string is not 'Y' ", function() { - expect(hasCCPAConsent('1NNN')).to.equal(true); - expect(hasCCPAConsent('1YNN')).to.equal(true); - expect(hasCCPAConsent('1NYN')).to.equal(true); - }); - - it("returns false when CCPA consent string is present, and notice_given or do-not-sell in the string is 'Y' ", function() { - expect(hasCCPAConsent('1YYN')).to.equal(false); - }); -}); - -describe('Quantcast GDPR consent check', function() { - it("returns true when GDPR doesn't apply", function() { - expect(hasGDPRConsent({ gdprApplies: false })).to.equal(true); - }); - - it('returns false if denied consent, even if special purpose 1 treatment is true in DE', function() { - expect(checkTCFv2({ - gdprApplies: true, - publisherCC: 'DE', - purposeOneTreatment: true, - vendor: { - consents: { '11': false } - }, - purpose: { - consents: { '1': false } - }, - publisher: { - restrictions: { - '1': { - '11': 0 // flatly disallow Quantcast - } - } - } - }, ['1'])).to.equal(false); - }); - - it('returns false if publisher flatly denies required purpose', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true } - }, - purpose: { - consents: { '1': true } - }, - publisher: { - restrictions: { - '1': { - '11': 0 // flatly disallow Quantcast - } - } - } - }, ['1'])).to.equal(false); - }); - - it('returns true if positive consent for required purpose', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true } - }, - purpose: { - consents: { '1': true } - } - }, ['1'])).to.equal(true); - }); - - it('returns false if positive consent but publisher requires legitimate interest for required purpose', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true } - }, - purpose: { - consents: { '1': true } - }, - publisher: { - restrictions: { - '1': { - '11': 2 // require legitimate interest for Quantcast - } - } - } - }, ['1'])).to.equal(false); - }); - - it('returns false if no vendor consent and no legitimate interest', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': false } - }, - purpose: { - consents: { '1': true } - } - }, ['1'])).to.equal(false); - }); - - it('returns false if no purpose consent and no legitimate interest', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true } - }, - purpose: { - consents: { '1': false } - } - }, ['1'])).to.equal(false); - }); - - it('returns false if no consent, but legitimate interest for consent-first purpose, and no restrictions specified', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '1': false }, - legitimateInterests: { '1': true } - } - }, ['1'])).to.equal(false); - }); - - it('returns false if consent, but no legitimate interest for legitimate-interest-first purpose, and no restrictions specified', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '10': true }, - legitimateInterests: { '10': false } - } - }, ['10'])).to.equal(false); - }); - - it('returns true if consent, but no legitimate interest for legitimate-interest-first purpose, and corresponding consent restriction specified', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '10': true }, - legitimateInterests: { '10': false } - }, - publisher: { - restrictions: { - '10': { - '11': 1 // require consent for Quantcast - } - } - } - }, ['10'])).to.equal(true); - }); - - it('returns false if no consent but legitimate interest for required purpose other than 1, but publisher requires consent', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': false }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '10': false }, - legitimateInterests: { '10': true } - }, - publisher: { - restrictions: { - '10': { - '11': 1 // require consent for Quantcast - } - } - } - }, ['10'])).to.equal(false); - }); - - it('returns false if no consent and no legitimate interest for vendor for required purpose other than 1', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': false }, - legitimateInterests: { '11': false } - }, - purpose: { - consents: { '10': false }, - legitimateInterests: { '10': true } - } - }, ['10'])).to.equal(false); - }); - - it('returns false if no consent and no legitimate interest for required purpose other than 1', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': false }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '10': false }, - legitimateInterests: { '10': false } - } - }, ['10'])).to.equal(false); - }); - - it('returns false if no consent but legitimate interest for required purpose, but required purpose is purpose 1', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': false }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { '1': false }, - legitimateInterests: { '1': true } - } - }, ['1'])).to.equal(false); - }); - - it('returns true if different legal bases for multiple required purposes', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { - '1': true, - '10': false - }, - legitimateInterests: { - '1': false, - '10': true - } - }, - publisher: { - restrictions: { - '10': { - '11': 2 // require legitimate interest for Quantcast - } - } - } - })).to.equal(true); - }); - - it('returns true if full consent and legitimate interest for all required purposes with no restrictions specified', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { - '1': true, - '3': true, - '7': true, - '8': true, - '9': true, - '10': true - }, - legitimateInterests: { - '1': true, - '3': true, - '7': true, - '8': true, - '9': true, - '10': true - } - } - })).to.equal(true); - }); - - it('returns false if one of multiple required purposes has no legal basis', function() { - expect(checkTCFv2({ - gdprApplies: true, - vendor: { - consents: { '11': true }, - legitimateInterests: { '11': true } - }, - purpose: { - consents: { - '1': true, - '10': false - }, - legitimateInterests: { - '11': false, - '10': true - } - }, - publisher: { - restrictions: { - '10': { - '11': 1 // require consent for Quantcast - } - } - } - })).to.equal(false); - }); - describe('eids', () => { - before(() => { - attachIdSystem(quantcastIdSubmodule); - }); - it('quantcastId', function() { - const userId = { - quantcastId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'quantcast.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - }) -}); diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index 862b56f6e82..ecd21896034 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -1,6 +1,5 @@ import { spec } from '../../../modules/rhythmoneBidAdapter.js'; import * as utils from '../../../src/utils.js'; -import * as dnt from 'libraries/dnt/index.js'; import * as sinon from 'sinon'; var r1adapter = spec; @@ -414,37 +413,6 @@ describe('rhythmone adapter tests', function () { expect(openrtbRequest.imp[0].banner.format.length).to.equal(1); }); - it('dnt is correctly set to 1', function () { - var bidRequestList = [ - { - 'bidder': 'rhythmone', - 'params': { - 'placementId': 'myplacement', - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 600]] - } - }, - 'adUnitCode': 'div-gpt-ad-1438287399331-0', - 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', - 'bidderRequestId': '418b37f85e772c', - 'auctionId': '18fd8b8b0bd757', - 'bidRequestsCount': 1, - 'bidId': '51ef8751f9aead' - } - ]; - - var dntStub = sinon.stub(dnt, 'getDNT').returns(1); - - var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); - - dntStub.restore(); - - const openrtbRequest = JSON.parse(bidRequest.data); - expect(openrtbRequest.device.dnt).to.equal(1); - }); - it('sets floor to zero', function () { var bidRequestList = [ { diff --git a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js deleted file mode 100644 index 0318a6987c6..00000000000 --- a/test/spec/modules/ringieraxelspringerBidAdapter_spec.js +++ /dev/null @@ -1,10 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/ringieraxelspringerBidAdapter.js'; - -describe('ringieraxelspringer backward-compatibility shim', function () { - it('should re-export spec from dasBidAdapter', function () { - expect(spec).to.exist; - expect(spec.code).to.equal('das'); - expect(spec.aliases).to.include('ringieraxelspringer'); - }); -}); diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 65fc9d1a532..cc7a3309e63 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -4,23 +4,13 @@ import * as utils from 'src/utils.js'; import * as mockGpt from 'test/spec/integration/faker/googletag.js'; import { config } from '../../../src/config.js'; import { BIDFLOOR_CURRENCY } from '../../../modules/seedtagBidAdapter.js'; +import * as adUnits from 'src/utils/adUnits'; 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, @@ -60,12 +50,25 @@ const createBannerSlotConfig = (mediatypes) => { }; describe('Seedtag Adapter', function () { + let sandbox; beforeEach(function () { mockGpt.reset(); + sandbox = sinon.createSandbox(); + sandbox.stub(adUnits, 'getAdUnitElement').returns({ + getBoundingClientRect() { + return { + top: 10, + left: 20, + width: 300, + height: 250 + } + } + }); }); afterEach(function () { mockGpt.enable(); + sandbox.restore(); }); describe('isBidRequestValid method', function () { describe('returns true', function () { @@ -328,9 +331,13 @@ describe('Seedtag Adapter', function () { }); describe('BidRequests params', function () { - const request = spec.buildRequests(validBidRequests, bidderRequest); - const data = JSON.parse(request.data); - const bidRequests = data.bidRequests; + let request, data, bidRequests; + beforeEach(() => { + request = spec.buildRequests(validBidRequests, bidderRequest); + data = JSON.parse(request.data); + bidRequests = data.bidRequests; + }); + it('should request a Banner', function () { const bannerBid = bidRequests[0]; expect(bannerBid.id).to.equal('30b31c1838de1e'); @@ -369,27 +376,21 @@ describe('Seedtag Adapter', function () { 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') - } + 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') + }) }) it('should have bidfloor parameter if available', function () { diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index 56f238ff1ba..26e9e57da7c 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -963,22 +963,6 @@ describe('sharethrough adapter spec', function () { }); }); - 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.paapi = { enabled: 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('isEqtvTest', () => { it('should set publisher id if equativNetworkId param is present', () => { const builtRequest = spec.buildRequests(multiImpBidRequests, bidderRequest)[0]; @@ -1221,53 +1205,6 @@ describe('sharethrough adapter spec', function () { const resp = spec.interpretResponse(response, request)[0]; expect(resp.ttl).to.equal(360); }); - - it('should return correct properties when fledgeAuctionEnabled is true and isEqtvTest is false', () => { - request = spec.buildRequests(bidRequests, bidderRequest)[0]; - response = { - body: { - ext: { - auctionConfigs: { - key: 'value', - }, - }, - seatbid: [ - { - bid: [ - { - id: 'abcd1234', - impid: 'aaaabbbbccccdd', - w: 300, - h: 250, - price: 42, - crid: 'creative', - dealid: 'deal', - adomain: ['domain.com'], - adm: 'markup', - exp: -1, - }, - { - id: 'efgh5678', - impid: 'ddeeeeffffgggg', - w: 300, - h: 250, - price: 42, - crid: 'creative', - dealid: 'deal', - adomain: ['domain.com'], - adm: 'markup', - exp: -1, - }, - ], - }, - ], - }, - }; - - const resp = spec.interpretResponse(response, request); - expect(resp.bids.length).to.equal(2); - expect(resp.paapi).to.deep.equal({ key: 'value' }); - }); }); describe('video', () => { diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 603c6dee835..1db32e61bc2 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -679,6 +679,8 @@ describe('ShinezRtbBidAdapter', function () { }); describe('unique deal id', function () { + let clock; + before(function () { getGlobal().bidderSettings = { shinezRtb: { @@ -689,27 +691,31 @@ describe('ShinezRtbBidAdapter', function () { after(function () { getGlobal().bidderSettings = {}; }); - const key = 'myKey'; + let key; let uniqueDealId; beforeEach(() => { + clock = useFakeTimers({ + now: Date.now() + }); + key = `myKey_${Date.now()}`; uniqueDealId = getUniqueDealId(storage, key, 0); - }) + }); + + afterEach(() => { + clock.restore(); + }); + + it('should get current unique deal id', function () { + // advance time in a deterministic way + clock.tick(200); + const current = getUniqueDealId(storage, key); + expect(current).to.be.equal(uniqueDealId); + }); - it('should get current unique deal id', function (done) { - // waiting some time so `now` will become past - setTimeout(() => { - const current = getUniqueDealId(storage, key); - expect(current).to.be.equal(uniqueDealId); - done(); - }, 200); - }); - - it('should get new unique deal id on expiration', function (done) { - setTimeout(() => { - const current = getUniqueDealId(storage, key, 100); - expect(current).to.not.be.equal(uniqueDealId); - done(); - }, 200) + it('should get new unique deal id on expiration', function () { + clock.tick(200); + const current = getUniqueDealId(storage, key, 100); + expect(current).to.not.be.equal(uniqueDealId); }); }); diff --git a/test/spec/modules/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 50e48e69fd8..8edeabd466b 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -79,29 +79,11 @@ describe('smaatoBidAdapterTest', () => { expect(spec.isBidRequestValid({ params: { publisherId: 123 } })).to.be.false; }); - describe('for ad pod / long form video requests', () => { - const ADPOD = { video: { context: 'adpod' } } - it('is invalid, when adbreakId is missing', () => { - expect(spec.isBidRequestValid({ mediaTypes: ADPOD, params: { publisherId: '123' } })).to.be.false; - }); - - it('is invalid, when adbreakId is present but of wrong type', () => { - expect(spec.isBidRequestValid({ mediaTypes: ADPOD, params: { publisherId: '123', adbreakId: 456 } })).to.be.false; - }); - - it('is valid, when required params are present', () => { - expect(spec.isBidRequestValid({ mediaTypes: ADPOD, params: { publisherId: '123', adbreakId: '456' } })).to.be.true; - }); - - it('is invalid, when forbidden adspaceId param is present', () => { - expect(spec.isBidRequestValid({ - mediaTypes: ADPOD, - params: { publisherId: '123', adbreakId: '456', adspaceId: '42' } - })).to.be.false; - }); + it('is invalid, when adbreakId param is present', () => { + expect(spec.isBidRequestValid({ params: { publisherId: '123', adspaceId: '456', adbreakId: '42' } })).to.be.false; }); - describe('for non adpod requests', () => { + describe('for supported requests', () => { it('is invalid, when adspaceId is missing', () => { expect(spec.isBidRequestValid({ params: { publisherId: '123' } })).to.be.false; }); @@ -672,299 +654,6 @@ describe('smaatoBidAdapterTest', () => { 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); }); - - 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); - }); - - it('sends instl if instl exists', () => { - const instl = { instl: 1 }; - const bidRequestWithInstl = Object.assign({}, longFormVideoBidRequest, { ortb2Imp: instl }); - - 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 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 } }); - - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - 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 } }); - - const reqs = spec.buildRequests([longFormVideoBidRequest], defaultBidderRequest); - - 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); - - 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('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; - - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - - 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; - - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - - 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; - - 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); - }); - - 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 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; - - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - - 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'; - - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - - 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'; - - const reqs = spec.buildRequests([adpodRequestWithParameter], defaultBidderRequest); - - 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); - - const req = extractPayloadOfFirstAndOnlyRequest(reqs); - expect(req.imp[0].video.ext.requireExactDuration).to.not.exist; - expect(req.site.content).to.not.exist; - }); - }); - }); } }); @@ -1539,7 +1228,6 @@ describe('smaatoBidAdapterTest', () => { }); describe('ad pod', () => { - const bidRequestWithAdpodContext = buildBidRequest({ imp: [{ video: { ext: { context: 'adpod' } } }] }); const PRIMARY_CAT_ID = 1337 const serverResponse = { body: { @@ -1578,44 +1266,6 @@ describe('smaatoBidAdapterTest', () => { }, headers: { get: () => undefined } }; - - it('sets required values for adpod bid from server response', () => { - const bids = spec.interpretResponse(serverResponse, bidRequestWithAdpodContext); - - expect(bids).to.deep.equal([ - { - requestId: '226416e6e6bf41', - cpm: 0.01, - width: 350, - height: 50, - vastXml: '', - ttl: 300, - creativeId: 'CR69381', - dealId: '12345', - netRevenue: true, - currency: 'USD', - mediaType: 'video', - video: { - context: 'adpod', - durationSeconds: 42 - }, - meta: { - advertiserDomains: ['smaato.com'], - agencyId: 'CM6523', - networkName: 'smaato', - mediaType: 'video' - } - } - ]); - }); - - it('sets primary category id in case of enabled brand category exclusion', () => { - config.setConfig({ adpod: { brandCategoryExclusion: true } }); - - const bids = spec.interpretResponse(serverResponse, bidRequestWithAdpodContext) - - expect(bids[0].meta.primaryCatId).to.be.equal(PRIMARY_CAT_ID) - }) }); it('uses correct TTL when expire header exists', () => { diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 53261a3a734..4a893aaedb9 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -690,14 +690,14 @@ describe('SSPBC adapter', function () { expect(result.length).to.equal(bids.length); expect(resultSingle.length).to.equal(1); - expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); + expect(resultSingle[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls', 'eventtrackers'); }); it('should create bid from OneCode (parameter-less) request, if response contains siteId', function () { const resultOneCode = spec.interpretResponse(serverResponseOneCode, requestOneCode); expect(resultOneCode.length).to.equal(1); - expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls'); + expect(resultOneCode[0]).to.have.keys('ad', 'cpm', 'width', 'height', 'mediaType', 'meta', 'requestId', 'creativeId', 'currency', 'netRevenue', 'ttl', 'vurls', 'eventtrackers'); }); it('should not create bid from OneCode (parameter-less) request, if response does not contain siteId', function () { @@ -724,7 +724,7 @@ describe('SSPBC adapter', function () { expect(resultVideo.length).to.equal(1); const videoBid = resultVideo[0]; - expect(videoBid).to.have.keys('adType', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl', 'vurls'); + expect(videoBid).to.have.keys('adType', 'cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'vastContent', 'vastXml', 'vastUrl', 'vurls', 'eventtrackers'); expect(videoBid.adType).to.equal('instream'); expect(videoBid.mediaType).to.equal('video'); expect(videoBid.vastXml).to.match(/^<\?xml.*<\/VAST>$/); @@ -738,7 +738,7 @@ describe('SSPBC adapter', function () { expect(resultNative.length).to.equal(1); const nativeBid = resultNative[0]; - expect(nativeBid).to.have.keys('cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); + expect(nativeBid).to.have.keys('cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls', 'eventtrackers'); expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers', 'clickTrackers'); }); @@ -747,13 +747,6 @@ describe('SSPBC adapter', function () { expect(resultIncorrect.length).to.equal(0); }); - - it('should response with fledge auction configs', function () { - const { bids, fledgeAuctionConfigs } = spec.interpretResponse(serverResponsePaapi, requestSingle); - - expect(bids.length).to.equal(1); - expect(fledgeAuctionConfigs.length).to.equal(1); - }); }); describe('getUserSyncs', function () { diff --git a/test/spec/modules/storageControl_spec.js b/test/spec/modules/storageControl_spec.js index 2e6a146150e..ac1d86badc0 100644 --- a/test/spec/modules/storageControl_spec.js +++ b/test/spec/modules/storageControl_spec.js @@ -4,7 +4,8 @@ import { ENFORCE_OFF, ENFORCE_STRICT, getDisclosures, - storageControlRule + storageControlRule, + deactivate } from '../../../modules/storageControl.js'; import { ACTIVITY_PARAM_COMPONENT_NAME, @@ -14,6 +15,10 @@ import { import { MODULE_TYPE_BIDDER } from '../../../src/activities/modules.js'; import { STORAGE_TYPE_COOKIES } from '../../../src/storageManager.js'; +// since the module is on by default, importing it here turn it on for other tests +// that happen to run together with this suite - turn it off +deactivate(); + describe('storageControl', () => { describe('getDisclosures', () => { let metadata; @@ -208,6 +213,12 @@ describe('storageControl', () => { expect(rule()).to.eql({ allow: false, reason: 'denied' }); }); + it('should deny by default when enforcement is not set', () => { + enforcement = undefined; + checkResult = { disclosed: false, parent: false, reason: 'denied' }; + expect(rule()).to.eql({ allow: false, reason: 'denied' }); + }); + it('should allow when enforcement is allowAliases and disclosure is done by the aliased module', () => { enforcement = ENFORCE_ALIAS; checkResult = { disclosed: false, parent: true, reason: 'allowed' }; diff --git a/test/spec/modules/taboolaBidAdapter_spec.js b/test/spec/modules/taboolaBidAdapter_spec.js index 9931831483c..66e0de4780a 100644 --- a/test/spec/modules/taboolaBidAdapter_spec.js +++ b/test/spec/modules/taboolaBidAdapter_spec.js @@ -1239,181 +1239,6 @@ describe('Taboola Adapter', function () { expect(res[0].meta.dchain).to.deep.equal(expectedDchainRes) }); - 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 - }, - } - ], - 'paapi': [ - { - '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 - }, - } - ], - 'paapi': [ - { - 'impId': request.bids[0].bidId, - 'config': { - 'seller': undefined, - 'resolveToConfig': undefined, - 'sellerSignals': {}, - 'sellerTimeout': undefined, - 'perBuyerSignals': {}, - 'auctionSignals': {}, - 'decisionLogicUrl': undefined, - 'interestGroupBuyers': undefined, - 'perBuyerTimeouts': undefined - } - } - ] - } - - const res = spec.interpretResponse(serverResponseWithPartialPa, request) - expect(res).to.deep.equal(expectedRes) - }); - - it('should interpret display response with wrong PA', function () { - const [bid] = serverResponse.body.seatbid[0].bid; - - const expectedRes = [ - { - requestId: request.bids[0].bidId, - seatBidId: serverResponse.body.seatbid[0].bid[0].id, - cpm: bid.price, - creativeId: bid.crid, - creative_id: bid.crid, - ttl: 60, - netRevenue: true, - currency: serverResponse.body.cur, - mediaType: 'banner', - ad: bid.adm, - width: bid.w, - height: bid.h, - nurl: 'http://win.example.com/', - meta: { - 'advertiserDomains': bid.adomain - }, - } - ] - - const res = spec.interpretResponse(serverResponseWithWrongPa, request) - expect(res).to.deep.equal(expectedRes) - }); - - it('should interpret display response with empty igbid wrong PA', function () { - const [bid] = serverResponse.body.seatbid[0].bid; - - const expectedRes = [ - { - requestId: request.bids[0].bidId, - seatBidId: serverResponse.body.seatbid[0].bid[0].id, - cpm: bid.price, - creativeId: bid.crid, - creative_id: bid.crid, - ttl: 60, - netRevenue: true, - currency: serverResponse.body.cur, - mediaType: 'banner', - ad: bid.adm, - width: bid.w, - height: bid.h, - nurl: 'http://win.example.com/', - meta: { - 'advertiserDomains': bid.adomain - }, - } - ] - - const res = spec.interpretResponse(serverResponseWithEmptyIgbidWIthWrongPa, request) - expect(res).to.deep.equal(expectedRes) - }); - it('should set the correct ttl form the response', function () { // set exp-ttl to be 125 const [bid] = serverResponse.body.seatbid[0].bid; diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 6b024a0a78c..7db3660ec7e 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -889,7 +889,6 @@ describe('teadsBidAdapter', () => { id5Id: toEid('id5-sync.com', 'id5Id-id'), criteoId: toEid('criteo.com', 'criteoId-id'), yahooConnectId: toEid('yahoo.com', 'yahooConnectId-id'), - quantcastId: toEid('quantcast.com', 'quantcastId-id'), epsilonPublisherLinkId: toEid('epsilon.com', 'epsilonPublisherLinkId-id'), publisherFirstPartyViewerId: toEid('pubcid.org', 'publisherFirstPartyViewerId-id'), merkleId: toEid('merkleinc.com', 'merkleId-id'), @@ -957,7 +956,6 @@ describe('teadsBidAdapter', () => { expect(payload['id5Id']).to.equal('id5Id-id'); expect(payload['criteoId']).to.equal('criteoId-id'); expect(payload['yahooConnectId']).to.equal('yahooConnectId-id'); - expect(payload['quantcastId']).to.equal('quantcastId-id'); expect(payload['epsilonPublisherLinkId']).to.equal('epsilonPublisherLinkId-id'); expect(payload['publisherFirstPartyViewerId']).to.equal('publisherFirstPartyViewerId-id'); expect(payload['merkleId']).to.equal('merkleId-id'); diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js deleted file mode 100644 index f20a2aa25aa..00000000000 --- a/test/spec/modules/topLevelPaapi_spec.js +++ /dev/null @@ -1,515 +0,0 @@ -import { - addPaapiConfigHook, - getPAAPIConfig, - registerSubmodule, - reset as resetPaapi -} from '../../../modules/paapi.js'; -import { config } from 'src/config.js'; -import { BID_STATUS, EVENTS } from 'src/constants.js'; -import * as events from 'src/events.js'; -import { - getPaapiAdId, - getPAAPIBids, - getRenderingDataHook, markWinningBidHook, - parsePaapiAdId, - parsePaapiSize, resizeCreativeHook, - topLevelPAAPI -} from '../../../modules/topLevelPaapi.js'; -import { auctionManager } from '../../../src/auctionManager.js'; -import { expect } from 'chai/index.js'; -import { getBidToRender } from '../../../src/adRendering.js'; - -describe('topLevelPaapi', () => { - let sandbox, auctionConfig, next, auctionId, auctions; - before(() => { - resetPaapi(); - }); - beforeEach(() => { - registerSubmodule(topLevelPAAPI); - }); - afterEach(() => { - resetPaapi(); - }); - beforeEach(() => { - sandbox = sinon.createSandbox(); - auctions = {}; - sandbox.stub(auctionManager.index, 'getAuction').callsFake(({ auctionId }) => auctions[auctionId]?.auction); - next = sinon.stub(); - auctionId = 'auct'; - auctionConfig = { - seller: 'mock.seller' - }; - config.setConfig({ - paapi: { - enabled: true, - defaultForSlots: 1 - } - }); - }); - afterEach(() => { - config.resetConfig(); - sandbox.restore(); - }); - - function addPaapiConfig(adUnitCode, auctionConfig, _auctionId = auctionId) { - let auction = auctions[_auctionId]; - if (!auction) { - auction = auctions[_auctionId] = { - auction: {}, - adUnits: {} - }; - } - if (!auction.adUnits.hasOwnProperty(adUnitCode)) { - auction.adUnits[adUnitCode] = { - code: adUnitCode, - ortb2Imp: { - ext: { - paapi: { - requestedSize: { - width: 123, - height: 321 - } - } - } - } - }; - } - addPaapiConfigHook(next, { adUnitCode, auctionId: _auctionId }, { - config: { - ...auctionConfig, - auctionId: _auctionId, - adUnitCode - } - }); - } - - function endAuctions() { - Object.entries(auctions).forEach(([auctionId, { adUnits }]) => { - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: Object.keys(adUnits), adUnits: Object.values(adUnits) }); - }); - } - - describe('when configured', () => { - let auctionConfig; - beforeEach(() => { - auctionConfig = { - seller: 'top.seller', - decisionLogicURL: 'https://top.seller/decision-logic.js' - }; - config.mergeConfig({ - paapi: { - topLevelSeller: { - auctionConfig, - autorun: false - } - } - }); - }); - - it('should augment config returned by getPAAPIConfig', () => { - addPaapiConfig('au', auctionConfig); - endAuctions(); - sinon.assert.match(getPAAPIConfig().au, auctionConfig); - }); - - it('should not choke if auction config is not defined', () => { - const cfg = config.getConfig('paapi'); - delete cfg.topLevelSeller.auctionConfig; - config.setConfig(cfg); - addPaapiConfig('au', auctionConfig); - endAuctions(); - expect(getPAAPIConfig().au.componentAuctions).to.exist; - }); - - it('should default resolveToConfig: false', () => { - addPaapiConfig('au', auctionConfig); - endAuctions(); - expect(getPAAPIConfig()['au'].resolveToConfig).to.eql(false); - }); - - describe('when autoRun is set', () => { - let origRaa; - beforeEach(() => { - origRaa = navigator.runAdAuction; - navigator.runAdAuction = sinon.stub(); - }); - afterEach(() => { - navigator.runAdAuction = origRaa; - }); - - it('should start auctions automatically, when autoRun is set', () => { - config.mergeConfig({ - paapi: { - topLevelSeller: { - autorun: true - } - } - }) - addPaapiConfig('au', auctionConfig); - endAuctions(); - sinon.assert.called(navigator.runAdAuction); - }); - }); - - describe('getPAAPIBids', () => { - Object.entries({ - 'a string URN': { - pack: (val) => val, - unpack: (urn) => ({ urn }), - canRender: true, - }, - 'a frameConfig object': { - pack: (val) => ({ val }), - unpack: (val) => ({ frameConfig: { val } }), - canRender: false - } - }).forEach(([t, { pack, unpack, canRender }]) => { - describe(`when runAdAuction returns ${t}`, () => { - let raa; - beforeEach(() => { - raa = sinon.stub().callsFake((cfg) => { - const { auctionId, adUnitCode } = cfg.componentAuctions[0]; - return Promise.resolve(pack(`raa-${adUnitCode}-${auctionId}`)); - }); - }); - - function getBids(filters) { - return getPAAPIBids(filters, raa); - } - - function expectBids(actual, expected) { - expect(Object.keys(actual)).to.eql(Object.keys(expected)); - Object.entries(expected).forEach(([au, val]) => { - sinon.assert.match(actual[au], val == null ? val : { - adId: sinon.match(val => parsePaapiAdId(val)[1] === au), - width: 123, - height: 321, - source: 'paapi', - ...unpack(val) - }); - }); - } - - describe('with one auction config', () => { - beforeEach(() => { - addPaapiConfig('au', auctionConfig, 'auct'); - endAuctions(); - }); - it('should resolve to raa result', () => { - return getBids({ adUnitCode: 'au', auctionId }).then(result => { - sinon.assert.calledOnce(raa); - sinon.assert.calledWith( - raa, - sinon.match({ - ...auctionConfig, - componentAuctions: sinon.match([ - sinon.match({ - ...auctionConfig, - auctionId: 'auct', - adUnitCode: 'au' - }) - ]) - }) - ); - expectBids(result, { au: 'raa-au-auct' }); - }); - }); - - Object.entries({ - 'returns null': () => Promise.resolve(), - 'throws': () => { throw new Error() }, - 'rejects': () => Promise.reject(new Error()) - }).forEach(([t, behavior]) => { - it('should resolve to null when runAdAuction returns null', () => { - raa = sinon.stub().callsFake(behavior); - return getBids({ adUnitCode: 'au', auctionId: 'auct' }).then(result => { - expectBids(result, { au: null }); - }); - }); - }) - - it('should resolve to the same result when called again', () => { - getBids({ adUnitCode: 'au', auctionId }); - return getBids({ adUnitCode: 'au', auctionId: 'auct' }).then(result => { - sinon.assert.calledOnce(raa); - expectBids(result, { au: 'raa-au-auct' }); - }); - }); - - describe('events', () => { - beforeEach(() => { - sandbox.stub(events, 'emit'); - }); - it('should fire PAAPI_RUN_AUCTION', () => { - return Promise.all([ - getBids({ adUnitCode: 'au', auctionId }), - getBids({ adUnitCode: 'other', auctionId }) - ]).then(() => { - sinon.assert.calledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { - adUnitCode: 'au', - auctionId, - auctionConfig: sinon.match(auctionConfig) - }); - sinon.assert.neverCalledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { - adUnitCode: 'other' - }); - }); - }); - it('should fire PAAPI_BID', () => { - return getBids({ adUnitCode: 'au', auctionId }).then(() => { - sinon.assert.calledWith(events.emit, EVENTS.PAAPI_BID, sinon.match({ - ...unpack('raa-au-auct'), - adUnitCode: 'au', - auctionId: 'auct' - })); - }); - }); - it('should fire PAAPI_NO_BID', () => { - raa = sinon.stub().callsFake(() => Promise.resolve(null)); - return getBids({ adUnitCode: 'au', auctionId }).then(() => { - sinon.assert.calledWith(events.emit, EVENTS.PAAPI_NO_BID, sinon.match({ - adUnitCode: 'au', - auctionId: 'auct' - })); - }); - }); - - it('should fire PAAPI_ERROR', () => { - raa = sinon.stub().callsFake(() => Promise.reject(new Error('message'))); - return getBids({ adUnitCode: 'au', auctionId }).then(res => { - expect(res).to.eql({ au: null }); - sinon.assert.calledWith(events.emit, EVENTS.PAAPI_ERROR, sinon.match({ - adUnitCode: 'au', - auctionId: 'auct', - error: sinon.match({ message: 'message' }) - })); - }); - }); - }); - - function getBidToRenderPm(adId, forRender = true) { - return new Promise((resolve) => getBidToRender(adId, forRender, resolve)); - } - - it('should hook into getBidToRender', () => { - return getBids({ adUnitCode: 'au', auctionId }).then(res => { - return getBidToRenderPm(res.au.adId).then(bidToRender => [res.au, bidToRender]) - }).then(([paapiBid, bidToRender]) => { - if (canRender) { - expect(bidToRender).to.eql(paapiBid) - } else { - expect(bidToRender).to.not.exist; - } - }); - }); - - describe('when overrideWinner is set', () => { - let mockContextual; - beforeEach(() => { - mockContextual = { - adId: 'mock', - adUnitCode: 'au' - } - sandbox.stub(auctionManager, 'findBidByAdId').returns(mockContextual); - config.mergeConfig({ - paapi: { - topLevelSeller: { - overrideWinner: true - } - } - }); - }); - - it(`should ${!canRender ? 'NOT' : ''} override winning bid for the same adUnit`, () => { - return Promise.all([ - getBids({ adUnitCode: 'au', auctionId }).then(res => res.au), - getBidToRenderPm(mockContextual.adId) - ]).then(([paapiBid, bidToRender]) => { - if (canRender) { - expect(bidToRender).to.eql(paapiBid); - expect(paapiBid.overriddenAdId).to.eql(mockContextual.adId); - } else { - expect(bidToRender).to.eql(mockContextual) - } - }) - }); - - it('should not override when the ad unit has no paapi winner', () => { - mockContextual.adUnitCode = 'other'; - return getBidToRenderPm(mockContextual.adId).then(bidToRender => { - expect(bidToRender).to.eql(mockContextual); - }) - }); - - it('should not override when already a paapi bid', () => { - return getBids({ adUnitCode: 'au', auctionId }).then(res => { - return getBidToRenderPm(res.au.adId).then((bidToRender) => [bidToRender, res.au]); - }).then(([bidToRender, paapiBid]) => { - expect(bidToRender).to.eql(canRender ? paapiBid : mockContextual) - }) - }); - - if (canRender) { - it('should not not override when the bid was already rendered', () => { - getBids(); - return getBidToRenderPm(mockContextual.adId).then((bid) => { - // first pass - paapi wins over contextual - expect(bid.source).to.eql('paapi'); - bid.status = BID_STATUS.RENDERED; - return getBidToRenderPm(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) - }).then(([paapiBid, bidToRender]) => { - // if `forRender` = false (bit retrieved for x-domain events and such) - // the referenced bid is still paapi - expect(bidToRender).to.eql(paapiBid); - return getBidToRenderPm(mockContextual.adId); - }).then(bidToRender => { - // second pass, paapi has been rendered, contextual should win - expect(bidToRender).to.eql(mockContextual); - bidToRender.status = BID_STATUS.RENDERED; - return getBidToRenderPm(mockContextual.adId, false); - }).then(bidToRender => { - // if the contextual bid has been rendered, it's the one being referenced - expect(bidToRender).to.eql(mockContextual); - }); - }) - } - }); - }); - - it('should resolve the same result from different filters', () => { - const targets = { - auct1: ['au1', 'au2'], - auct2: ['au1', 'au3'] - }; - Object.entries(targets).forEach(([auctionId, adUnitCodes]) => { - adUnitCodes.forEach(au => addPaapiConfig(au, auctionConfig, auctionId)); - }); - endAuctions(); - return Promise.all( - [ - [ - { adUnitCode: 'au1', auctionId: 'auct1' }, - { - au1: 'raa-au1-auct1' - } - ], - [ - {}, - { - au1: 'raa-au1-auct2', - au2: 'raa-au2-auct1', - au3: 'raa-au3-auct2' - } - ], - [ - { auctionId: 'auct1' }, - { - au1: 'raa-au1-auct1', - au2: 'raa-au2-auct1' - } - ], - [ - { adUnitCode: 'au1' }, - { - au1: 'raa-au1-auct2' - } - ], - ].map(([filters, expected]) => getBids(filters).then(res => [res, expected])) - ).then(res => { - res.forEach(([actual, expected]) => { - expectBids(actual, expected); - }); - }); - }); - }); - }); - }); - }); - - describe('when not configured', () => { - it('should not alter configs returned by getPAAPIConfig', () => { - addPaapiConfig('au', auctionConfig); - endAuctions(); - expect(getPAAPIConfig().au.seller).to.not.exist; - }); - }); - - describe('paapi adId', () => { - [ - ['auctionId', 'adUnitCode'], - ['auction:id', 'adUnit:code'], - ['auction:uid', 'ad:unit'] - ].forEach(([auctionId, adUnitCode]) => { - it(`can encode and decode ${auctionId}, ${adUnitCode}`, () => { - expect(parsePaapiAdId(getPaapiAdId(auctionId, adUnitCode))).to.eql([auctionId, adUnitCode]); - }); - }); - - [undefined, null, 'not-a-paapi-ad', 'paapi:/malformed'].forEach(adId => { - it(`returns null for adId ${adId}`, () => { - expect(parsePaapiAdId(adId)).to.not.exist; - }); - }); - }); - - describe('parsePaapiSize', () => { - [ - [null, null], - [undefined, null], - [123, 123], - ['123', 123], - ['123px', 123], - ['1sw', null], - ['garbage', null] - ].forEach(([input, expected]) => { - it(`can parse ${input} => ${expected}`, () => { - expect(parsePaapiSize(input)).to.eql(expected); - }); - }); - }); - - describe('rendering hooks', () => { - let next; - beforeEach(() => { - next = sinon.stub() - next.bail = sinon.stub() - }); - describe('getRenderingDataHook', () => { - it('intercepts paapi bids', () => { - getRenderingDataHook(next, { - source: 'paapi', - width: 123, - height: null, - urn: 'url' - }); - sinon.assert.calledWith(next.bail, { - width: 123, - height: null, - adUrl: 'url' - }); - }); - it('does not touch non-paapi bids', () => { - getRenderingDataHook(next, { bid: 'data' }, { other: 'options' }); - sinon.assert.calledWith(next, { bid: 'data' }, { other: 'options' }); - }); - }); - - describe('markWinnigBidsHook', () => { - beforeEach(() => { - sandbox.stub(events, 'emit'); - }); - it('handles paapi bids', () => { - const bid = { source: 'paapi' }; - markWinningBidHook(next, bid); - sinon.assert.notCalled(next); - sinon.assert.called(next.bail); - sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); - }); - it('ignores non-paapi bids', () => { - markWinningBidHook(next, { other: 'bid' }); - sinon.assert.calledWith(next, { other: 'bid' }); - sinon.assert.notCalled(next.bail); - }); - }); - }); -}); diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 1b560937c99..e5897d077ab 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -12,7 +12,6 @@ import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; -import 'modules/paapi.js'; import { deepClone } from 'src/utils.js'; import { addFPDToBidderRequest } from '../../helpers/fpd.js'; @@ -849,38 +848,6 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); }); - context('do not track (DNT)', function() { - let doNotTrackStub; - - beforeEach(function () { - doNotTrackStub = sinon.stub(dnt, 'getDNT'); - }); - afterEach(function() { - doNotTrackStub.restore(); - }); - - it('when there is a do not track, should send a dnt', async function () { - doNotTrackStub.returns(1); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(1); - }); - - it('when there is not do not track, don\'t send dnt', async function () { - doNotTrackStub.returns(0); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(0); - }); - - it('when there is no defined do not track, don\'t send dnt', async function () { - doNotTrackStub.returns(null); - - const request = spec.buildRequests(bidRequestsWithMediaTypes, await addFPDToBidderRequest(mockBidderRequest)); - expect(request[0].data.device.dnt).to.equal(0); - }); - }); - context('supply chain (schain)', function () { let bidRequests; let schainConfig; diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 8b52932ae3e..e9f205b6745 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -892,12 +892,6 @@ describe('triplelift adapter', function () { const url = request.url; expect(url).to.match(/(\?|&)us_privacy=1YYY/); }); - it('should pass fledge signal when Triplelift is eligible for fledge', function() { - bidderRequest.paapi = { enabled: true }; - const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); - const url = request.url; - expect(url).to.match(/(\?|&)fledge=true/); - }); it('should return coppa param when COPPA config is set to true', function() { sinon.stub(config, 'getConfig').withArgs('coppa').returns(true); const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); @@ -1400,57 +1394,6 @@ describe('triplelift adapter', function () { expect(result[2].meta.networkId).to.equal('5989'); expect(result[3].meta.networkId).to.equal('5989'); }); - - it('should return fledgeAuctionConfigs if PAAPI response is received', function() { - response.body.paapi = [ - { - imp_id: '0', - auctionConfig: { - seller: 'https://3lift.com', - decisionLogicUrl: 'https://3lift.com/decision_logic.js', - interestGroupBuyers: ['https://some_buyer.com'], - perBuyerSignals: { - 'https://some_buyer.com': { a: 1 } - } - } - }, - { - imp_id: '2', - auctionConfig: { - seller: 'https://3lift.com', - decisionLogicUrl: 'https://3lift.com/decision_logic.js', - interestGroupBuyers: ['https://some_other_buyer.com'], - perBuyerSignals: { - 'https://some_other_buyer.com': { b: 2 } - } - } - } - ]; - - const result = tripleliftAdapterSpec.interpretResponse(response, { bidderRequest }); - - expect(result).to.have.property('bids'); - expect(result).to.have.property('paapi'); - expect(result.paapi.length).to.equal(2); - expect(result.paapi[0].bidId).to.equal('30b31c1838de1e'); - expect(result.paapi[1].bidId).to.equal('73edc0ba8de203'); - expect(result.paapi[0].config).to.deep.equal( - { - 'seller': 'https://3lift.com', - 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', - 'interestGroupBuyers': ['https://some_buyer.com'], - 'perBuyerSignals': { 'https://some_buyer.com': { 'a': 1 } } - } - ); - expect(result.paapi[1].config).to.deep.equal( - { - 'seller': 'https://3lift.com', - 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', - 'interestGroupBuyers': ['https://some_other_buyer.com'], - 'perBuyerSignals': { 'https://some_other_buyer.com': { 'b': 2 } } - } - ); - }); }); describe('getUserSyncs', function() { diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index e4c6c2777b4..97a27f79b65 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -641,15 +641,6 @@ describe('TwistDigitalBidAdapter', function () { expect(requests).to.have.length(2); }); - it('should set fledge correctly if enabled', function () { - config.resetConfig(); - const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.paapi = { enabled: true }; - deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); - const requests = adapter.buildRequests([BID], bidderRequest); - expect(requests[0].data.fledge).to.equal(1); - }); - after(function () { getGlobal().bidderSettings = {}; config.resetConfig(); diff --git a/test/spec/modules/undertoneBidAdapter_spec.js b/test/spec/modules/undertoneBidAdapter_spec.js index f4f85a110a1..d8326ab64c8 100644 --- a/test/spec/modules/undertoneBidAdapter_spec.js +++ b/test/spec/modules/undertoneBidAdapter_spec.js @@ -2,6 +2,8 @@ import { expect } from 'chai'; import { spec } from 'modules/undertoneBidAdapter.js'; import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; import { deepClone, getWinDimensions } from '../../../src/utils.js'; +import * as adUnits from 'src/utils/adUnits'; +import { getAdUnitElement } from 'src/utils/adUnits'; const URL = 'https://hb.undertone.com/hb'; const BIDDER_CODE = 'undertone'; @@ -314,7 +316,7 @@ describe('Undertone Adapter', () => { }; sandbox = sinon.createSandbox(); - sandbox.stub(document, 'getElementById').withArgs('div-gpt-ad-1460505748561-0').returns(element); + sandbox.stub(adUnits, 'getAdUnitElement').returns(element); }); afterEach(function() { diff --git a/test/spec/modules/valuadBidAdapter_spec.js b/test/spec/modules/valuadBidAdapter_spec.js index d2e05930619..4a27bbd32df 100644 --- a/test/spec/modules/valuadBidAdapter_spec.js +++ b/test/spec/modules/valuadBidAdapter_spec.js @@ -6,7 +6,6 @@ import { BANNER } from 'src/mediaTypes.js'; import { deepClone, generateUUID } from 'src/utils.js'; import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; -import * as dnt from 'libraries/dnt/index.js'; import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; import * as refererDetection from 'src/refererDetection.js'; import * as BoundingClientRect from 'libraries/boundingClientRect/boundingClientRect.js'; @@ -122,7 +121,6 @@ describe('ValuadAdapter', function () { }); sandbox.stub(utils, 'canAccessWindowTop').returns(true); - sandbox.stub(dnt, 'getDNT').returns(false); sandbox.stub(utils, 'generateUUID').returns('test-uuid'); sandbox.stub(refererDetection, 'parseDomain').returns('test.com'); diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 54d24f2f540..08b68058cd2 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -661,15 +661,6 @@ 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.paapi = { enabled: true }; - deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); - const requests = adapter.buildRequests([BID], bidderRequest); - expect(requests[0].data.fledge).to.equal(1); - }); - after(function () { getGlobal().bidderSettings = {}; config.resetConfig(); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 48305719b57..218bc364a0c 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -8,6 +8,7 @@ import { mergeDeep } from '../../../src/utils.js'; import { setConfig as setCurrencyConfig } from '../../../modules/currency.js'; import { addFPDToBidderRequest } from '../../helpers/fpd.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; +import * as adUnits from 'src/utils/adUnits'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -1071,13 +1072,9 @@ describe('VisxAdapter', function () { before(function() { sandbox = sinon.createSandbox(); - documentStub = sandbox.stub(document, 'getElementById'); - documentStub.withArgs('visx-adunit-code-1').returns({ - id: 'visx-adunit-code-1' - }); - documentStub.withArgs('visx-adunit-element-2').returns({ - id: 'visx-adunit-element-2' - }); + sandbox.stub(adUnits, 'getAdUnitElement').callsFake(({ adUnitCode }) => { + return ['visx-adunit-code-1', 'visx-adunit-code-2'].includes(adUnitCode); + }) getGlobal().bidderSettings = { visx: { diff --git a/test/spec/native_spec.js b/test/spec/native_spec.js index 629831c4e36..f134be9984b 100644 --- a/test/spec/native_spec.js +++ b/test/spec/native_spec.js @@ -22,6 +22,7 @@ import { auctionManager } from '../../src/auctionManager.js'; import { getRenderingData } from '../../src/adRendering.js'; import { getCreativeRendererSource, PUC_MIN_VERSION } from '../../src/creativeRenderers.js'; import { deepSetValue } from '../../src/utils.js'; +import { EVENT_TYPE_IMPRESSION, TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from 'src/eventTrackers.js'; const utils = require('src/utils'); const bid = { @@ -1126,7 +1127,7 @@ describe('fireImpressionTrackers', () => { }) function runTrackers(resp) { - fireImpressionTrackers(resp, { runMarkup, fetchURL }) + fireImpressionTrackers(resp, {}, { runMarkup, fetchURL }) } it('should run markup in jstracker', () => { @@ -1173,7 +1174,108 @@ describe('fireImpressionTrackers', () => { }); sinon.assert.notCalled(fetchURL); sinon.assert.notCalled(runMarkup); - }) + }); + + describe('when bidResponse mediaTypes.native.ortb.eventtrackers filters allowed trackers', () => { + let indexStub; + let getMediaTypesStub; + + beforeEach(() => { + getMediaTypesStub = sinon.stub(); + indexStub = sinon.stub(auctionManager, 'index').get(() => ({ getMediaTypes: getMediaTypesStub })); + }); + + afterEach(() => { + indexStub.restore(); + }); + + it('should fire only impression+IMG eventtrackers when request allows only IMG for impression', () => { + getMediaTypesStub.returns({ + native: { + ortb: { + eventtrackers: [{ event: EVENT_TYPE_IMPRESSION, methods: [TRACKER_METHOD_IMG] }] + } + } + }); + const bidResponse = { adUnitId: 'au', requestId: 'req' }; + fireImpressionTrackers({ + eventtrackers: [ + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'img-url' }, + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'js-url' } + ] + }, bidResponse, { runMarkup, fetchURL }); + sinon.assert.calledOnceWithExactly(fetchURL, 'img-url'); + sinon.assert.notCalled(runMarkup); + }); + + it('should fire only impression+JS eventtrackers when request allows only JS for impression', () => { + getMediaTypesStub.returns({ + native: { + ortb: { + eventtrackers: [{ event: EVENT_TYPE_IMPRESSION, methods: [TRACKER_METHOD_JS] }] + } + } + }); + const bidResponse = { adUnitId: 'au', requestId: 'req' }; + fireImpressionTrackers({ + eventtrackers: [ + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'img-url' }, + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'js-url' } + ] + }, bidResponse, { runMarkup, fetchURL }); + sinon.assert.notCalled(fetchURL); + sinon.assert.calledWith(runMarkup, sinon.match('script async src="js-url"')); + }); + + it('should not fire any eventtrackers when request eventtrackers do not include impression', () => { + getMediaTypesStub.returns({ + native: { + ortb: { + eventtrackers: [{ event: 2, methods: [TRACKER_METHOD_IMG, TRACKER_METHOD_JS] }] + } + } + }); + const bidResponse = { adUnitId: 'au', requestId: 'req' }; + fireImpressionTrackers({ + eventtrackers: [ + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'imp-img-url' } + ] + }, bidResponse, { runMarkup, fetchURL }); + sinon.assert.notCalled(fetchURL); + sinon.assert.notCalled(runMarkup); + }); + + it('should still fire legacy imptrackers and jstracker when eventtrackers are filtered out', () => { + getMediaTypesStub.returns({ + native: { + ortb: { + eventtrackers: [] + } + } + }); + const bidResponse = { adUnitId: 'au', requestId: 'req' }; + fireImpressionTrackers({ + eventtrackers: [{ event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'from-eventtrackers' }], + imptrackers: ['legacy-imp-url'], + jstracker: 'legacy-js-markup' + }, bidResponse, { runMarkup, fetchURL }); + sinon.assert.calledOnceWithExactly(fetchURL, 'legacy-imp-url'); + sinon.assert.calledWith(runMarkup, 'legacy-js-markup'); + }); + + it('should use default allowed trackers when getMediaTypes returns empty', () => { + getMediaTypesStub.returns({}); + const bidResponse = { adUnitId: 'au', requestId: 'req' }; + fireImpressionTrackers({ + eventtrackers: [ + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_IMG, url: 'default-img' }, + { event: EVENT_TYPE_IMPRESSION, method: TRACKER_METHOD_JS, url: 'default-js' } + ] + }, bidResponse, { runMarkup, fetchURL }); + sinon.assert.calledWith(fetchURL, 'default-img'); + sinon.assert.calledWith(runMarkup, sinon.match('script async src="default-js"')); + }); + }); }) describe('fireClickTrackers', () => { diff --git a/test/spec/unit/adRendering_spec.js b/test/spec/unit/adRendering_spec.js index 2943f27283b..3a04342c800 100644 --- a/test/spec/unit/adRendering_spec.js +++ b/test/spec/unit/adRendering_spec.js @@ -35,27 +35,6 @@ describe('adRendering', () => { sandbox.restore(); }) - describe('getBidToRender', () => { - beforeEach(() => { - sandbox.stub(auctionManager, 'findBidByAdId').callsFake(() => 'auction-bid') - }); - it('should default to bid from auctionManager', async () => { - await new Promise((resolve) => { - getBidToRender('adId', true, (res) => { - expect(res).to.eql('auction-bid'); - sinon.assert.calledWith(auctionManager.findBidByAdId, 'adId'); - resolve(); - }) - }) - }); - it('should, by default, not give up the thread', () => { - let ran = false; - getBidToRender('adId', true, () => { - ran = true; - }); - expect(ran).to.be.true; - }) - }) describe('getRenderingData', () => { let bidResponse; beforeEach(() => { diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index f159b8a0870..4c7757e1bb5 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -32,6 +32,7 @@ import { TRACKER_METHOD_IMG, TRACKER_METHOD_JS } from '../../../../src/eventTrackers.js'; +import 'src/prebid.js'; var events = require('../../../../src/events.js'); const CONFIG = { @@ -1816,6 +1817,18 @@ describe('adapterManager tests', function () { expect(sizes1).not.to.deep.equal(sizes2); }); + it('should transfer element from ad unit', () => { + adUnits[0].element = 'test'; + const requests = makeBidRequests(); + requests.flatMap(req => req.bids).forEach(bidRequest => { + if (bidRequest.adUnitCode === adUnits[0].code) { + expect(bidRequest.element).to.equal('test'); + } else { + expect(bidRequest.element).to.not.exist; + } + }); + }) + it('should transfer deferBilling from ad unit', () => { adUnits[0].deferBilling = true; const requests = makeBidRequests(); diff --git a/test/spec/unit/core/ajax_spec.js b/test/spec/unit/core/ajax_spec.js index d2e891f0d78..476b0de005f 100644 --- a/test/spec/unit/core/ajax_spec.js +++ b/test/spec/unit/core/ajax_spec.js @@ -186,7 +186,7 @@ describe('toFetchRequest', () => { }); describe('chrome options', () => { - ['browsingTopics', 'adAuctionHeaders'].forEach(option => { + ['browsingTopics'].forEach(option => { Object.entries({ [`${option} = true`]: [{ [option]: true }, true], [`${option} = false`]: [{ [option]: false }, false], diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index c2974906451..383d7441a62 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -1,4 +1,4 @@ -import { addPaapiConfig, addIGBuyer, isValid, newBidder, registerBidder } from 'src/adapters/bidderFactory.js'; +import { isValid, newBidder, registerBidder } from 'src/adapters/bidderFactory.js'; import adapterManager from 'src/adapterManager.js'; import * as ajax from 'src/ajax.js'; import { expect } from 'chai'; @@ -1640,59 +1640,6 @@ describe('bidderFactory', () => { bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bid)); }) - - describe('when response has PAAPI config', function() { - let paapiStub; - - function paapiHook(next, ...args) { - paapiStub(...args); - } - - function runBidder(response) { - const bidder = newBidder(spec); - spec.interpretResponse.returns(response); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); - } - - before(() => { - addPaapiConfig.before(paapiHook); - }); - - after(() => { - addPaapiConfig.getHooks({ hook: paapiHook }).remove(); - }) - - beforeEach(function () { - paapiStub = sinon.stub(); - }); - - describe(`when response has paapi`, () => { - it('should call paapi config hook with auction configs', function () { - runBidder({ - bids: bids, - paapi: [paapiConfig] - }); - expect(paapiStub.calledOnce).to.equal(true); - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - sinon.assert.calledWith(addBidResponseStub, 'mock/placement', sinon.match(bids[0])); - }); - - Object.entries({ - 'missing': undefined, - 'an empty array': [] - }).forEach(([t, bids]) => { - it(`should call paapi config hook with PAAPI configs even when bids is ${t}`, function () { - runBidder({ - bids, - paapi: [paapiConfig] - }); - expect(paapiStub.calledOnce).to.be.true; - sinon.assert.calledWith(paapiStub, bidRequest.bids[0], paapiConfig); - expect(addBidResponseStub.calledOnce).to.equal(false); - }); - }); - }); - }); }); }); @@ -1744,6 +1691,114 @@ describe('bidderFactory', () => { }); }); }) + + describe('media type validation', () => { + let req; + + function mkResponse(props) { + return Object.assign({ + requestId: req.bidId, + cpm: 1, + ttl: 60, + creativeId: '123', + netRevenue: true, + currency: 'USD', + width: 1, + height: 2, + mediaType: 'banner', + }, props); + } + + function checkValid(bid, opts = {}) { + return isValid('au', bid, { + index: stubAuctionIndex({ bidRequests: [req] }), + ...opts, + }); + } + + beforeEach(() => { + req = { + ...MOCK_BIDS_REQUEST.bids[0], + mediaTypes: { + banner: { + sizes: [[1, 2]] + } + } + }; + }); + + it('should reject video bid when ad unit only has banner', () => { + expect(checkValid(mkResponse({ mediaType: 'video' }))).to.be.false; + }); + + it('should accept video bid when ad unit has both banner and video', () => { + req.mediaTypes = { + banner: { sizes: [[1, 2]] }, + video: { context: 'instream' } + }; + expect(checkValid(mkResponse({ mediaType: 'video', vastUrl: 'http://vast.xml' }))).to.be.true; + }); + + it('should skip media type check when adapter omits mediaType', () => { + req.mediaTypes = { + video: { context: 'instream' } + }; + + expect(checkValid(mkResponse({ mediaType: 'banner' }), { responseMediaType: null })).to.be.true; + }); + + it('should reject unknown media type when configured and adapter omits mediaType', () => { + req.mediaTypes = { + video: { context: 'instream' } + }; + config.setConfig({ + auctionOptions: { + rejectUnknownMediaTypes: true + } + }); + + expect(checkValid(mkResponse({ mediaType: 'banner' }), { responseMediaType: null })).to.be.false; + }); + + it('should keep legacy behavior when rejectUnknownMediaTypes is disabled', () => { + req.mediaTypes = { + video: { context: 'instream' } + }; + config.setConfig({ + auctionOptions: { + rejectUnknownMediaTypes: false + } + }); + + expect(checkValid(mkResponse({ mediaType: 'banner' }), { responseMediaType: null })).to.be.true; + }); + + it('should allow mismatched media type when rejectInvalidMediaTypes is disabled', () => { + req.mediaTypes = { + banner: { sizes: [[1, 2]] } + }; + config.setConfig({ + auctionOptions: { + rejectInvalidMediaTypes: false + } + }); + + expect(checkValid(mkResponse({ mediaType: 'video' }))).to.be.true; + }); + + it('should reject mismatched media type when rejectInvalidMediaTypes is enabled', () => { + req.mediaTypes = { + banner: { sizes: [[1, 2]] } + }; + config.setConfig({ + auctionOptions: { + rejectInvalidMediaTypes: true + } + }); + + expect(checkValid(mkResponse({ mediaType: 'video' }))).to.be.false; + }); + }); }); describe('gzip compression', () => { diff --git a/test/spec/unit/core/targeting_spec.js b/test/spec/unit/core/targeting_spec.js index 28937d70f8b..473a66efab7 100644 --- a/test/spec/unit/core/targeting_spec.js +++ b/test/spec/unit/core/targeting_spec.js @@ -1728,22 +1728,22 @@ describe('targeting tests', function () { it('can find slots by ad unit path', () => { const paths = ['slot/1', 'slot/2'] - expect(getGPTSlotsForAdUnits(paths, null, () => slots)).to.eql({ [paths[0]]: [slots[0], slots[2]], [paths[1]]: [slots[1]] }); + expect(getGPTSlotsForAdUnits(paths, () => slots)).to.eql({ [paths[0]]: [slots[0], slots[2]], [paths[1]]: [slots[1]] }); }) it('can find slots by ad element ID', () => { const elementIds = ['div-1', 'div-2'] - expect(getGPTSlotsForAdUnits(elementIds, null, () => slots)).to.eql({ [elementIds[0]]: [slots[0]], [elementIds[1]]: [slots[1]] }); + expect(getGPTSlotsForAdUnits(elementIds, () => slots)).to.eql({ [elementIds[0]]: [slots[0]], [elementIds[1]]: [slots[1]] }); }) it('returns empty list on no match', () => { - expect(getGPTSlotsForAdUnits(['missing', 'slot/2'], null, () => slots)).to.eql({ + expect(getGPTSlotsForAdUnits(['missing', 'slot/2'], () => slots)).to.eql({ missing: [], 'slot/2': [slots[1]] }); }); - it('can use customSlotMatching resolving to ad unit codes', () => { + it('can use customGptSlotMatching resolving to ad unit codes', () => { const csm = (slot) => { if (slot.getAdUnitPath() === 'slot/1') { return (au) => { @@ -1751,13 +1751,17 @@ describe('targeting tests', function () { } } } - expect(getGPTSlotsForAdUnits(['div-2', 'custom'], csm, () => slots)).to.eql({ + config.setConfig({ + customGptSlotMatching: csm + }) + expect(getGPTSlotsForAdUnits(['div-2', 'custom'], () => slots)).to.eql({ 'custom': [slots[0], slots[2]], 'div-2': [slots[1]] }) + config.resetConfig(); }); - it('can use customSlotMatching resolving to elementIds', () => { + it('can use customGptSlotMatching resolving to elementIds', () => { const csm = (slot) => { if (slot.getSlotElementId() === 'div-1') { return (au) => { @@ -1765,14 +1769,18 @@ describe('targeting tests', function () { } } } - expect(getGPTSlotsForAdUnits(['div-2', 'custom'], csm, () => slots)).to.eql({ + config.setConfig({ + customGptSlotMatching: csm + }) + expect(getGPTSlotsForAdUnits(['div-2', 'custom'], () => slots)).to.eql({ 'custom': [slots[0]], 'div-2': [slots[1]] }) + config.resetConfig(); }); it('can handle repeated adUnitCodes', () => { - expect(getGPTSlotsForAdUnits(['div-1', 'div-1'], null, () => slots)).to.eql({ + expect(getGPTSlotsForAdUnits(['div-1', 'div-1'], () => slots)).to.eql({ 'div-1': [slots[0]] }) }) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index e0fc7ab0da2..bbc4d3cc424 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -26,7 +26,6 @@ import { mockFpdEnrichments } from '../../helpers/fpd.js'; import { deepAccess, deepSetValue, generateUUID } from '../../../src/utils.js'; import { getCreativeRenderer } from '../../../src/creativeRenderers.js'; import { BID_STATUS, EVENTS, GRANULARITY_OPTIONS, PB_LOCATOR, TARGETING_KEYS } from 'src/constants.js'; -import { getBidToRender } from '../../../src/adRendering.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; var assert = require('chai').assert; @@ -82,6 +81,24 @@ var Slot = function Slot(elementId, pathId) { return Object.getOwnPropertyNames(this.targeting); }, + getConfig: function getConfig(key) { + if (key === 'targeting') { + return this.targeting; + } + }, + + setConfig: function setConfig(config) { + if (config?.targeting) { + Object.keys(config.targeting).forEach((key) => { + if (config.targeting[key] == null) { + delete this.targeting[key]; + } else { + this.setTargeting(key, config.targeting[key]); + } + }); + } + }, + clearTargeting: function clearTargeting() { this.targeting = {}; return this; @@ -118,6 +135,24 @@ var createSlotArrayScenario2 = function createSlotArrayScenario2() { window.googletag = { _slots: [], _targeting: {}, + getConfig: function (key) { + if (key === 'targeting') { + return this._targeting; + } + }, + setConfig: function (config) { + if (config?.targeting) { + Object.keys(config.targeting).forEach((key) => { + if (config.targeting[key] == null) { + delete this._targeting[key]; + } else { + this._targeting[key] = Array.isArray(config.targeting[key]) + ? config.targeting[key] + : [config.targeting[key]]; + } + }); + } + }, pubads: function () { var self = this; return { @@ -1066,11 +1101,18 @@ describe('Unit: Prebid Module', function () { }); }); - it('should set googletag targeting keys to specific slot with customSlotMatching', function () { + it('should set googletag targeting keys to specific slot with customGptSlotMatching', function () { // same ad unit code but two differnt divs - // we make sure we can set targeting for a specific one with customSlotMatching + // we make sure we can set targeting for a specific one with customGptSlotMatching - pbjs.setConfig({ enableSendAllBids: false }); + pbjs.setConfig({ + enableSendAllBids: false, + customGptSlotMatching: (slot) => { + return (adUnitCode) => { + return slots[0].getSlotElementId() === slot.getSlotElementId(); + }; + } + }); var slots = createSlotArrayScenario2(); @@ -1078,11 +1120,7 @@ describe('Unit: Prebid Module', function () { slots[1].spySetTargeting.resetHistory(); window.googletag.pubads().setSlots(slots); pbjs.setConfig({ targetingControls: { allBidsCustomTargeting: true } }); - pbjs.setTargetingForGPTAsync([config.adUnitCodes[0]], (slot) => { - return (adUnitCode) => { - return slots[0].getSlotElementId() === slot.getSlotElementId(); - }; - }); + pbjs.setTargetingForGPTAsync([config.adUnitCodes[0]]); var expected = getTargetingKeys(); expect(slots[0].spySetTargeting.args).to.deep.contain.members(expected); @@ -3240,14 +3278,6 @@ describe('Unit: Prebid Module', function () { assert.ok(spyEventsOn.calledWith('bidWon', Function)); events.on.restore(); }); - - it('should emit event BID_ACCEPTED when invoked', function () { - var callback = sinon.spy(); - pbjs.onEvent('bidAccepted', callback); - events.emit(EVENTS.BID_ACCEPTED); - sinon.assert.calledOnce(callback); - }); - describe('beforeRequestBids', function () { let bidRequestedHandler; let beforeRequestBidsHandler; diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 085b5734e5a..d451424772d 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -10,28 +10,17 @@ import { config as configObj } from 'src/config.js'; import * as creativeRenderers from 'src/creativeRenderers.js'; import 'src/prebid.js'; import 'modules/nativeRendering.js'; +import * as adUnits from 'src/utils/adUnits'; import { expect } from 'chai'; import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; -import { getBidToRender } from '../../../src/adRendering.js'; import { PUC_MIN_VERSION } from 'src/creativeRenderers.js'; import { getGlobal } from '../../../src/prebidGlobal.js'; describe('secureCreatives', () => { let sandbox; - function getBidToRenderHook(next, ...args) { - // make sure that bids can be retrieved asynchronously - setTimeout(() => next(...args)) - } - before(() => { - getBidToRender.before(getBidToRenderHook); - }); - after(() => { - getBidToRender.getHooks({ hook: getBidToRenderHook }).remove() - }); - beforeEach(() => { sandbox = sinon.createSandbox(); }); @@ -552,6 +541,7 @@ describe('secureCreatives', () => { value = Array.isArray(value) ? value : [value]; targeting[key] = value; }), + getConfig: sinon.stub().callsFake((key) => key === 'targeting' ? targeting : null), getTargetingKeys: sinon.stub().callsFake(() => Object.keys(targeting)), getTargeting: sinon.stub().callsFake((key) => targeting[key] || []) } diff --git a/test/spec/utils/adUnits_spec.js b/test/spec/utils/adUnits_spec.js new file mode 100644 index 00000000000..f2652baf3a0 --- /dev/null +++ b/test/spec/utils/adUnits_spec.js @@ -0,0 +1,40 @@ +import { getAdUnitElement } from '../../../src/utils/adUnits.js'; + +describe('ad unit utils', () => { + let sandbox; + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + afterEach(() => { + sandbox.restore(); + }); + describe('getAdUnitElement', () => { + beforeEach(() => { + sandbox.stub(document, 'getElementById').callsFake((id) => ({ id })); + }); + it('should return null on invalid input', () => { + expect(getAdUnitElement({})).to.eql(null); + }); + it('should prefer element', () => { + expect(getAdUnitElement({ + element: 'explicit', + code: 'ignored', + adUnitCode: 'ignored' + })).to.eql('explicit'); + }); + it('should fallback to code as id', () => { + expect(getAdUnitElement({ + code: 'au' + })).to.eql({ + id: 'au' + }); + }); + it('should fallback to adUnitCode as id', () => { + expect(getAdUnitElement({ + adUnitCode: 'au' + })).to.eql({ + id: 'au' + }) + }) + }); +}); diff --git a/test/test_deps.js b/test/test_deps.js index 2c16b6e9587..f75279193fc 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -56,6 +56,5 @@ require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); require('test/mocks/analyticsStub.js'); require('test/mocks/ortbConverter.js') -require('modules/categoryTranslation.js'); require('modules/rtdModule/index.js'); require('modules/fpdModule/index.js'); From 287ef2cfee822c431acdc28ff13b83ab41f2c310 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Mar 2026 23:09:34 +0000 Subject: [PATCH 27/50] Prebid 11.0.0 release --- metadata/modules.json | 29 +---------- metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/aceexBidAdapter.json | 2 +- metadata/modules/acuityadsBidAdapter.json | 2 +- metadata/modules/adagioBidAdapter.json | 2 +- metadata/modules/adagioRtdProvider.json | 2 +- metadata/modules/adbroBidAdapter.json | 2 +- metadata/modules/addefendBidAdapter.json | 2 +- metadata/modules/adfBidAdapter.json | 2 +- metadata/modules/adfusionBidAdapter.json | 2 +- metadata/modules/adheseBidAdapter.json | 2 +- metadata/modules/adipoloBidAdapter.json | 2 +- metadata/modules/adkernelAdnBidAdapter.json | 2 +- metadata/modules/adkernelBidAdapter.json | 10 ++-- metadata/modules/admaticBidAdapter.json | 4 +- metadata/modules/admixerBidAdapter.json | 2 +- metadata/modules/admixerIdSystem.json | 2 +- metadata/modules/adnowBidAdapter.json | 2 +- metadata/modules/adnuntiusBidAdapter.json | 2 +- metadata/modules/adnuntiusRtdProvider.json | 2 +- metadata/modules/adoceanBidAdapter.json | 2 +- metadata/modules/adotBidAdapter.json | 2 +- metadata/modules/adponeBidAdapter.json | 2 +- metadata/modules/adqueryBidAdapter.json | 2 +- metadata/modules/adqueryIdSystem.json | 2 +- metadata/modules/adrinoBidAdapter.json | 2 +- .../modules/ads_interactiveBidAdapter.json | 2 +- metadata/modules/adtargetBidAdapter.json | 2 +- metadata/modules/adtelligentBidAdapter.json | 4 +- metadata/modules/adtelligentIdSystem.json | 2 +- metadata/modules/aduptechBidAdapter.json | 2 +- metadata/modules/adyoulikeBidAdapter.json | 2 +- metadata/modules/airgridRtdProvider.json | 2 +- metadata/modules/alkimiBidAdapter.json | 2 +- metadata/modules/allegroBidAdapter.json | 2 +- metadata/modules/amxBidAdapter.json | 2 +- metadata/modules/amxIdSystem.json | 2 +- metadata/modules/aniviewBidAdapter.json | 2 +- metadata/modules/anonymisedRtdProvider.json | 2 +- metadata/modules/apesterBidAdapter.json | 2 +- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 8 +-- metadata/modules/appushBidAdapter.json | 2 +- metadata/modules/apsBidAdapter.json | 2 +- metadata/modules/apstreamBidAdapter.json | 2 +- metadata/modules/audiencerunBidAdapter.json | 2 +- metadata/modules/axisBidAdapter.json | 2 +- metadata/modules/azerionedgeRtdProvider.json | 2 +- metadata/modules/beachfrontBidAdapter.json | 2 +- metadata/modules/beopBidAdapter.json | 2 +- metadata/modules/betweenBidAdapter.json | 2 +- metadata/modules/bidfuseBidAdapter.json | 2 +- metadata/modules/bidmaticBidAdapter.json | 2 +- metadata/modules/bidtheatreBidAdapter.json | 2 +- metadata/modules/bliinkBidAdapter.json | 2 +- metadata/modules/blockthroughBidAdapter.json | 2 +- metadata/modules/blueBidAdapter.json | 2 +- metadata/modules/bmsBidAdapter.json | 2 +- metadata/modules/boldwinBidAdapter.json | 2 +- metadata/modules/bridBidAdapter.json | 2 +- metadata/modules/browsiBidAdapter.json | 2 +- metadata/modules/bucksenseBidAdapter.json | 2 +- metadata/modules/carodaBidAdapter.json | 2 +- metadata/modules/categoryTranslation.json | 16 ++++++ metadata/modules/ceeIdSystem.json | 2 +- metadata/modules/chromeAiRtdProvider.json | 2 +- metadata/modules/clickioBidAdapter.json | 2 +- metadata/modules/compassBidAdapter.json | 2 +- metadata/modules/conceptxBidAdapter.json | 2 +- metadata/modules/connatixBidAdapter.json | 2 +- metadata/modules/connectIdSystem.json | 2 +- metadata/modules/connectadBidAdapter.json | 2 +- .../modules/contentexchangeBidAdapter.json | 2 +- metadata/modules/conversantBidAdapter.json | 2 +- metadata/modules/copper6sspBidAdapter.json | 2 +- metadata/modules/cpmstarBidAdapter.json | 2 +- metadata/modules/criteoBidAdapter.json | 2 +- metadata/modules/criteoIdSystem.json | 2 +- metadata/modules/cwireBidAdapter.json | 2 +- metadata/modules/czechAdIdSystem.json | 2 +- metadata/modules/dailymotionBidAdapter.json | 2 +- metadata/modules/debugging.json | 2 +- metadata/modules/deepintentBidAdapter.json | 2 +- metadata/modules/defineMediaBidAdapter.json | 2 +- metadata/modules/deltaprojectsBidAdapter.json | 2 +- metadata/modules/dianomiBidAdapter.json | 2 +- metadata/modules/digitalMatterBidAdapter.json | 2 +- metadata/modules/distroscaleBidAdapter.json | 2 +- metadata/modules/dmdIdSystem.json | 13 ----- .../modules/docereeAdManagerBidAdapter.json | 2 +- metadata/modules/docereeBidAdapter.json | 2 +- metadata/modules/dspxBidAdapter.json | 2 +- metadata/modules/e_volutionBidAdapter.json | 2 +- metadata/modules/edge226BidAdapter.json | 2 +- metadata/modules/empowerBidAdapter.json | 2 +- metadata/modules/equativBidAdapter.json | 2 +- metadata/modules/eskimiBidAdapter.json | 2 +- metadata/modules/etargetBidAdapter.json | 2 +- metadata/modules/euidIdSystem.json | 2 +- metadata/modules/exadsBidAdapter.json | 2 +- metadata/modules/feedadBidAdapter.json | 2 +- metadata/modules/fwsspBidAdapter.json | 2 +- metadata/modules/gamoshiBidAdapter.json | 2 +- metadata/modules/gemiusIdSystem.json | 2 +- metadata/modules/glomexBidAdapter.json | 2 +- metadata/modules/goldbachBidAdapter.json | 2 +- metadata/modules/gridBidAdapter.json | 2 +- metadata/modules/gumgumBidAdapter.json | 2 +- metadata/modules/hadronIdSystem.json | 2 +- metadata/modules/hadronRtdProvider.json | 2 +- metadata/modules/harionBidAdapter.json | 2 +- metadata/modules/holidBidAdapter.json | 2 +- metadata/modules/hybridBidAdapter.json | 2 +- metadata/modules/id5IdSystem.json | 2 +- metadata/modules/identityLinkIdSystem.json | 2 +- metadata/modules/illuminBidAdapter.json | 2 +- metadata/modules/impactifyBidAdapter.json | 2 +- .../modules/improvedigitalBidAdapter.json | 2 +- metadata/modules/inmobiBidAdapter.json | 2 +- metadata/modules/insticatorBidAdapter.json | 2 +- metadata/modules/insuradsBidAdapter.json | 2 +- metadata/modules/intentIqIdSystem.json | 2 +- metadata/modules/intersectionRtdProvider.json | 12 ----- metadata/modules/invibesBidAdapter.json | 2 +- metadata/modules/ipromBidAdapter.json | 2 +- metadata/modules/ixBidAdapter.json | 2 +- metadata/modules/justIdSystem.json | 2 +- metadata/modules/justpremiumBidAdapter.json | 2 +- metadata/modules/jwplayerBidAdapter.json | 2 +- metadata/modules/kargoBidAdapter.json | 2 +- metadata/modules/kueezRtbBidAdapter.json | 2 +- .../modules/limelightDigitalBidAdapter.json | 4 +- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 2 +- metadata/modules/magniteBidAdapter.json | 2 +- metadata/modules/marsmediaBidAdapter.json | 2 +- .../modules/mediaConsortiumBidAdapter.json | 2 +- metadata/modules/mediaforceBidAdapter.json | 2 +- metadata/modules/mediafuseBidAdapter.json | 2 +- metadata/modules/mediagoBidAdapter.json | 2 +- metadata/modules/mediakeysBidAdapter.json | 2 +- metadata/modules/medianetBidAdapter.json | 4 +- metadata/modules/mediasquareBidAdapter.json | 2 +- metadata/modules/mgidBidAdapter.json | 2 +- metadata/modules/mgidRtdProvider.json | 2 +- metadata/modules/mgidXBidAdapter.json | 2 +- metadata/modules/minutemediaBidAdapter.json | 2 +- metadata/modules/missenaBidAdapter.json | 2 +- metadata/modules/mobianRtdProvider.json | 2 +- metadata/modules/mobkoiBidAdapter.json | 2 +- metadata/modules/mobkoiIdSystem.json | 2 +- metadata/modules/msftBidAdapter.json | 2 +- metadata/modules/nativeryBidAdapter.json | 2 +- metadata/modules/nativoBidAdapter.json | 2 +- metadata/modules/newspassidBidAdapter.json | 2 +- .../modules/nextMillenniumBidAdapter.json | 2 +- metadata/modules/nextrollBidAdapter.json | 2 +- metadata/modules/nexx360BidAdapter.json | 12 ++--- metadata/modules/nobidBidAdapter.json | 2 +- metadata/modules/nodalsAiRtdProvider.json | 2 +- metadata/modules/novatiqIdSystem.json | 2 +- metadata/modules/oguryBidAdapter.json | 2 +- metadata/modules/omnidexBidAdapter.json | 2 +- metadata/modules/omsBidAdapter.json | 2 +- metadata/modules/onetagBidAdapter.json | 2 +- metadata/modules/openwebBidAdapter.json | 2 +- metadata/modules/openxBidAdapter.json | 2 +- metadata/modules/operaadsBidAdapter.json | 2 +- metadata/modules/optableBidAdapter.json | 13 ----- metadata/modules/optidigitalBidAdapter.json | 2 +- metadata/modules/optoutBidAdapter.json | 2 +- metadata/modules/orbidderBidAdapter.json | 2 +- metadata/modules/outbrainBidAdapter.json | 2 +- metadata/modules/ozoneBidAdapter.json | 2 +- metadata/modules/pairIdSystem.json | 2 +- metadata/modules/panxoBidAdapter.json | 2 +- metadata/modules/performaxBidAdapter.json | 2 +- .../permutiveIdentityManagerIdSystem.json | 2 +- metadata/modules/permutiveRtdProvider.json | 2 +- metadata/modules/pixfutureBidAdapter.json | 2 +- metadata/modules/playdigoBidAdapter.json | 2 +- metadata/modules/prebid-core.json | 4 +- metadata/modules/precisoBidAdapter.json | 2 +- metadata/modules/prismaBidAdapter.json | 2 +- metadata/modules/programmaticXBidAdapter.json | 2 +- metadata/modules/proxistoreBidAdapter.json | 2 +- metadata/modules/publinkIdSystem.json | 2 +- metadata/modules/pubmaticBidAdapter.json | 2 +- metadata/modules/pubmaticIdSystem.json | 2 +- metadata/modules/pubstackBidAdapter.json | 2 +- metadata/modules/pulsepointBidAdapter.json | 2 +- metadata/modules/quantcastBidAdapter.json | 51 ------------------- metadata/modules/quantcastIdSystem.json | 51 ------------------- metadata/modules/r2b2BidAdapter.json | 2 +- metadata/modules/readpeakBidAdapter.json | 2 +- metadata/modules/relayBidAdapter.json | 2 +- .../modules/relevantdigitalBidAdapter.json | 2 +- metadata/modules/resetdigitalBidAdapter.json | 2 +- metadata/modules/responsiveAdsBidAdapter.json | 2 +- metadata/modules/revcontentBidAdapter.json | 2 +- metadata/modules/revnewBidAdapter.json | 2 +- metadata/modules/rhythmoneBidAdapter.json | 2 +- metadata/modules/richaudienceBidAdapter.json | 2 +- metadata/modules/riseBidAdapter.json | 4 +- metadata/modules/rixengineBidAdapter.json | 2 +- metadata/modules/rtbhouseBidAdapter.json | 2 +- metadata/modules/rubiconBidAdapter.json | 2 +- metadata/modules/scaliburBidAdapter.json | 2 +- metadata/modules/screencoreBidAdapter.json | 2 +- .../modules/seedingAllianceBidAdapter.json | 2 +- metadata/modules/seedtagBidAdapter.json | 2 +- metadata/modules/semantiqRtdProvider.json | 2 +- metadata/modules/sevioBidAdapter.json | 2 +- metadata/modules/sharedIdSystem.json | 2 +- metadata/modules/sharethroughBidAdapter.json | 2 +- metadata/modules/showheroes-bsBidAdapter.json | 2 +- metadata/modules/silvermobBidAdapter.json | 2 +- metadata/modules/sirdataRtdProvider.json | 2 +- metadata/modules/smaatoBidAdapter.json | 2 +- metadata/modules/smartadserverBidAdapter.json | 2 +- metadata/modules/smartxBidAdapter.json | 2 +- metadata/modules/smartyadsBidAdapter.json | 2 +- metadata/modules/smilewantedBidAdapter.json | 2 +- metadata/modules/snigelBidAdapter.json | 2 +- metadata/modules/sonaradsBidAdapter.json | 2 +- metadata/modules/sonobiBidAdapter.json | 2 +- metadata/modules/sovrnBidAdapter.json | 2 +- metadata/modules/sparteoBidAdapter.json | 2 +- metadata/modules/ssmasBidAdapter.json | 2 +- metadata/modules/sspBCBidAdapter.json | 2 +- metadata/modules/stackadaptBidAdapter.json | 2 +- metadata/modules/startioBidAdapter.json | 2 +- metadata/modules/stroeerCoreBidAdapter.json | 2 +- metadata/modules/stvBidAdapter.json | 2 +- metadata/modules/sublimeBidAdapter.json | 2 +- metadata/modules/taboolaBidAdapter.json | 2 +- metadata/modules/taboolaIdSystem.json | 2 +- metadata/modules/tadvertisingBidAdapter.json | 2 +- metadata/modules/tappxBidAdapter.json | 2 +- metadata/modules/targetVideoBidAdapter.json | 2 +- metadata/modules/teadsBidAdapter.json | 2 +- metadata/modules/teadsIdSystem.json | 2 +- metadata/modules/tealBidAdapter.json | 2 +- metadata/modules/tncIdSystem.json | 2 +- metadata/modules/topicsFpdModule.json | 2 +- metadata/modules/toponBidAdapter.json | 2 +- metadata/modules/tripleliftBidAdapter.json | 2 +- metadata/modules/ttdBidAdapter.json | 2 +- metadata/modules/twistDigitalBidAdapter.json | 2 +- metadata/modules/underdogmediaBidAdapter.json | 2 +- metadata/modules/undertoneBidAdapter.json | 2 +- metadata/modules/unifiedIdSystem.json | 2 +- metadata/modules/unrulyBidAdapter.json | 2 +- metadata/modules/userId.json | 2 +- metadata/modules/utiqIdSystem.json | 2 +- metadata/modules/utiqMtpIdSystem.json | 2 +- metadata/modules/validationFpdModule.json | 2 +- metadata/modules/valuadBidAdapter.json | 2 +- metadata/modules/vidazooBidAdapter.json | 2 +- metadata/modules/vidoomyBidAdapter.json | 2 +- metadata/modules/viouslyBidAdapter.json | 2 +- metadata/modules/visxBidAdapter.json | 2 +- metadata/modules/vlybyBidAdapter.json | 2 +- metadata/modules/voxBidAdapter.json | 2 +- metadata/modules/vrtcalBidAdapter.json | 2 +- metadata/modules/vuukleBidAdapter.json | 2 +- metadata/modules/weboramaRtdProvider.json | 2 +- metadata/modules/welectBidAdapter.json | 2 +- metadata/modules/yahooAdsBidAdapter.json | 2 +- metadata/modules/yaleoBidAdapter.json | 2 +- metadata/modules/yieldlabBidAdapter.json | 2 +- metadata/modules/yieldloveBidAdapter.json | 2 +- metadata/modules/yieldmoBidAdapter.json | 2 +- metadata/modules/zeotapIdPlusIdSystem.json | 2 +- metadata/modules/zeta_globalBidAdapter.json | 2 +- .../modules/zeta_global_sspBidAdapter.json | 2 +- package-lock.json | 4 +- package.json | 2 +- 285 files changed, 314 insertions(+), 465 deletions(-) create mode 100644 metadata/modules/categoryTranslation.json delete mode 100644 metadata/modules/dmdIdSystem.json delete mode 100644 metadata/modules/intersectionRtdProvider.json delete mode 100644 metadata/modules/optableBidAdapter.json delete mode 100644 metadata/modules/quantcastBidAdapter.json delete mode 100644 metadata/modules/quantcastIdSystem.json diff --git a/metadata/modules.json b/metadata/modules.json index 757b2215bd7..45362e4421e 100644 --- a/metadata/modules.json +++ b/metadata/modules.json @@ -3620,13 +3620,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "optable", - "aliasOf": null, - "gvlid": null, - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "optidigital", @@ -3963,13 +3956,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "bidder", - "componentName": "quantcast", - "aliasOf": null, - "gvlid": "11", - "disclosureURL": null - }, { "componentType": "bidder", "componentName": "qwarry", @@ -5400,12 +5386,6 @@ "gvlid": null, "disclosureURL": null }, - { - "componentType": "rtd", - "componentName": "intersection", - "gvlid": null, - "disclosureURL": null - }, { "componentType": "rtd", "componentName": "jwplayer", @@ -5677,13 +5657,6 @@ "disclosureURL": null, "aliasOf": null }, - { - "componentType": "userId", - "componentName": "dmdId", - "gvlid": null, - "disclosureURL": null, - "aliasOf": null - }, { "componentType": "userId", "componentName": "euid", @@ -6350,4 +6323,4 @@ "gvlid": null } ] -} +} \ No newline at end of file diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 4b68024bb65..1c632537642 100644 --- a/metadata/modules/33acrossBidAdapter.json +++ b/metadata/modules/33acrossBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-12T22:08:10.749Z", + "timestamp": "2026-03-12T23:06:53.494Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 957546a4135..60e8e6b862c 100644 --- a/metadata/modules/33acrossIdSystem.json +++ b/metadata/modules/33acrossIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-12T22:08:10.865Z", + "timestamp": "2026-03-12T23:06:53.599Z", "disclosures": [] } }, diff --git a/metadata/modules/aceexBidAdapter.json b/metadata/modules/aceexBidAdapter.json index 9535236d103..8db5f6edf69 100644 --- a/metadata/modules/aceexBidAdapter.json +++ b/metadata/modules/aceexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://aceex.io/tcf.json": { - "timestamp": "2026-03-12T22:08:10.868Z", + "timestamp": "2026-03-12T23:06:53.601Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index d7eec3c718c..a94432ea059 100644 --- a/metadata/modules/acuityadsBidAdapter.json +++ b/metadata/modules/acuityadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.acuityads.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:08:10.916Z", + "timestamp": "2026-03-12T23:06:53.671Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index b315108810d..0c193144608 100644 --- a/metadata/modules/adagioBidAdapter.json +++ b/metadata/modules/adagioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:08:10.960Z", + "timestamp": "2026-03-12T23:06:53.712Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index 9e7ee8694c3..52ce021decc 100644 --- a/metadata/modules/adagioRtdProvider.json +++ b/metadata/modules/adagioRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:08:11.025Z", + "timestamp": "2026-03-12T23:06:53.808Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index b7e6f7cef38..5808a21d09e 100644 --- a/metadata/modules/adbroBidAdapter.json +++ b/metadata/modules/adbroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tag.adbro.me/privacy/devicestorage.json": { - "timestamp": "2026-03-12T22:08:11.026Z", + "timestamp": "2026-03-12T23:06:53.808Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index 9286e7ffb20..a109b242581 100644 --- a/metadata/modules/addefendBidAdapter.json +++ b/metadata/modules/addefendBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.addefend.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:11.346Z", + "timestamp": "2026-03-12T23:06:54.626Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index f72d6c1d35f..ce395bc4380 100644 --- a/metadata/modules/adfBidAdapter.json +++ b/metadata/modules/adfBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://site.adform.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T22:08:12.031Z", + "timestamp": "2026-03-12T23:06:55.310Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 1ade808a78c..226f4fa1177 100644 --- a/metadata/modules/adfusionBidAdapter.json +++ b/metadata/modules/adfusionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spicyrtb.com/static/iab-disclosure.json": { - "timestamp": "2026-03-12T22:08:12.032Z", + "timestamp": "2026-03-12T23:06:55.310Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index b570ab72fb3..71485261a0d 100644 --- a/metadata/modules/adheseBidAdapter.json +++ b/metadata/modules/adheseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adhese.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:12.386Z", + "timestamp": "2026-03-12T23:06:55.582Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 4418bf6006a..5c5722b49a8 100644 --- a/metadata/modules/adipoloBidAdapter.json +++ b/metadata/modules/adipoloBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adipolo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:08:12.661Z", + "timestamp": "2026-03-12T23:06:55.845Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index 835d67e0994..0951e076316 100644 --- a/metadata/modules/adkernelAdnBidAdapter.json +++ b/metadata/modules/adkernelAdnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:12.798Z", + "timestamp": "2026-03-12T23:06:55.988Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index c645c463152..844009c1ed8 100644 --- a/metadata/modules/adkernelBidAdapter.json +++ b/metadata/modules/adkernelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:12.975Z", + "timestamp": "2026-03-12T23:06:56.128Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:12.975Z", + "timestamp": "2026-03-12T23:06:56.128Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-03-12T22:08:13.019Z", + "timestamp": "2026-03-12T23:06:56.172Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:13.737Z", + "timestamp": "2026-03-12T23:06:56.902Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:08:13.755Z", + "timestamp": "2026-03-12T23:06:56.923Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index d5dc724b95e..e147ca71841 100644 --- a/metadata/modules/admaticBidAdapter.json +++ b/metadata/modules/admaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.admatic.de/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T22:08:14.117Z", + "timestamp": "2026-03-12T23:06:57.505Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:14.117Z", + "timestamp": "2026-03-12T23:06:57.505Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 7261e78c554..28b3d0ebd86 100644 --- a/metadata/modules/admixerBidAdapter.json +++ b/metadata/modules/admixerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-12T22:08:14.124Z", + "timestamp": "2026-03-12T23:06:57.505Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index f41639bba7b..43ccf69c9e9 100644 --- a/metadata/modules/admixerIdSystem.json +++ b/metadata/modules/admixerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-12T22:08:14.487Z", + "timestamp": "2026-03-12T23:06:57.936Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index 36354def608..ccd488d4c0e 100644 --- a/metadata/modules/adnowBidAdapter.json +++ b/metadata/modules/adnowBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adnow.com/vdsod.json": { - "timestamp": "2026-03-12T22:08:14.489Z", + "timestamp": "2026-03-12T23:06:57.937Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index dd431425833..4407be983f7 100644 --- a/metadata/modules/adnuntiusBidAdapter.json +++ b/metadata/modules/adnuntiusBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:14.722Z", + "timestamp": "2026-03-12T23:06:58.190Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index ecf3d00ad40..0b8bfe9f47a 100644 --- a/metadata/modules/adnuntiusRtdProvider.json +++ b/metadata/modules/adnuntiusRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:15.044Z", + "timestamp": "2026-03-12T23:06:58.526Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index 8628d48a135..0169ff6d3ea 100644 --- a/metadata/modules/adoceanBidAdapter.json +++ b/metadata/modules/adoceanBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-12T22:08:15.045Z", + "timestamp": "2026-03-12T23:06:58.526Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index 3e905e4b794..a3830e0e97f 100644 --- a/metadata/modules/adotBidAdapter.json +++ b/metadata/modules/adotBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.adotmob.com/tcf/tcf.json": { - "timestamp": "2026-03-12T22:08:15.614Z", + "timestamp": "2026-03-12T23:06:58.993Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index 076e973e642..3de63e96da5 100644 --- a/metadata/modules/adponeBidAdapter.json +++ b/metadata/modules/adponeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.adpone.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:15.675Z", + "timestamp": "2026-03-12T23:06:59.037Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 02476afd6ad..605e58304ac 100644 --- a/metadata/modules/adqueryBidAdapter.json +++ b/metadata/modules/adqueryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-12T22:08:15.710Z", + "timestamp": "2026-03-12T23:06:59.058Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index 17ea2a86ecf..300397ce37d 100644 --- a/metadata/modules/adqueryIdSystem.json +++ b/metadata/modules/adqueryIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-12T22:08:16.064Z", + "timestamp": "2026-03-12T23:06:59.412Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 304eb72816a..6c0cfae110e 100644 --- a/metadata/modules/adrinoBidAdapter.json +++ b/metadata/modules/adrinoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.adrino.cloud/iab/device-storage.json": { - "timestamp": "2026-03-12T22:08:16.065Z", + "timestamp": "2026-03-12T23:06:59.412Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index ca4166b6364..1648b192b69 100644 --- a/metadata/modules/ads_interactiveBidAdapter.json +++ b/metadata/modules/ads_interactiveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adsinteractive.com/vendor.json": { - "timestamp": "2026-03-12T22:08:16.105Z", + "timestamp": "2026-03-12T23:06:59.485Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index ebb82b7b8e4..958000a562f 100644 --- a/metadata/modules/adtargetBidAdapter.json +++ b/metadata/modules/adtargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:16.416Z", + "timestamp": "2026-03-12T23:06:59.773Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 82f4ed350f1..42e44bb8c21 100644 --- a/metadata/modules/adtelligentBidAdapter.json +++ b/metadata/modules/adtelligentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:08:16.416Z", + "timestamp": "2026-03-12T23:06:59.773Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:09:18.291Z", + "timestamp": "2026-03-12T23:08:01.416Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index 21caf7c1570..abd10f50638 100644 --- a/metadata/modules/adtelligentIdSystem.json +++ b/metadata/modules/adtelligentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:18.378Z", + "timestamp": "2026-03-12T23:08:01.500Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index de3428e058a..ebd9ec15438 100644 --- a/metadata/modules/aduptechBidAdapter.json +++ b/metadata/modules/aduptechBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.d.adup-tech.com/gdpr/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:18.378Z", + "timestamp": "2026-03-12T23:08:01.500Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index 61b5e3e7f20..42d7c6320a4 100644 --- a/metadata/modules/adyoulikeBidAdapter.json +++ b/metadata/modules/adyoulikeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adyoulike.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T22:09:18.405Z", + "timestamp": "2026-03-12T23:08:01.520Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index 78e6d6a1fa8..1f45b7ea126 100644 --- a/metadata/modules/airgridRtdProvider.json +++ b/metadata/modules/airgridRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.wearemiq.com/privacy-and-compliance/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:09:18.862Z", + "timestamp": "2026-03-12T23:08:01.960Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index 5247a02bf92..447f221d6f5 100644 --- a/metadata/modules/alkimiBidAdapter.json +++ b/metadata/modules/alkimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d1xjh92lb8fey3.cloudfront.net/tcf/alkimi_exchange_tcf.json": { - "timestamp": "2026-03-12T22:09:18.913Z", + "timestamp": "2026-03-12T23:08:01.984Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index 539a2009cb3..245d497dc9c 100644 --- a/metadata/modules/allegroBidAdapter.json +++ b/metadata/modules/allegroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.allegrostatic.com/dsp-tcf-external/device-storage.json": { - "timestamp": "2026-03-12T22:09:19.196Z", + "timestamp": "2026-03-12T23:08:02.265Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index eed80763963..9c78f63d985 100644 --- a/metadata/modules/amxBidAdapter.json +++ b/metadata/modules/amxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-12T22:09:19.700Z", + "timestamp": "2026-03-12T23:08:02.720Z", "disclosures": [ { "identifier": "amuid2", diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index c4555079049..382757827e6 100644 --- a/metadata/modules/amxIdSystem.json +++ b/metadata/modules/amxIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-12T22:09:19.756Z", + "timestamp": "2026-03-12T23:08:02.761Z", "disclosures": [ { "identifier": "amuid2", diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index dba8263fb7e..6009cadb356 100644 --- a/metadata/modules/aniviewBidAdapter.json +++ b/metadata/modules/aniviewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.aniview.com/gdpr/gdpr.json": { - "timestamp": "2026-03-12T22:09:19.757Z", + "timestamp": "2026-03-12T23:08:02.761Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index ce7160245b2..185498f7564 100644 --- a/metadata/modules/anonymisedRtdProvider.json +++ b/metadata/modules/anonymisedRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn1.anonymised.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:19.910Z", + "timestamp": "2026-03-12T23:08:02.902Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/apesterBidAdapter.json b/metadata/modules/apesterBidAdapter.json index c5275d130e9..fca85e6881b 100644 --- a/metadata/modules/apesterBidAdapter.json +++ b/metadata/modules/apesterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apester.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:20.148Z", + "timestamp": "2026-03-12T23:08:02.966Z", "disclosures": [] } }, diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index cbbefa93bb6..4a1476147e9 100644 --- a/metadata/modules/appStockSSPBidAdapter.json +++ b/metadata/modules/appStockSSPBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://app-stock.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:20.249Z", + "timestamp": "2026-03-12T23:08:03.098Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index 2bc7db65725..c978541c082 100644 --- a/metadata/modules/appierBidAdapter.json +++ b/metadata/modules/appierBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.appier.com/deviceStorage2025.json": { - "timestamp": "2026-03-12T22:09:20.279Z", + "timestamp": "2026-03-12T23:08:03.121Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index c3145d71c2e..0f3d645b194 100644 --- a/metadata/modules/appnexusBidAdapter.json +++ b/metadata/modules/appnexusBidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:09:20.988Z", + "timestamp": "2026-03-12T23:08:03.673Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:20.425Z", + "timestamp": "2026-03-12T23:08:03.187Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:09:20.534Z", + "timestamp": "2026-03-12T23:08:03.200Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-03-12T22:09:20.988Z", + "timestamp": "2026-03-12T23:08:03.673Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index bb5878d25ec..771db595f7f 100644 --- a/metadata/modules/appushBidAdapter.json +++ b/metadata/modules/appushBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.thebiding.com/disclosures.json": { - "timestamp": "2026-03-12T22:09:21.030Z", + "timestamp": "2026-03-12T23:08:03.701Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index 022dd34be7a..6726d7e847b 100644 --- a/metadata/modules/apsBidAdapter.json +++ b/metadata/modules/apsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://m.media-amazon.com/images/G/01/adprefs/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:09:21.114Z", + "timestamp": "2026-03-12T23:08:03.768Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index 6df2ca9ceba..e3920836179 100644 --- a/metadata/modules/apstreamBidAdapter.json +++ b/metadata/modules/apstreamBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sak.userreport.com/tcf.json": { - "timestamp": "2026-03-12T22:09:21.133Z", + "timestamp": "2026-03-12T23:08:03.784Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index 61e095e2d2c..b27007c3b8c 100644 --- a/metadata/modules/audiencerunBidAdapter.json +++ b/metadata/modules/audiencerunBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.audiencerun.com/tcf.json": { - "timestamp": "2026-03-12T22:09:21.157Z", + "timestamp": "2026-03-12T23:08:03.796Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index 1eda337c48e..ac71c567d26 100644 --- a/metadata/modules/axisBidAdapter.json +++ b/metadata/modules/axisBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://axis-marketplace.com/tcf.json": { - "timestamp": "2026-03-12T22:09:21.203Z", + "timestamp": "2026-03-12T23:08:03.834Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 6247ff8a4b0..299180fb5b1 100644 --- a/metadata/modules/azerionedgeRtdProvider.json +++ b/metadata/modules/azerionedgeRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-12T22:09:21.257Z", + "timestamp": "2026-03-12T23:08:03.875Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index bb7a0acd6a8..233c2264fec 100644 --- a/metadata/modules/beachfrontBidAdapter.json +++ b/metadata/modules/beachfrontBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-12T22:09:21.289Z", + "timestamp": "2026-03-12T23:08:04.133Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 1b4ff5484c4..8d6cb757d03 100644 --- a/metadata/modules/beopBidAdapter.json +++ b/metadata/modules/beopBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://beop.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:21.690Z", + "timestamp": "2026-03-12T23:08:04.161Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index da57c14afeb..b962ec40078 100644 --- a/metadata/modules/betweenBidAdapter.json +++ b/metadata/modules/betweenBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.betweenx.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:21.820Z", + "timestamp": "2026-03-12T23:08:04.274Z", "disclosures": [] } }, diff --git a/metadata/modules/bidfuseBidAdapter.json b/metadata/modules/bidfuseBidAdapter.json index cb5dc975164..2f8dad2ef5a 100644 --- a/metadata/modules/bidfuseBidAdapter.json +++ b/metadata/modules/bidfuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidfuse.com/disclosure.json": { - "timestamp": "2026-03-02T14:45:34.436Z", + "timestamp": "2026-03-12T23:08:04.348Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index 816ee4718f8..31e722c93aa 100644 --- a/metadata/modules/bidmaticBidAdapter.json +++ b/metadata/modules/bidmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidmatic.io/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:54.416Z", + "timestamp": "2026-03-12T23:08:05.063Z", "disclosures": [] } }, diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index ed3af3d24fe..84a33bc0755 100644 --- a/metadata/modules/bidtheatreBidAdapter.json +++ b/metadata/modules/bidtheatreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.bidtheatre.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:54.453Z", + "timestamp": "2026-03-12T23:08:05.109Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index 0824a1a7add..535b23dc168 100644 --- a/metadata/modules/bliinkBidAdapter.json +++ b/metadata/modules/bliinkBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bliink.io/disclosures.json": { - "timestamp": "2026-03-12T22:09:54.738Z", + "timestamp": "2026-03-12T23:08:05.406Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index bbc08c1fa1a..40a23c294c8 100644 --- a/metadata/modules/blockthroughBidAdapter.json +++ b/metadata/modules/blockthroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://blockthrough.com/tcf_disclosures.json": { - "timestamp": "2026-03-12T22:09:55.082Z", + "timestamp": "2026-03-12T23:08:05.710Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index 11b378f8835..fcb39e75aac 100644 --- a/metadata/modules/blueBidAdapter.json +++ b/metadata/modules/blueBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://getblue.io/iab/iab.json": { - "timestamp": "2026-03-12T22:09:55.228Z", + "timestamp": "2026-03-12T23:08:05.834Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 11b17992f98..0ee8844b0ec 100644 --- a/metadata/modules/bmsBidAdapter.json +++ b/metadata/modules/bmsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bluems.com/iab.json": { - "timestamp": "2026-03-12T22:09:55.573Z", + "timestamp": "2026-03-12T23:08:06.234Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index c40c0ee7f65..bf2900f86e7 100644 --- a/metadata/modules/boldwinBidAdapter.json +++ b/metadata/modules/boldwinBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://magav.videowalldirect.com/iab/videowalldirectiab.json": { - "timestamp": "2026-03-12T22:09:55.591Z", + "timestamp": "2026-03-12T23:08:06.249Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index bc5462cd033..650a7044376 100644 --- a/metadata/modules/bridBidAdapter.json +++ b/metadata/modules/bridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-12T22:09:55.614Z", + "timestamp": "2026-03-12T23:08:06.269Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index ba95cf30aa3..c07c991f104 100644 --- a/metadata/modules/browsiBidAdapter.json +++ b/metadata/modules/browsiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.browsiprod.com/ads/tcf.json": { - "timestamp": "2026-03-12T22:09:55.753Z", + "timestamp": "2026-03-12T23:08:06.406Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index 0f670977350..c3e1aacbce5 100644 --- a/metadata/modules/bucksenseBidAdapter.json +++ b/metadata/modules/bucksenseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://j.bksnimages.com/iab/devsto02.json": { - "timestamp": "2026-03-12T22:09:55.767Z", + "timestamp": "2026-03-12T23:08:06.427Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index b2569e86f48..8a355bfa97b 100644 --- a/metadata/modules/carodaBidAdapter.json +++ b/metadata/modules/carodaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn2.caroda.io/tcfvds/2022-05-17/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:56.173Z", + "timestamp": "2026-03-12T23:08:06.539Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json new file mode 100644 index 00000000000..bc271ef89af --- /dev/null +++ b/metadata/modules/categoryTranslation.json @@ -0,0 +1,16 @@ +{ + "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", + "disclosures": { + "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json": { + "timestamp": "2026-03-12T23:06:53.491Z", + "disclosures": null + } + }, + "components": [ + { + "componentType": "prebid", + "componentName": "categoryTranslation", + "disclosureURL": "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json" + } + ] +} \ No newline at end of file diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 7c7e894d2b9..28a33f4a7b3 100644 --- a/metadata/modules/ceeIdSystem.json +++ b/metadata/modules/ceeIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:56.495Z", + "timestamp": "2026-03-12T23:08:06.830Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 22e58bebe8d..96cb1e5eefa 100644 --- a/metadata/modules/chromeAiRtdProvider.json +++ b/metadata/modules/chromeAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/chromeAiRtdProvider.json": { - "timestamp": "2026-03-12T22:09:56.813Z", + "timestamp": "2026-03-12T23:08:07.158Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index 73f1c96d6c1..1f09a2fec63 100644 --- a/metadata/modules/clickioBidAdapter.json +++ b/metadata/modules/clickioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://o.clickiocdn.com/tcf_storage_info.json": { - "timestamp": "2026-03-12T22:09:56.814Z", + "timestamp": "2026-03-12T23:08:07.160Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index 1feb117ad27..f4b61452680 100644 --- a/metadata/modules/compassBidAdapter.json +++ b/metadata/modules/compassBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T22:09:57.254Z", + "timestamp": "2026-03-12T23:08:07.581Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index 6e5a6830cda..bf2f61d605e 100644 --- a/metadata/modules/conceptxBidAdapter.json +++ b/metadata/modules/conceptxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cncptx.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:09:57.274Z", + "timestamp": "2026-03-12T23:08:07.600Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index 930e6e5e4d9..07abebbeb49 100644 --- a/metadata/modules/connatixBidAdapter.json +++ b/metadata/modules/connatixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://connatix.com/iab-tcf-disclosure.json": { - "timestamp": "2026-03-12T22:09:57.306Z", + "timestamp": "2026-03-12T23:08:07.618Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 08d1c6c9d98..34b44de146a 100644 --- a/metadata/modules/connectIdSystem.json +++ b/metadata/modules/connectIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:09:57.388Z", + "timestamp": "2026-03-12T23:08:07.698Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index 51a49127252..ff0a94c231f 100644 --- a/metadata/modules/connectadBidAdapter.json +++ b/metadata/modules/connectadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.connectad.io/tcf_storage_info.json": { - "timestamp": "2026-03-12T22:09:57.411Z", + "timestamp": "2026-03-12T23:08:07.718Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index 4e3ccf4ad9e..bca3ce6c453 100644 --- a/metadata/modules/contentexchangeBidAdapter.json +++ b/metadata/modules/contentexchangeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://hb.contentexchange.me/template/device_storage.json": { - "timestamp": "2026-03-12T22:09:57.846Z", + "timestamp": "2026-03-12T23:08:07.747Z", "disclosures": [] } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index f14e36e6370..af3cec9cab8 100644 --- a/metadata/modules/conversantBidAdapter.json +++ b/metadata/modules/conversantBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:09:58.819Z", + "timestamp": "2026-03-12T23:08:08.082Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 1f5278881f6..69becfed522 100644 --- a/metadata/modules/copper6sspBidAdapter.json +++ b/metadata/modules/copper6sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.copper6.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:09:58.843Z", + "timestamp": "2026-03-12T23:08:08.099Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 381ae920bb0..178952284b5 100644 --- a/metadata/modules/cpmstarBidAdapter.json +++ b/metadata/modules/cpmstarBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-12T22:09:58.880Z", + "timestamp": "2026-03-12T23:08:08.142Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index 6426fdb0372..b68ba2dbb5c 100644 --- a/metadata/modules/criteoBidAdapter.json +++ b/metadata/modules/criteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T22:09:58.937Z", + "timestamp": "2026-03-12T23:08:08.195Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index d40b57fb9d2..21751576441 100644 --- a/metadata/modules/criteoIdSystem.json +++ b/metadata/modules/criteoIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T22:09:58.954Z", + "timestamp": "2026-03-12T23:08:08.215Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index ac41e883643..eb75e268896 100644 --- a/metadata/modules/cwireBidAdapter.json +++ b/metadata/modules/cwireBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.cwi.re/artifacts/iab/iab.json": { - "timestamp": "2026-03-12T22:09:58.954Z", + "timestamp": "2026-03-12T23:08:08.215Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index b294babde24..3eb5954423e 100644 --- a/metadata/modules/czechAdIdSystem.json +++ b/metadata/modules/czechAdIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cpex.cz/storagedisclosure.json": { - "timestamp": "2026-03-12T22:09:59.276Z", + "timestamp": "2026-03-12T23:08:08.553Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index bff13008f22..88f077fb1ad 100644 --- a/metadata/modules/dailymotionBidAdapter.json +++ b/metadata/modules/dailymotionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://statics.dmcdn.net/a/vds.json": { - "timestamp": "2026-03-12T22:09:59.583Z", + "timestamp": "2026-03-12T23:08:08.856Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index ea02b4a9b01..fe45a2d970c 100644 --- a/metadata/modules/debugging.json +++ b/metadata/modules/debugging.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-12T22:08:10.746Z", + "timestamp": "2026-03-12T23:06:53.490Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 2ac35d12aad..753e53cd5e1 100644 --- a/metadata/modules/deepintentBidAdapter.json +++ b/metadata/modules/deepintentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.deepintent.com/iabeurope_vendor_disclosures.json": { - "timestamp": "2026-03-12T22:09:59.602Z", + "timestamp": "2026-03-12T23:08:08.881Z", "disclosures": null } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index 356a501cf0b..e3071aeffb7 100644 --- a/metadata/modules/defineMediaBidAdapter.json +++ b/metadata/modules/defineMediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://definemedia.de/tcf/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T22:09:59.761Z", + "timestamp": "2026-03-12T23:08:09.128Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index 255f08a9cee..a0edb8a0ffe 100644 --- a/metadata/modules/deltaprojectsBidAdapter.json +++ b/metadata/modules/deltaprojectsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.de17a.com/policy/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:00.194Z", + "timestamp": "2026-03-12T23:08:10.238Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index 6ace04274fd..bd44b87363a 100644 --- a/metadata/modules/dianomiBidAdapter.json +++ b/metadata/modules/dianomiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.dianomi.com/device_storage.json": { - "timestamp": "2026-03-12T22:10:00.626Z", + "timestamp": "2026-03-12T23:08:10.665Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 5ff4e0eb703..98d5b253e34 100644 --- a/metadata/modules/digitalMatterBidAdapter.json +++ b/metadata/modules/digitalMatterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://digitalmatter.ai/disclosures.json": { - "timestamp": "2026-03-12T22:10:00.626Z", + "timestamp": "2026-03-12T23:08:10.666Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index fd69ce7ec72..66d555911f0 100644 --- a/metadata/modules/distroscaleBidAdapter.json +++ b/metadata/modules/distroscaleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.jsrdn.com/tcf/tcf-vendor-disclosure.json": { - "timestamp": "2026-03-12T22:10:00.986Z", + "timestamp": "2026-03-12T23:08:11.035Z", "disclosures": [] } }, diff --git a/metadata/modules/dmdIdSystem.json b/metadata/modules/dmdIdSystem.json deleted file mode 100644 index 1bad2dec26e..00000000000 --- a/metadata/modules/dmdIdSystem.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, - "components": [ - { - "componentType": "userId", - "componentName": "dmdId", - "gvlid": null, - "disclosureURL": null, - "aliasOf": null - } - ] -} \ No newline at end of file diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index d652b8adc62..f964ac6f40a 100644 --- a/metadata/modules/docereeAdManagerBidAdapter.json +++ b/metadata/modules/docereeAdManagerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:01.677Z", + "timestamp": "2026-03-12T23:08:11.085Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index 8ce1f403483..2227437a8b1 100644 --- a/metadata/modules/docereeBidAdapter.json +++ b/metadata/modules/docereeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:02.469Z", + "timestamp": "2026-03-12T23:08:11.837Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 7d7450b3cdf..2b04a0126e4 100644 --- a/metadata/modules/dspxBidAdapter.json +++ b/metadata/modules/dspxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/os.json": { - "timestamp": "2026-03-12T22:10:02.472Z", + "timestamp": "2026-03-12T23:08:11.839Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 3bc787529ba..2251fa970d1 100644 --- a/metadata/modules/e_volutionBidAdapter.json +++ b/metadata/modules/e_volutionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://e-volution.ai/file.json": { - "timestamp": "2026-03-12T22:10:03.134Z", + "timestamp": "2026-03-12T23:08:12.572Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index bcfcefa5892..5a609aed2d1 100644 --- a/metadata/modules/edge226BidAdapter.json +++ b/metadata/modules/edge226BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.serveteck.com/cdn_storage/tcf/tcf.json?a=1.io": { - "timestamp": "2026-03-12T22:10:03.478Z", + "timestamp": "2026-03-12T23:08:12.922Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index 7350a490ed8..60b672222b5 100644 --- a/metadata/modules/empowerBidAdapter.json +++ b/metadata/modules/empowerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.empower.net/vendor/vendor.json": { - "timestamp": "2026-03-12T22:10:03.546Z", + "timestamp": "2026-03-12T23:08:12.982Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index 385a8ec15af..ee2f88eca70 100644 --- a/metadata/modules/equativBidAdapter.json +++ b/metadata/modules/equativBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-12T22:10:03.580Z", + "timestamp": "2026-03-12T23:08:13.007Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 7df427dd047..2dd4b209a26 100644 --- a/metadata/modules/eskimiBidAdapter.json +++ b/metadata/modules/eskimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://dsp-media.eskimi.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:04.284Z", + "timestamp": "2026-03-12T23:08:13.050Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 5a54031bf1d..0f1570f176d 100644 --- a/metadata/modules/etargetBidAdapter.json +++ b/metadata/modules/etargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.etarget.sk/cookies3.json": { - "timestamp": "2026-03-12T22:10:04.304Z", + "timestamp": "2026-03-12T23:08:13.072Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index f3bc5f20405..52bd5e93c0f 100644 --- a/metadata/modules/euidIdSystem.json +++ b/metadata/modules/euidIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T22:10:04.853Z", + "timestamp": "2026-03-12T23:08:13.610Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 579e363fa82..657b485c3e9 100644 --- a/metadata/modules/exadsBidAdapter.json +++ b/metadata/modules/exadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.native7.com/tcf/deviceStorage.php": { - "timestamp": "2026-03-12T22:10:05.054Z", + "timestamp": "2026-03-12T23:08:13.810Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index fd12d52dc2b..1818e4e43c7 100644 --- a/metadata/modules/feedadBidAdapter.json +++ b/metadata/modules/feedadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.feedad.com/tcf-device-disclosures.json": { - "timestamp": "2026-03-12T22:10:05.248Z", + "timestamp": "2026-03-12T23:08:14.068Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index cf7bd6faec6..465fd96af22 100644 --- a/metadata/modules/fwsspBidAdapter.json +++ b/metadata/modules/fwsspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.fwmrm.net/g/devicedisclosure.json": { - "timestamp": "2026-03-12T22:10:05.405Z", + "timestamp": "2026-03-12T23:08:14.177Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index edff51fdee9..349f992b216 100644 --- a/metadata/modules/gamoshiBidAdapter.json +++ b/metadata/modules/gamoshiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.gamoshi.io/disclosures-client-storage.json": { - "timestamp": "2026-03-12T22:10:05.674Z", + "timestamp": "2026-03-12T23:08:14.510Z", "disclosures": [] } }, diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 2205a3b23cc..72c6f91797d 100644 --- a/metadata/modules/gemiusIdSystem.json +++ b/metadata/modules/gemiusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-12T22:10:05.779Z", + "timestamp": "2026-03-12T23:08:14.600Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 927abb549a0..45f9a187dc0 100644 --- a/metadata/modules/glomexBidAdapter.json +++ b/metadata/modules/glomexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:05.780Z", + "timestamp": "2026-03-12T23:08:14.604Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 623adbbb835..868942a038d 100644 --- a/metadata/modules/goldbachBidAdapter.json +++ b/metadata/modules/goldbachBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gb-next.ch/TcfGoldbachDeviceStorage.json": { - "timestamp": "2026-03-12T22:10:05.800Z", + "timestamp": "2026-03-12T23:08:14.621Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index d9150fda2ed..a733083c5c4 100644 --- a/metadata/modules/gridBidAdapter.json +++ b/metadata/modules/gridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.themediagrid.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:05.821Z", + "timestamp": "2026-03-12T23:08:14.648Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index c3633ad3b88..4821ce19b0c 100644 --- a/metadata/modules/gumgumBidAdapter.json +++ b/metadata/modules/gumgumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://marketing.gumgum.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:10:05.908Z", + "timestamp": "2026-03-12T23:08:14.788Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index 18bcb8e7093..3a6b1822dd6 100644 --- a/metadata/modules/hadronIdSystem.json +++ b/metadata/modules/hadronIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-12T22:10:05.974Z", + "timestamp": "2026-03-12T23:08:14.856Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index d4e03bb9a86..47e76dbe42d 100644 --- a/metadata/modules/hadronRtdProvider.json +++ b/metadata/modules/hadronRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-12T22:10:06.105Z", + "timestamp": "2026-03-12T23:08:16.142Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json index a9c4df9982c..27b5783e2f0 100644 --- a/metadata/modules/harionBidAdapter.json +++ b/metadata/modules/harionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://markappmedia.site/vendor.json": { - "timestamp": "2026-03-12T22:10:06.106Z", + "timestamp": "2026-03-12T23:08:16.142Z", "disclosures": [] } }, diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index fc27e1a506a..15af3f082fd 100644 --- a/metadata/modules/holidBidAdapter.json +++ b/metadata/modules/holidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ads.holid.io/devicestorage.json": { - "timestamp": "2026-03-12T22:10:06.473Z", + "timestamp": "2026-03-12T23:08:16.527Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index 33d0175ca70..346e450bf43 100644 --- a/metadata/modules/hybridBidAdapter.json +++ b/metadata/modules/hybridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:06.977Z", + "timestamp": "2026-03-12T23:08:16.773Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index 0b7383bfa2a..5715a40c52e 100644 --- a/metadata/modules/id5IdSystem.json +++ b/metadata/modules/id5IdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://id5-sync.com/tcf/disclosures.json": { - "timestamp": "2026-03-12T22:10:07.237Z", + "timestamp": "2026-03-12T23:08:17.007Z", "disclosures": [ { "identifier": "id5id", diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index d5bb4caadcd..15fecdf12a0 100644 --- a/metadata/modules/identityLinkIdSystem.json +++ b/metadata/modules/identityLinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.ats.rlcdn.com/device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:07.516Z", + "timestamp": "2026-03-12T23:08:17.290Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index eb7ea4a067e..46cbf55a0e3 100644 --- a/metadata/modules/illuminBidAdapter.json +++ b/metadata/modules/illuminBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admanmedia.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:07.534Z", + "timestamp": "2026-03-12T23:08:17.307Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index 8102aef886b..50ea1b92708 100644 --- a/metadata/modules/impactifyBidAdapter.json +++ b/metadata/modules/impactifyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.impactify.io/tcfvendors.json": { - "timestamp": "2026-03-12T22:10:07.832Z", + "timestamp": "2026-03-12T23:08:17.607Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 843ba84217f..0f3990eeb45 100644 --- a/metadata/modules/improvedigitalBidAdapter.json +++ b/metadata/modules/improvedigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-12T22:10:08.170Z", + "timestamp": "2026-03-12T23:08:17.901Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index b20c592af2d..d6b15298b8e 100644 --- a/metadata/modules/inmobiBidAdapter.json +++ b/metadata/modules/inmobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publisher.inmobi.com/public/disclosure": { - "timestamp": "2026-03-12T22:10:08.171Z", + "timestamp": "2026-03-12T23:08:17.901Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index 5020f2f3d16..caeb36fffd7 100644 --- a/metadata/modules/insticatorBidAdapter.json +++ b/metadata/modules/insticatorBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.insticator.com/iab/device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:08.204Z", + "timestamp": "2026-03-12T23:08:17.926Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json index d567f9a835f..c4385d7bcc0 100644 --- a/metadata/modules/insuradsBidAdapter.json +++ b/metadata/modules/insuradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.insurads.com/tcf-vdsod.json": { - "timestamp": "2026-03-12T22:10:08.238Z", + "timestamp": "2026-03-12T23:08:17.954Z", "disclosures": [ { "identifier": "___iat_ses", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 40f5e96a85b..98d6db601de 100644 --- a/metadata/modules/intentIqIdSystem.json +++ b/metadata/modules/intentIqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://agent.intentiq.com/GDPR/gdpr.json": { - "timestamp": "2026-03-12T22:10:08.550Z", + "timestamp": "2026-03-12T23:08:18.067Z", "disclosures": [] } }, diff --git a/metadata/modules/intersectionRtdProvider.json b/metadata/modules/intersectionRtdProvider.json deleted file mode 100644 index ef41a3ebacf..00000000000 --- a/metadata/modules/intersectionRtdProvider.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, - "components": [ - { - "componentType": "rtd", - "componentName": "intersection", - "gvlid": null, - "disclosureURL": null - } - ] -} \ No newline at end of file diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index f86648bc980..bbd35015280 100644 --- a/metadata/modules/invibesBidAdapter.json +++ b/metadata/modules/invibesBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.invibes.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:08.603Z", + "timestamp": "2026-03-12T23:08:18.118Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 71c0ea27e07..917784a694c 100644 --- a/metadata/modules/ipromBidAdapter.json +++ b/metadata/modules/ipromBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://core.iprom.net/info/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:08.957Z", + "timestamp": "2026-03-12T23:08:18.439Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index 88659fa035d..877820657a9 100644 --- a/metadata/modules/ixBidAdapter.json +++ b/metadata/modules/ixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.indexexchange.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:09.420Z", + "timestamp": "2026-03-12T23:08:18.915Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index 9a34bbbaea2..b272ca58355 100644 --- a/metadata/modules/justIdSystem.json +++ b/metadata/modules/justIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audience-solutions.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:09.648Z", + "timestamp": "2026-03-12T23:08:19.038Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 6d12275ca46..4cb4c9861b1 100644 --- a/metadata/modules/justpremiumBidAdapter.json +++ b/metadata/modules/justpremiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.justpremium.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:10:10.167Z", + "timestamp": "2026-03-12T23:08:19.557Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 81af671c6b6..670a86dbe39 100644 --- a/metadata/modules/jwplayerBidAdapter.json +++ b/metadata/modules/jwplayerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.jwplayer.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:10.190Z", + "timestamp": "2026-03-12T23:08:19.594Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 8b226bcb2cc..95a52ff1316 100644 --- a/metadata/modules/kargoBidAdapter.json +++ b/metadata/modules/kargoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://storage.cloud.kargo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:10.397Z", + "timestamp": "2026-03-12T23:08:19.748Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index ac00928a027..8baa9919489 100644 --- a/metadata/modules/kueezRtbBidAdapter.json +++ b/metadata/modules/kueezRtbBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.kueez.com/tcf.json": { - "timestamp": "2026-03-12T22:10:10.435Z", + "timestamp": "2026-03-12T23:08:19.768Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index e2c25df8df3..fc448abd578 100644 --- a/metadata/modules/limelightDigitalBidAdapter.json +++ b/metadata/modules/limelightDigitalBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://policy.iion.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:10.498Z", + "timestamp": "2026-03-12T23:08:19.834Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:10.534Z", + "timestamp": "2026-03-12T23:08:19.881Z", "disclosures": [] } }, diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index 20520f5badc..696af417708 100644 --- a/metadata/modules/liveIntentIdSystem.json +++ b/metadata/modules/liveIntentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:10.534Z", + "timestamp": "2026-03-12T23:08:19.882Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index 530ec55347a..11b01c3ba8b 100644 --- a/metadata/modules/liveIntentRtdProvider.json +++ b/metadata/modules/liveIntentRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:10.647Z", + "timestamp": "2026-03-12T23:08:19.893Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 09fb8c00149..331625f658a 100644 --- a/metadata/modules/livewrappedBidAdapter.json +++ b/metadata/modules/livewrappedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://content.lwadm.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:10.647Z", + "timestamp": "2026-03-12T23:08:19.893Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 76aab6868d4..51d96fa187e 100644 --- a/metadata/modules/loopmeBidAdapter.json +++ b/metadata/modules/loopmeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://co.loopme.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:10.692Z", + "timestamp": "2026-03-12T23:08:19.905Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 2b3ad349907..4670c6cdf58 100644 --- a/metadata/modules/lotamePanoramaIdSystem.json +++ b/metadata/modules/lotamePanoramaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tags.crwdcntrl.net/privacy/tcf-purposes.json": { - "timestamp": "2026-03-12T22:10:10.820Z", + "timestamp": "2026-03-12T23:08:20.945Z", "disclosures": [ { "identifier": "_cc_id", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 08ef9ad992b..17d05c419a5 100644 --- a/metadata/modules/luponmediaBidAdapter.json +++ b/metadata/modules/luponmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://luponmedia.com/vendor_device_storage.json": { - "timestamp": "2026-03-12T22:10:10.839Z", + "timestamp": "2026-03-12T23:08:20.956Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 04740d52f33..7b0849b4c21 100644 --- a/metadata/modules/madvertiseBidAdapter.json +++ b/metadata/modules/madvertiseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.bluestack.app/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:11.259Z", + "timestamp": "2026-03-12T23:08:21.375Z", "disclosures": [] } }, diff --git a/metadata/modules/magniteBidAdapter.json b/metadata/modules/magniteBidAdapter.json index 8b379ec0237..647f67af714 100644 --- a/metadata/modules/magniteBidAdapter.json +++ b/metadata/modules/magniteBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T22:10:11.625Z", + "timestamp": "2026-03-12T23:08:21.727Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index 9c7931d5359..c1472bc9153 100644 --- a/metadata/modules/marsmediaBidAdapter.json +++ b/metadata/modules/marsmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mars.media/apis/tcf-v2.json": { - "timestamp": "2026-03-12T22:10:11.864Z", + "timestamp": "2026-03-12T23:08:21.962Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index b4a0931c6d9..b5e34ebeed4 100644 --- a/metadata/modules/mediaConsortiumBidAdapter.json +++ b/metadata/modules/mediaConsortiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.hubvisor.io/assets/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:12.001Z", + "timestamp": "2026-03-12T23:08:22.137Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index 6bfc75a6d6d..1843a229116 100644 --- a/metadata/modules/mediaforceBidAdapter.json +++ b/metadata/modules/mediaforceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://comparisons.org/privacy.json": { - "timestamp": "2026-03-12T22:10:12.152Z", + "timestamp": "2026-03-12T23:08:22.153Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index 43b2a7073d1..e30b3e64011 100644 --- a/metadata/modules/mediafuseBidAdapter.json +++ b/metadata/modules/mediafuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:10:12.171Z", + "timestamp": "2026-03-12T23:08:22.169Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index 672ce38fc43..645b720d610 100644 --- a/metadata/modules/mediagoBidAdapter.json +++ b/metadata/modules/mediagoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.mediago.io/js/tcf.json": { - "timestamp": "2026-03-12T22:10:12.171Z", + "timestamp": "2026-03-12T23:08:22.170Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index 7b38366099f..614413ecf7b 100644 --- a/metadata/modules/mediakeysBidAdapter.json +++ b/metadata/modules/mediakeysBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.eu-west-3.amazonaws.com/adserving.resourcekeys.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:12.237Z", + "timestamp": "2026-03-12T23:08:22.193Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index 1d96abaea09..f259e397a4e 100644 --- a/metadata/modules/medianetBidAdapter.json +++ b/metadata/modules/medianetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.media.net/tcfv2/gvl/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:12.526Z", + "timestamp": "2026-03-12T23:08:22.474Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:12.601Z", + "timestamp": "2026-03-12T23:08:22.604Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index cd7dfc198f1..4d650651c3e 100644 --- a/metadata/modules/mediasquareBidAdapter.json +++ b/metadata/modules/mediasquareBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediasquare.fr/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:12.649Z", + "timestamp": "2026-03-12T23:08:22.644Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index 6607c7790d3..db30999407d 100644 --- a/metadata/modules/mgidBidAdapter.json +++ b/metadata/modules/mgidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T22:10:13.290Z", + "timestamp": "2026-03-12T23:08:23.161Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index c9ffffe8d39..3dad2be7b2c 100644 --- a/metadata/modules/mgidRtdProvider.json +++ b/metadata/modules/mgidRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T22:10:13.445Z", + "timestamp": "2026-03-12T23:08:23.213Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index f6ab5b938d8..d3f1cb262e9 100644 --- a/metadata/modules/mgidXBidAdapter.json +++ b/metadata/modules/mgidXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T22:10:13.446Z", + "timestamp": "2026-03-12T23:08:23.213Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 906e3985743..384513bb788 100644 --- a/metadata/modules/minutemediaBidAdapter.json +++ b/metadata/modules/minutemediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://disclosures.mmctsvc.com/device-storage.json": { - "timestamp": "2026-03-12T22:10:13.446Z", + "timestamp": "2026-03-12T23:08:23.214Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index ea3ceb2fef5..89eb9062040 100644 --- a/metadata/modules/missenaBidAdapter.json +++ b/metadata/modules/missenaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.missena.io/iab.json": { - "timestamp": "2026-03-12T22:10:13.477Z", + "timestamp": "2026-03-12T23:08:23.229Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index a16278af4a2..51f881c92e5 100644 --- a/metadata/modules/mobianRtdProvider.json +++ b/metadata/modules/mobianRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.outcomes.net/tcf.json": { - "timestamp": "2026-03-12T22:10:13.530Z", + "timestamp": "2026-03-12T23:08:23.278Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index 4847f81f9a1..a583908e83d 100644 --- a/metadata/modules/mobkoiBidAdapter.json +++ b/metadata/modules/mobkoiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:13.545Z", + "timestamp": "2026-03-12T23:08:23.293Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index 5fdd9aad4b6..fa8f77eb08f 100644 --- a/metadata/modules/mobkoiIdSystem.json +++ b/metadata/modules/mobkoiIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:13.564Z", + "timestamp": "2026-03-12T23:08:23.308Z", "disclosures": [] } }, diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index f183f403cef..0da54ed3895 100644 --- a/metadata/modules/msftBidAdapter.json +++ b/metadata/modules/msftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T22:10:13.564Z", + "timestamp": "2026-03-12T23:08:23.308Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index 39f79a8f57e..a347669741f 100644 --- a/metadata/modules/nativeryBidAdapter.json +++ b/metadata/modules/nativeryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnimg.nativery.com/widget/js/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:13.565Z", + "timestamp": "2026-03-12T23:08:23.309Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index a2e0a2e5a39..1299b651c55 100644 --- a/metadata/modules/nativoBidAdapter.json +++ b/metadata/modules/nativoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.nativo.com/tcf-disclosures.json": { - "timestamp": "2026-03-12T22:10:13.937Z", + "timestamp": "2026-03-12T23:08:23.635Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index c44beecc740..2053f96f14a 100644 --- a/metadata/modules/newspassidBidAdapter.json +++ b/metadata/modules/newspassidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-12T22:10:13.956Z", + "timestamp": "2026-03-12T23:08:23.650Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 77e05d285b9..6fe74538d4a 100644 --- a/metadata/modules/nextMillenniumBidAdapter.json +++ b/metadata/modules/nextMillenniumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://nextmillennium.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:13.956Z", + "timestamp": "2026-03-12T23:08:23.650Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index 690992ecc53..2c7214929d9 100644 --- a/metadata/modules/nextrollBidAdapter.json +++ b/metadata/modules/nextrollBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.adroll.com/shares/device_storage.json": { - "timestamp": "2026-03-12T22:10:14.009Z", + "timestamp": "2026-03-12T23:08:23.692Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 2ed0c6194dc..0fea9796603 100644 --- a/metadata/modules/nexx360BidAdapter.json +++ b/metadata/modules/nexx360BidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:15.647Z", + "timestamp": "2026-03-12T23:08:24.482Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-03-12T22:10:14.792Z", + "timestamp": "2026-03-12T23:08:23.940Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:14.814Z", + "timestamp": "2026-03-12T23:08:23.956Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:15.157Z", + "timestamp": "2026-03-12T23:08:24.080Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T22:10:15.157Z", + "timestamp": "2026-03-12T23:08:24.080Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:15.275Z", + "timestamp": "2026-03-12T23:08:24.094Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index 3e1db39a1f1..532bc1a1274 100644 --- a/metadata/modules/nobidBidAdapter.json +++ b/metadata/modules/nobidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://public.servenobid.com/gdpr_tcf/vendor_device_storage_operational_disclosures.json": { - "timestamp": "2026-03-12T22:10:15.648Z", + "timestamp": "2026-03-12T23:08:24.483Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index e9149f928ed..41ecd2dbbb8 100644 --- a/metadata/modules/nodalsAiRtdProvider.json +++ b/metadata/modules/nodalsAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.nodals.ai/vendor.json": { - "timestamp": "2026-03-12T22:10:15.662Z", + "timestamp": "2026-03-12T23:08:24.494Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index e00ff6cb06c..0a48587fd13 100644 --- a/metadata/modules/novatiqIdSystem.json +++ b/metadata/modules/novatiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://novatiq.com/privacy/iab/novatiq.json": { - "timestamp": "2026-03-12T22:10:17.062Z", + "timestamp": "2026-03-12T23:08:26.306Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index 4cfb3f0349f..c9583ef6b1b 100644 --- a/metadata/modules/oguryBidAdapter.json +++ b/metadata/modules/oguryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.ogury.co/disclosure.json": { - "timestamp": "2026-03-12T22:10:17.397Z", + "timestamp": "2026-03-12T23:08:26.889Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 0fa6858f1cf..2848970e619 100644 --- a/metadata/modules/omnidexBidAdapter.json +++ b/metadata/modules/omnidexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.omni-dex.io/devicestorage.json": { - "timestamp": "2026-03-12T22:10:17.454Z", + "timestamp": "2026-03-12T23:08:26.949Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index 33e9ca2e1e7..da2d3c212c6 100644 --- a/metadata/modules/omsBidAdapter.json +++ b/metadata/modules/omsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T22:10:17.549Z", + "timestamp": "2026-03-12T23:08:27.002Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index b80995d641a..559390ec04c 100644 --- a/metadata/modules/onetagBidAdapter.json +++ b/metadata/modules/onetagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://onetag-cdn.com/privacy/tcf_storage.json": { - "timestamp": "2026-03-12T22:10:17.549Z", + "timestamp": "2026-03-12T23:08:27.002Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index 7dfcd9c4de1..5bf9951f472 100644 --- a/metadata/modules/openwebBidAdapter.json +++ b/metadata/modules/openwebBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-12T22:10:17.834Z", + "timestamp": "2026-03-12T23:08:27.271Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index 0cadad35da9..3384063c1cb 100644 --- a/metadata/modules/openxBidAdapter.json +++ b/metadata/modules/openxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.openx.com/device-storage.json": { - "timestamp": "2026-03-12T22:10:17.876Z", + "timestamp": "2026-03-12T23:08:27.301Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index 67037d8a019..cdce5936aa0 100644 --- a/metadata/modules/operaadsBidAdapter.json +++ b/metadata/modules/operaadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://res.adx.opera.com/dsd.json": { - "timestamp": "2026-03-12T22:10:17.979Z", + "timestamp": "2026-03-12T23:08:27.331Z", "disclosures": [] } }, diff --git a/metadata/modules/optableBidAdapter.json b/metadata/modules/optableBidAdapter.json deleted file mode 100644 index 52fd4a88cd7..00000000000 --- a/metadata/modules/optableBidAdapter.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": {}, - "components": [ - { - "componentType": "bidder", - "componentName": "optable", - "aliasOf": null, - "gvlid": null, - "disclosureURL": null - } - ] -} \ No newline at end of file diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 2ebbd12f3f7..163d763b5ac 100644 --- a/metadata/modules/optidigitalBidAdapter.json +++ b/metadata/modules/optidigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://scripts.opti-digital.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:18.004Z", + "timestamp": "2026-03-12T23:08:27.351Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index 0f851cf3c51..69155fcc870 100644 --- a/metadata/modules/optoutBidAdapter.json +++ b/metadata/modules/optoutBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserving.optoutadvertising.com/dsd": { - "timestamp": "2026-03-12T22:10:18.038Z", + "timestamp": "2026-03-12T23:08:27.391Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index a865b24291f..6d44e6e8128 100644 --- a/metadata/modules/orbidderBidAdapter.json +++ b/metadata/modules/orbidderBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://orbidder.otto.de/disclosure/dsd.json": { - "timestamp": "2026-03-12T22:10:18.322Z", + "timestamp": "2026-03-12T23:08:27.643Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index 217b851be6c..c71b6d9ca56 100644 --- a/metadata/modules/outbrainBidAdapter.json +++ b/metadata/modules/outbrainBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.outbrain.com/privacy/wp-json/privacy/v2/devicestorage.json": { - "timestamp": "2026-03-12T22:10:18.632Z", + "timestamp": "2026-03-12T23:08:27.934Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index bbdf224c628..767945d7c82 100644 --- a/metadata/modules/ozoneBidAdapter.json +++ b/metadata/modules/ozoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://prebid.the-ozone-project.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:18.983Z", + "timestamp": "2026-03-12T23:08:28.179Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index feac8380bea..f6b33be6f43 100644 --- a/metadata/modules/pairIdSystem.json +++ b/metadata/modules/pairIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gstatic.com/iabtcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:19.229Z", + "timestamp": "2026-03-12T23:08:28.359Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index 5882564d13a..2feff926c6d 100644 --- a/metadata/modules/panxoBidAdapter.json +++ b/metadata/modules/panxoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.panxo.ai/tcf/device-storage.json": { - "timestamp": "2026-03-12T22:10:19.246Z", + "timestamp": "2026-03-12T23:08:28.376Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index eecb2ee2d1a..21d4a5a2e0a 100644 --- a/metadata/modules/performaxBidAdapter.json +++ b/metadata/modules/performaxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.performax.cz/device_storage.json": { - "timestamp": "2026-03-12T22:10:19.458Z", + "timestamp": "2026-03-12T23:08:28.562Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index 9153cc5dd1a..995eec3b97e 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-12T22:10:19.875Z", + "timestamp": "2026-03-12T23:08:28.975Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index b4e8c426292..1ee8c15c8bb 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-12T22:10:20.045Z", + "timestamp": "2026-03-12T23:08:29.157Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index ec912ad2766..774bed2c22b 100644 --- a/metadata/modules/pixfutureBidAdapter.json +++ b/metadata/modules/pixfutureBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.pixfuture.com/vendor-disclosures.json": { - "timestamp": "2026-03-12T22:10:20.046Z", + "timestamp": "2026-03-12T23:08:29.162Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index 14e5ad67b65..e6b20e6efdc 100644 --- a/metadata/modules/playdigoBidAdapter.json +++ b/metadata/modules/playdigoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://playdigo.com/file.json": { - "timestamp": "2026-03-12T22:10:20.107Z", + "timestamp": "2026-03-12T23:08:29.253Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index f3b7e083762..814e273947c 100644 --- a/metadata/modules/prebid-core.json +++ b/metadata/modules/prebid-core.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json": { - "timestamp": "2026-03-12T22:08:10.745Z", + "timestamp": "2026-03-12T23:06:53.489Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-12T22:08:10.745Z", + "timestamp": "2026-03-12T23:06:53.490Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index 2ea7fa4976b..a3b84473e7e 100644 --- a/metadata/modules/precisoBidAdapter.json +++ b/metadata/modules/precisoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://preciso.net/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:20.282Z", + "timestamp": "2026-03-12T23:08:29.427Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 4ac853595b1..91464af95ef 100644 --- a/metadata/modules/prismaBidAdapter.json +++ b/metadata/modules/prismaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:20.548Z", + "timestamp": "2026-03-12T23:08:29.668Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index d7293abed2c..7c89c1f8793 100644 --- a/metadata/modules/programmaticXBidAdapter.json +++ b/metadata/modules/programmaticXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://progrtb.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T22:10:20.549Z", + "timestamp": "2026-03-12T23:08:29.668Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 5cbd6f6fcfb..428f24795a4 100644 --- a/metadata/modules/proxistoreBidAdapter.json +++ b/metadata/modules/proxistoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://abs.proxistore.com/assets/json/proxistore_device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:20.620Z", + "timestamp": "2026-03-12T23:08:29.716Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index c1f0d5e4604..39fa4ce12ce 100644 --- a/metadata/modules/publinkIdSystem.json +++ b/metadata/modules/publinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:21.075Z", + "timestamp": "2026-03-12T23:08:30.247Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index bcfeedecd7d..b765455d5c5 100644 --- a/metadata/modules/pubmaticBidAdapter.json +++ b/metadata/modules/pubmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:21.077Z", + "timestamp": "2026-03-12T23:08:30.247Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 9bf4088f622..0e11b84000d 100644 --- a/metadata/modules/pubmaticIdSystem.json +++ b/metadata/modules/pubmaticIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:21.107Z", + "timestamp": "2026-03-12T23:08:30.281Z", "disclosures": [] } }, diff --git a/metadata/modules/pubstackBidAdapter.json b/metadata/modules/pubstackBidAdapter.json index b58ebc84074..c79764d4b69 100644 --- a/metadata/modules/pubstackBidAdapter.json +++ b/metadata/modules/pubstackBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json": { - "timestamp": "2026-03-12T22:10:21.138Z", + "timestamp": "2026-03-12T23:08:30.307Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index f45dabc2911..348f5e96539 100644 --- a/metadata/modules/pulsepointBidAdapter.json +++ b/metadata/modules/pulsepointBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bh.contextweb.com/tcf/vendorInfo.json": { - "timestamp": "2026-03-12T22:10:21.139Z", + "timestamp": "2026-03-12T23:08:30.308Z", "disclosures": [] } }, diff --git a/metadata/modules/quantcastBidAdapter.json b/metadata/modules/quantcastBidAdapter.json deleted file mode 100644 index 81f572ce634..00000000000 --- a/metadata/modules/quantcastBidAdapter.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": { - "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2026-03-12T22:10:21.156Z", - "disclosures": [ - { - "identifier": "__qca", - "type": "cookie", - "maxAgeSeconds": 33868800, - "cookieRefresh": false, - "purposes": [ - 1, - 2, - 3, - 4, - 7, - 8, - 9, - 10 - ] - }, - { - "identifier": "__dlt", - "type": "cookie", - "maxAgeSeconds": 0, - "cookieRefresh": false, - "purposes": [ - 1, - 2, - 3, - 4, - 7, - 8, - 9, - 10 - ] - } - ] - } - }, - "components": [ - { - "componentType": "bidder", - "componentName": "quantcast", - "aliasOf": null, - "gvlid": "11", - "disclosureURL": "https://www.quantcast.com/.well-known/devicestorage.json" - } - ] -} diff --git a/metadata/modules/quantcastIdSystem.json b/metadata/modules/quantcastIdSystem.json deleted file mode 100644 index 8da244b8bcc..00000000000 --- a/metadata/modules/quantcastIdSystem.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", - "disclosures": { - "https://www.quantcast.com/.well-known/devicestorage.json": { - "timestamp": "2026-03-12T22:10:21.339Z", - "disclosures": [ - { - "identifier": "__qca", - "type": "cookie", - "maxAgeSeconds": 33868800, - "cookieRefresh": false, - "purposes": [ - 1, - 2, - 3, - 4, - 7, - 8, - 9, - 10 - ] - }, - { - "identifier": "__dlt", - "type": "cookie", - "maxAgeSeconds": 0, - "cookieRefresh": false, - "purposes": [ - 1, - 2, - 3, - 4, - 7, - 8, - 9, - 10 - ] - } - ] - } - }, - "components": [ - { - "componentType": "userId", - "componentName": "quantcastId", - "gvlid": "11", - "disclosureURL": "https://www.quantcast.com/.well-known/devicestorage.json", - "aliasOf": null - } - ] -} \ No newline at end of file diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index f74872ab028..698ef82d928 100644 --- a/metadata/modules/r2b2BidAdapter.json +++ b/metadata/modules/r2b2BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.r2b2.io/cookie_disclosure": { - "timestamp": "2026-03-12T22:10:21.340Z", + "timestamp": "2026-03-12T23:08:30.324Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index b4cf2c217d3..61fca3ddb3b 100644 --- a/metadata/modules/readpeakBidAdapter.json +++ b/metadata/modules/readpeakBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.readpeak.com/tcf/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:21.726Z", + "timestamp": "2026-03-12T23:08:30.745Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index aa6f3948712..118f8ee3809 100644 --- a/metadata/modules/relayBidAdapter.json +++ b/metadata/modules/relayBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://relay42.com/hubfs/raw_assets/public/IAB.json": { - "timestamp": "2026-03-12T22:10:21.840Z", + "timestamp": "2026-03-12T23:08:30.788Z", "disclosures": null } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index 93df72f5adb..717f504aa72 100644 --- a/metadata/modules/relevantdigitalBidAdapter.json +++ b/metadata/modules/relevantdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.relevant-digital.com/resources/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:22.912Z", + "timestamp": "2026-03-12T23:08:31.502Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index 2ac4905e0ab..b8db18e8e0e 100644 --- a/metadata/modules/resetdigitalBidAdapter.json +++ b/metadata/modules/resetdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resetdigital.co/GDPR-TCF.json": { - "timestamp": "2026-03-12T22:10:23.072Z", + "timestamp": "2026-03-12T23:08:31.656Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index a4675905c32..b5b76647d7b 100644 --- a/metadata/modules/responsiveAdsBidAdapter.json +++ b/metadata/modules/responsiveAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publish.responsiveads.com/tcf/tcf-v2.json": { - "timestamp": "2026-03-12T22:10:23.124Z", + "timestamp": "2026-03-12T23:08:31.692Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index 1d9f78f29fa..b1033f2976b 100644 --- a/metadata/modules/revcontentBidAdapter.json +++ b/metadata/modules/revcontentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sothebys.revcontent.com/static/device_storage.json": { - "timestamp": "2026-03-12T22:10:23.147Z", + "timestamp": "2026-03-12T23:08:31.707Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 151f806367c..60c89b747ee 100644 --- a/metadata/modules/revnewBidAdapter.json +++ b/metadata/modules/revnewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediafuse.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:23.170Z", + "timestamp": "2026-03-12T23:08:31.723Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index 5f49fbaff6f..d77ddb44dc7 100644 --- a/metadata/modules/rhythmoneBidAdapter.json +++ b/metadata/modules/rhythmoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:23.313Z", + "timestamp": "2026-03-12T23:08:31.783Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index 0cd2060ac10..3e057285d43 100644 --- a/metadata/modules/richaudienceBidAdapter.json +++ b/metadata/modules/richaudienceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnj.richaudience.com/52a26ab9400b2a9f5aabfa20acf3196g.json": { - "timestamp": "2026-03-12T22:10:23.857Z", + "timestamp": "2026-03-12T23:08:32.102Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index 09556ab8f3c..acb704c0a08 100644 --- a/metadata/modules/riseBidAdapter.json +++ b/metadata/modules/riseBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d2pm7iglz0b6eq.cloudfront.net/RiseDeviceStorage.json": { - "timestamp": "2026-03-12T22:10:23.924Z", + "timestamp": "2026-03-12T23:08:32.161Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-12T22:10:23.924Z", + "timestamp": "2026-03-12T23:08:32.161Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 24e76038617..3aaf42a9f17 100644 --- a/metadata/modules/rixengineBidAdapter.json +++ b/metadata/modules/rixengineBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.algorix.co/gdpr-disclosure.json": { - "timestamp": "2026-03-12T22:10:23.928Z", + "timestamp": "2026-03-12T23:08:32.162Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index 46482b73609..c0469a2e18f 100644 --- a/metadata/modules/rtbhouseBidAdapter.json +++ b/metadata/modules/rtbhouseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://rtbhouse.com/DeviceStorage.json": { - "timestamp": "2026-03-12T22:10:23.956Z", + "timestamp": "2026-03-12T23:08:32.183Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index 747e77a010c..0667103ba03 100644 --- a/metadata/modules/rubiconBidAdapter.json +++ b/metadata/modules/rubiconBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T22:10:24.245Z", + "timestamp": "2026-03-12T23:08:32.640Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 2a972b9ff57..2c8cb6075eb 100644 --- a/metadata/modules/scaliburBidAdapter.json +++ b/metadata/modules/scaliburBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://legal.overwolf.com/docs/overwolf/website/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:24.246Z", + "timestamp": "2026-03-12T23:08:32.640Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 7517281f3c9..9a577aa57a0 100644 --- a/metadata/modules/screencoreBidAdapter.json +++ b/metadata/modules/screencoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://screencore.io/tcf.json": { - "timestamp": "2026-03-12T22:10:24.262Z", + "timestamp": "2026-03-12T23:08:32.653Z", "disclosures": [] } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index 4361242461a..d60004aa12f 100644 --- a/metadata/modules/seedingAllianceBidAdapter.json +++ b/metadata/modules/seedingAllianceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.nativendo.de/cdn/asset/tcf/purpose-specific-storage-and-access-information.json": { - "timestamp": "2026-03-12T22:10:24.306Z", + "timestamp": "2026-03-12T23:08:32.698Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index 1eec287d127..851af0ba8bf 100644 --- a/metadata/modules/seedtagBidAdapter.json +++ b/metadata/modules/seedtagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-12T22:10:24.453Z", + "timestamp": "2026-03-12T23:08:32.722Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index 92235a4ab7f..cad8ee98bea 100644 --- a/metadata/modules/semantiqRtdProvider.json +++ b/metadata/modules/semantiqRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-12T22:10:24.454Z", + "timestamp": "2026-03-12T23:08:32.722Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 3b1021d99aa..319c63422fd 100644 --- a/metadata/modules/sevioBidAdapter.json +++ b/metadata/modules/sevioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sevio.com/tcf.json": { - "timestamp": "2026-03-12T22:10:24.579Z", + "timestamp": "2026-03-12T23:08:32.815Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index 48b2b4cc735..f54e17d525e 100644 --- a/metadata/modules/sharedIdSystem.json +++ b/metadata/modules/sharedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-12T22:10:24.726Z", + "timestamp": "2026-03-12T23:08:32.981Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index b8a82151eed..26a1c5a5805 100644 --- a/metadata/modules/sharethroughBidAdapter.json +++ b/metadata/modules/sharethroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.sharethrough.com/gvl.json": { - "timestamp": "2026-03-12T22:10:24.726Z", + "timestamp": "2026-03-12T23:08:32.982Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index 1383db8041f..fb503b4c6e3 100644 --- a/metadata/modules/showheroes-bsBidAdapter.json +++ b/metadata/modules/showheroes-bsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static-origin.showheroes.com/gvl_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:24.749Z", + "timestamp": "2026-03-12T23:08:33.004Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index f28a5bbb8d4..eeea13e4f81 100644 --- a/metadata/modules/silvermobBidAdapter.json +++ b/metadata/modules/silvermobBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://silvermob.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:25.202Z", + "timestamp": "2026-03-12T23:08:33.468Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index fcc145f2ec1..e95e6c322e4 100644 --- a/metadata/modules/sirdataRtdProvider.json +++ b/metadata/modules/sirdataRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sirdata.eu/sirdata_device_storage_disclosure.json": { - "timestamp": "2026-03-12T22:10:25.219Z", + "timestamp": "2026-03-12T23:08:33.480Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index bbc0a256952..f629ce94df0 100644 --- a/metadata/modules/smaatoBidAdapter.json +++ b/metadata/modules/smaatoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.smaato.com/hubfs/Smaato/IAB/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:25.539Z", + "timestamp": "2026-03-12T23:08:33.781Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index e14adb2f1b4..8382a3dc5fa 100644 --- a/metadata/modules/smartadserverBidAdapter.json +++ b/metadata/modules/smartadserverBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-12T22:10:25.608Z", + "timestamp": "2026-03-12T23:08:33.859Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 118329be943..45c75b5553b 100644 --- a/metadata/modules/smartxBidAdapter.json +++ b/metadata/modules/smartxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.smartclip.net/iab/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:25.608Z", + "timestamp": "2026-03-12T23:08:33.860Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index 150dcdc2d47..70506e2e382 100644 --- a/metadata/modules/smartyadsBidAdapter.json +++ b/metadata/modules/smartyadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smartyads.com/tcf.json": { - "timestamp": "2026-03-12T22:10:25.631Z", + "timestamp": "2026-03-12T23:08:33.878Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index 999c50003b5..0dbc4d5ae08 100644 --- a/metadata/modules/smilewantedBidAdapter.json +++ b/metadata/modules/smilewantedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smilewanted.com/vendor-device-storage-disclosures.json": { - "timestamp": "2026-03-12T22:10:25.681Z", + "timestamp": "2026-03-12T23:08:33.923Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index d5a39ee78a1..6540fc1e34b 100644 --- a/metadata/modules/snigelBidAdapter.json +++ b/metadata/modules/snigelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.snigelweb.com/gvl/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:26.162Z", + "timestamp": "2026-03-12T23:08:34.058Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index b0b7f687461..8c1110100c4 100644 --- a/metadata/modules/sonaradsBidAdapter.json +++ b/metadata/modules/sonaradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bridgeupp.com/device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:26.212Z", + "timestamp": "2026-03-12T23:08:34.094Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index d6b32f27212..873f8ac006c 100644 --- a/metadata/modules/sonobiBidAdapter.json +++ b/metadata/modules/sonobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sonobi.com/tcf2-device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:26.465Z", + "timestamp": "2026-03-12T23:08:34.315Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index cb49c204338..c59398e0424 100644 --- a/metadata/modules/sovrnBidAdapter.json +++ b/metadata/modules/sovrnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sovrn.com/tcf-cookie-disclosure/disclosure.json": { - "timestamp": "2026-03-12T22:10:26.697Z", + "timestamp": "2026-03-12T23:08:34.614Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index f08cd053efb..6d4b498dfa1 100644 --- a/metadata/modules/sparteoBidAdapter.json +++ b/metadata/modules/sparteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:26.723Z", + "timestamp": "2026-03-12T23:08:34.640Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index 8bdd6c9a108..e2b8bba3e02 100644 --- a/metadata/modules/ssmasBidAdapter.json +++ b/metadata/modules/ssmasBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://semseoymas.com/iab.json": { - "timestamp": "2026-03-12T22:10:26.996Z", + "timestamp": "2026-03-12T23:08:35.024Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index d8c8173139b..0d7ffe8dc10 100644 --- a/metadata/modules/sspBCBidAdapter.json +++ b/metadata/modules/sspBCBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:27.562Z", + "timestamp": "2026-03-12T23:08:35.666Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index 51c35687f35..cb95c1af2d4 100644 --- a/metadata/modules/stackadaptBidAdapter.json +++ b/metadata/modules/stackadaptBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.amazonaws.com/stackadapt_public/disclosures.json": { - "timestamp": "2026-03-12T22:10:27.563Z", + "timestamp": "2026-03-12T23:08:35.666Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index 3a8247469cd..9bd22e20ca2 100644 --- a/metadata/modules/startioBidAdapter.json +++ b/metadata/modules/startioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://info.startappservice.com/tcf/start.io_domains.json": { - "timestamp": "2026-03-12T22:10:27.596Z", + "timestamp": "2026-03-12T23:08:35.695Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index e91ec5ae523..52c97b0c17a 100644 --- a/metadata/modules/stroeerCoreBidAdapter.json +++ b/metadata/modules/stroeerCoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.stroeer.de/StroeerSSP_deviceStorage.json": { - "timestamp": "2026-03-12T22:10:27.628Z", + "timestamp": "2026-03-12T23:08:35.710Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 80afcbf23ed..3bdb29742c1 100644 --- a/metadata/modules/stvBidAdapter.json +++ b/metadata/modules/stvBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/stv.json": { - "timestamp": "2026-03-12T22:10:28.116Z", + "timestamp": "2026-03-12T23:08:35.856Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index 3e2f3c966d9..da12e9c4075 100644 --- a/metadata/modules/sublimeBidAdapter.json +++ b/metadata/modules/sublimeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.ayads.co/cookiepolicy.json": { - "timestamp": "2026-03-12T22:10:28.751Z", + "timestamp": "2026-03-12T23:08:36.483Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index 21e4a05b7c9..0a707f7db5f 100644 --- a/metadata/modules/taboolaBidAdapter.json +++ b/metadata/modules/taboolaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-12T22:10:29.014Z", + "timestamp": "2026-03-12T23:08:36.739Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 91570ca4d8c..644320f8aa4 100644 --- a/metadata/modules/taboolaIdSystem.json +++ b/metadata/modules/taboolaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-12T22:10:29.225Z", + "timestamp": "2026-03-12T23:08:36.964Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index bb3f6fd76ca..7c43b990245 100644 --- a/metadata/modules/tadvertisingBidAdapter.json +++ b/metadata/modules/tadvertisingBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:29.226Z", + "timestamp": "2026-03-12T23:08:36.964Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index b050e2466be..071896b3a49 100644 --- a/metadata/modules/tappxBidAdapter.json +++ b/metadata/modules/tappxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tappx.com/devicestorage.json": { - "timestamp": "2026-03-12T22:10:29.489Z", + "timestamp": "2026-03-12T23:08:37.058Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index 044a05022c1..cbc4b5e3a72 100644 --- a/metadata/modules/targetVideoBidAdapter.json +++ b/metadata/modules/targetVideoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-12T22:10:29.515Z", + "timestamp": "2026-03-12T23:08:37.087Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 1d4efcbd205..4b3118c201f 100644 --- a/metadata/modules/teadsBidAdapter.json +++ b/metadata/modules/teadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:29.515Z", + "timestamp": "2026-03-12T23:08:37.087Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index 52fc75596fb..eb2202852a4 100644 --- a/metadata/modules/teadsIdSystem.json +++ b/metadata/modules/teadsIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:29.533Z", + "timestamp": "2026-03-12T23:08:37.103Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index 3c00282271f..e997209791f 100644 --- a/metadata/modules/tealBidAdapter.json +++ b/metadata/modules/tealBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://c.bids.ws/iab/disclosures.json": { - "timestamp": "2026-03-12T22:10:29.534Z", + "timestamp": "2026-03-12T23:08:37.103Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index 281333fae45..7d86176897f 100644 --- a/metadata/modules/tncIdSystem.json +++ b/metadata/modules/tncIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.tncid.app/iab-tcf-device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:29.622Z", + "timestamp": "2026-03-12T23:08:37.149Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index b0b40591db7..c9d5568690e 100644 --- a/metadata/modules/topicsFpdModule.json +++ b/metadata/modules/topicsFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/topicsFpdModule.json": { - "timestamp": "2026-03-12T22:08:10.746Z", + "timestamp": "2026-03-12T23:06:53.490Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index ae41022c029..4524251e34b 100644 --- a/metadata/modules/toponBidAdapter.json +++ b/metadata/modules/toponBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json": { - "timestamp": "2026-03-12T22:10:29.645Z", + "timestamp": "2026-03-12T23:08:37.167Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index f6ccfe73ca2..03b331982d0 100644 --- a/metadata/modules/tripleliftBidAdapter.json +++ b/metadata/modules/tripleliftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://triplelift.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:29.688Z", + "timestamp": "2026-03-12T23:08:37.203Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index 2d814677a16..c343c4d0e4a 100644 --- a/metadata/modules/ttdBidAdapter.json +++ b/metadata/modules/ttdBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T22:10:29.732Z", + "timestamp": "2026-03-12T23:08:37.241Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 1c2daa0178b..6d25355743e 100644 --- a/metadata/modules/twistDigitalBidAdapter.json +++ b/metadata/modules/twistDigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://twistdigital.net/iab.json": { - "timestamp": "2026-03-12T22:10:29.733Z", + "timestamp": "2026-03-12T23:08:37.241Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index 54c6db09e91..a7ad18fb94c 100644 --- a/metadata/modules/underdogmediaBidAdapter.json +++ b/metadata/modules/underdogmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.underdog.media/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:29.781Z", + "timestamp": "2026-03-12T23:08:37.298Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index 13fb362df12..f3696ffd6f9 100644 --- a/metadata/modules/undertoneBidAdapter.json +++ b/metadata/modules/undertoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.undertone.com/js/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:29.805Z", + "timestamp": "2026-03-12T23:08:37.316Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index a40798d0689..a24a207bf7c 100644 --- a/metadata/modules/unifiedIdSystem.json +++ b/metadata/modules/unifiedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T22:10:29.821Z", + "timestamp": "2026-03-12T23:08:37.326Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index a72c97154d2..f314503aad4 100644 --- a/metadata/modules/unrulyBidAdapter.json +++ b/metadata/modules/unrulyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:29.822Z", + "timestamp": "2026-03-12T23:08:37.327Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 6623422b5ec..02321da2ddd 100644 --- a/metadata/modules/userId.json +++ b/metadata/modules/userId.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/userId-optout.json": { - "timestamp": "2026-03-12T22:08:10.748Z", + "timestamp": "2026-03-12T23:06:53.492Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 375af471798..48b958e6684 100644 --- a/metadata/modules/utiqIdSystem.json +++ b/metadata/modules/utiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:29.822Z", + "timestamp": "2026-03-12T23:08:37.327Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index a9594833154..f7714f2496e 100644 --- a/metadata/modules/utiqMtpIdSystem.json +++ b/metadata/modules/utiqMtpIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:29.822Z", + "timestamp": "2026-03-12T23:08:37.327Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 2b168fbf985..53d8fa66502 100644 --- a/metadata/modules/validationFpdModule.json +++ b/metadata/modules/validationFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-12T22:08:10.747Z", + "timestamp": "2026-03-12T23:06:53.491Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index 492d3bd631d..f70c7ecbc43 100644 --- a/metadata/modules/valuadBidAdapter.json +++ b/metadata/modules/valuadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.valuad.cloud/tcfdevice.json": { - "timestamp": "2026-03-12T22:10:29.823Z", + "timestamp": "2026-03-12T23:08:37.327Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index a4863792f6a..1d7b7cf92e7 100644 --- a/metadata/modules/vidazooBidAdapter.json +++ b/metadata/modules/vidazooBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidazoo.com/gdpr-tcf/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:30.009Z", + "timestamp": "2026-03-12T23:08:37.492Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index b780fe526fa..75176ce6615 100644 --- a/metadata/modules/vidoomyBidAdapter.json +++ b/metadata/modules/vidoomyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidoomy.com/storageurl/devicestoragediscurl.json": { - "timestamp": "2026-03-12T22:10:30.076Z", + "timestamp": "2026-03-12T23:08:37.564Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index b09eff904eb..5280246976d 100644 --- a/metadata/modules/viouslyBidAdapter.json +++ b/metadata/modules/viouslyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:30.561Z", + "timestamp": "2026-03-12T23:08:38.059Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 1f96f020fa1..8c3aeb35775 100644 --- a/metadata/modules/visxBidAdapter.json +++ b/metadata/modules/visxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.yoc.com/visx/sellers/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:30.561Z", + "timestamp": "2026-03-12T23:08:38.060Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index 42573ed4eac..96b677c1965 100644 --- a/metadata/modules/vlybyBidAdapter.json +++ b/metadata/modules/vlybyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vlyby.com/conf/iab/gvl.json": { - "timestamp": "2026-03-12T22:10:30.871Z", + "timestamp": "2026-03-12T23:08:38.603Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index c9563feb52b..d6589dc61b9 100644 --- a/metadata/modules/voxBidAdapter.json +++ b/metadata/modules/voxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:31.208Z", + "timestamp": "2026-03-12T23:08:38.805Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 2b7f0bc3f4a..5dda95e8954 100644 --- a/metadata/modules/vrtcalBidAdapter.json +++ b/metadata/modules/vrtcalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vrtcal.com/docs/gdpr-tcf-disclosures.json": { - "timestamp": "2026-03-12T22:10:31.208Z", + "timestamp": "2026-03-12T23:08:38.805Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index 7511f2a459a..c971f4c74a9 100644 --- a/metadata/modules/vuukleBidAdapter.json +++ b/metadata/modules/vuukleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vuukle.com/data-privacy/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:31.226Z", + "timestamp": "2026-03-12T23:08:38.818Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 6e018cb51af..6fbb3823928 100644 --- a/metadata/modules/weboramaRtdProvider.json +++ b/metadata/modules/weboramaRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cstatic.weborama.fr/tcf/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:31.508Z", + "timestamp": "2026-03-12T23:08:39.112Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index 6beaadcfa65..989d5ce9e23 100644 --- a/metadata/modules/welectBidAdapter.json +++ b/metadata/modules/welectBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.welect.de/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:31.681Z", + "timestamp": "2026-03-12T23:08:39.395Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index d220a3c2026..32dc22359b5 100644 --- a/metadata/modules/yahooAdsBidAdapter.json +++ b/metadata/modules/yahooAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-12T22:10:32.155Z", + "timestamp": "2026-03-12T23:08:39.851Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json index 1aa5db80bfd..e04bbb7cefc 100644 --- a/metadata/modules/yaleoBidAdapter.json +++ b/metadata/modules/yaleoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-12T22:10:32.156Z", + "timestamp": "2026-03-12T23:08:39.851Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index b75cdeb4f5d..797c988da96 100644 --- a/metadata/modules/yieldlabBidAdapter.json +++ b/metadata/modules/yieldlabBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.yieldlab.net/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:32.156Z", + "timestamp": "2026-03-12T23:08:39.852Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index c5e07235556..4e813fbcae8 100644 --- a/metadata/modules/yieldloveBidAdapter.json +++ b/metadata/modules/yieldloveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn-a.yieldlove.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:32.297Z", + "timestamp": "2026-03-12T23:08:39.967Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index 718616bdde3..901ecb4dce1 100644 --- a/metadata/modules/yieldmoBidAdapter.json +++ b/metadata/modules/yieldmoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://devicestoragedisclosureurl.yieldmo.com/deviceStorage.json": { - "timestamp": "2026-03-12T22:10:32.321Z", + "timestamp": "2026-03-12T23:08:39.982Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 34c54e057cb..7d39fc4607a 100644 --- a/metadata/modules/zeotapIdPlusIdSystem.json +++ b/metadata/modules/zeotapIdPlusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spl.zeotap.com/assets/iab-disclosure.json": { - "timestamp": "2026-03-12T22:10:32.404Z", + "timestamp": "2026-03-12T23:08:40.229Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index aefa390a9ff..b66af7e0ffe 100644 --- a/metadata/modules/zeta_globalBidAdapter.json +++ b/metadata/modules/zeta_globalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:32.522Z", + "timestamp": "2026-03-12T23:08:40.340Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index c4961662609..0d3fd5a6294 100644 --- a/metadata/modules/zeta_global_sspBidAdapter.json +++ b/metadata/modules/zeta_global_sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T22:10:32.604Z", + "timestamp": "2026-03-12T23:08:40.459Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index 1c9389d6faa..df46dcbdd4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "10.29.1-pre", + "version": "11.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "10.29.1-pre", + "version": "11.0.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index c65d337c002..57506004790 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "10.29.1-pre", + "version": "11.0.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From a5f73714d8ce9d5dc07a669c780dc16e2bed5602 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Thu, 12 Mar 2026 23:09:34 +0000 Subject: [PATCH 28/50] Increment version to 11.1.1-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index df46dcbdd4e..f3158525443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "11.0.0", + "version": "11.1.1-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "11.0.0", + "version": "11.1.1-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 57506004790..c641af69015 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "11.0.0", + "version": "11.1.1-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 11ea4232934a581751a01153ea10a2d2944c1103 Mon Sep 17 00:00:00 2001 From: Philip Watson Date: Fri, 13 Mar 2026 18:45:00 +1300 Subject: [PATCH 29/50] StroeerCore Bid Adapter: add support for TIDs and GPID (#14548) * StroeerCore Bid Adapter: add support for TIDs and GPID * Fix lint --- modules/stroeerCoreBidAdapter.js | 39 +++++--- .../modules/stroeerCoreBidAdapter_spec.js | 97 ++++++++++++++++--- 2 files changed, 110 insertions(+), 26 deletions(-) diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index 3a73cbbac6b..9ece204613e 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -79,13 +79,8 @@ export const spec = { }; } - const ORTB2_KEYS = ['regs.ext.dsa', 'device.ext.cdep', 'site.ext']; - ORTB2_KEYS.forEach(key => { - const value = deepAccess(bidderRequest.ortb2, key); - if (value !== undefined) { - deepSetValue(basePayload, `ortb2.${key}`, value); - } - }); + const ORTB2_PATHS = ['regs.ext.dsa', 'device.ext.cdep', 'site.ext', 'source.tid']; + copyDeepPaths(basePayload, bidderRequest.ortb2, ORTB2_PATHS, 'ortb2'); const bannerBids = validBidRequests .filter(hasBanner) @@ -214,12 +209,17 @@ const hasVideo = bidReq => { ['instream', 'outstream'].indexOf(mediaTypes.video.context) > -1; }; -const mapToPayloadBaseBid = (bidRequest) => ({ - bid: bidRequest.bidId, - sid: bidRequest.params.sid, - viz: elementInView(bidRequest.adUnitCode), - sfp: bidRequest.params.sfp, -}); +const mapToPayloadBaseBid = (bidRequest) => { + const bid = { + bid: bidRequest.bidId, + sid: bidRequest.params.sid, + viz: elementInView(bidRequest.adUnitCode), + sfp: bidRequest.params.sfp, + tid: bidRequest.transactionId, + } + copyDeepPaths(bid, bidRequest.ortb2Imp, ['ext.gpid'], 'ortb2Imp'); + return bid; +}; const mapToPayloadBannerBid = (bidRequest) => { const sizes = deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []; @@ -288,4 +288,17 @@ const createFloorPriceObject = (mediaType, sizes, bidRequest) => { }; } +const copyDeepPaths = (target, source, paths, targetPrefix = '') => { + paths.forEach(path => { + const value = deepAccess(source, path); + if (value !== undefined) { + const targetPath = targetPrefix + ? `${targetPrefix}.${path}` + : path; + + deepSetValue(target, targetPath, value); + } + }); +} + registerBidder(spec); diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 7c08dc5d118..defefedb6c9 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -107,8 +107,10 @@ describe('stroeerCore bid adapter', function () { bidder: 'stroeerCore', adUnitCode: 'div-2', mediaTypes: { - banner: { - sizes: [[728, 90]], + video: { + context: 'outstream', + playerSize: [1280, 720], + mimes: ['video/mp4'] } }, params: { @@ -430,8 +432,10 @@ describe('stroeerCore bid adapter', function () { 'sid': 'ODA=', 'bid': 'bid2', 'viz': true, - 'ban': { - 'siz': [[728, 90]] + 'vid': { + 'ctx': 'outstream', + 'mim': ['video/mp4'], + 'siz': [1280, 720] } }], 'user': { @@ -547,6 +551,7 @@ describe('stroeerCore bid adapter', function () { 'fp': undefined }, 'sfp': undefined, + 'tid': undefined, }, { 'sid': 'ABC=', @@ -557,6 +562,7 @@ describe('stroeerCore bid adapter', function () { }, 'viz': undefined, 'sfp': undefined, + 'tid': undefined, } ]; @@ -572,6 +578,7 @@ describe('stroeerCore bid adapter', function () { 'fp': undefined }, 'sfp': undefined, + 'tid': undefined, } ]; @@ -615,6 +622,7 @@ describe('stroeerCore bid adapter', function () { 'fp': undefined }, 'sfp': undefined, + 'tid': undefined, } ]; @@ -630,6 +638,7 @@ describe('stroeerCore bid adapter', function () { 'fp': undefined }, 'sfp': undefined, + 'tid': undefined, } ]; @@ -750,6 +759,11 @@ describe('stroeerCore bid adapter', function () { .withArgs({ currency: 'EUR', mediaType: 'banner', size: [728, 90] }) .returns({ currency: 'USD', floor: 1.85 }) + delete bidReq.bids[1].mediaTypes.video; + bidReq.bids[1].mediaTypes.banner = { + sizes: [[728, 90]], + }; + bidReq.bids[0].getFloor = getFloorStub1; bidReq.bids[1].getFloor = getFloorStub2; @@ -798,12 +812,6 @@ describe('stroeerCore bid adapter', function () { context: 'instream' }; - delete bidReq.bids[1].mediaTypes.banner; - bidReq.bids[1].mediaTypes.video = { - playerSize: [1280, 720], - context: 'outstream' - }; - bidReq.bids[0].getFloor = getFloorStub1; bidReq.bids[1].getFloor = getFloorStub2; @@ -840,10 +848,10 @@ describe('stroeerCore bid adapter', function () { const secondBid = serverRequestBids[1]; assert.nestedPropertyVal(firstBid, 'ban.fp', undefined); - assert.nestedPropertyVal(secondBid, 'ban.fp', undefined); + assert.nestedPropertyVal(secondBid, 'vid.fp', undefined); - assert.isTrue(getFloorSpy.calledWith({ currency: 'EUR', mediaType: 'banner', size: '*' })); - assert.isTrue(getFloorSpy.calledWith({ currency: 'EUR', mediaType: 'banner', size: [728, 90] })); + assert.isTrue(getFloorSpy.calledWith({ currency: 'EUR', mediaType: 'video', size: '*' })); + assert.isTrue(getFloorSpy.calledWith({ currency: 'EUR', mediaType: 'video', size: [1280, 720] })); assert.isTrue(getFloorSpy.calledTwice); }); @@ -976,6 +984,69 @@ describe('stroeerCore bid adapter', function () { const sentOrtb2 = serverRequestInfo.data.ortb2; assert.deepEqual(sentOrtb2, { site: { ext: ortb2.site.ext } }) }); + + it('should add the bid transaction id', () => { + const bidReq = buildBidderRequest(); + const uuid0 = 'f9545c4c-7d3f-4941-9319-d515af162085'; + const uuid1 = '8ce92d85-e9b0-4682-8025-bf58d452b2a7'; + + bidReq.bids[0].transactionId = uuid0; + bidReq.bids[1].transactionId = uuid1; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const [bid0, bid1] = serverRequestInfo.data.bids; + + assert.equal(bid0.tid, uuid0); + assert.equal(bid1.tid, uuid1); + }); + + it('should add the source transaction id', () => { + const bidReq = buildBidderRequest(); + const tid = '7c3c82b2-30bb-49dc-9e3b-0148cd769a28'; + + const ortb2 = { + source: { + tid + } + }; + + bidReq.ortb2 = utils.deepClone(ortb2); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const sentOrtb2 = serverRequestInfo.data.ortb2; + + assert.equal(sentOrtb2.source.tid, tid); + }); + + describe('ortb2Imp interface', () => { + it('should add the Global Placement IDs (GPID)', () => { + const bidReq = buildBidderRequest(); + + bidReq.bids[0].ortb2Imp = { + ext: { + gpid: '/8292/homepage-top', + do: 'not care about this' + } + }; + + bidReq.bids[1].ortb2Imp = { + random: { + number: 2329 + }, + ext: { + gpid: '/2231/bottom' + } + }; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + const [bid1, bid2] = serverRequestInfo.data.bids; + + assert.deepEqual(bid1.ortb2Imp, { ext: { gpid: '/8292/homepage-top' } }); + assert.deepEqual(bid2.ortb2Imp, { ext: { gpid: '/2231/bottom' } }); + }); + }); }); }); }); From f584def8b5cf9da2f7a8129123a56c26bfcc175c Mon Sep 17 00:00:00 2001 From: Antonios Sarhanis Date: Fri, 13 Mar 2026 17:02:04 +1100 Subject: [PATCH 30/50] Incorporate ortb2Imp.ext.data as kv in ad request. (#14549) --- modules/adnuntiusBidAdapter.js | 29 ++- test/spec/modules/adnuntiusBidAdapter_spec.js | 189 +++++++++++------- 2 files changed, 145 insertions(+), 73 deletions(-) diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 8dd1ad55247..5c758c09c2e 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -7,6 +7,7 @@ import { getUnixTimestampFromNow, getWinDimensions, isArray, + isPlainObject, isEmpty, isStr } from '../src/utils.js'; @@ -254,10 +255,25 @@ const targetingTool = (function() { existingUrlRelatedData.segments = segments; }, - mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { - const siteKvs = getKvsFromOrtb(bidderRequest || {}, 'site.ext.data'); - const userKvs = getKvsFromOrtb(bidderRequest || {}, 'user.ext.data'); - if (isEmpty(siteKvs) && isEmpty(userKvs)) { + mergeKvsFromOrtb: function(bidTargeting, bidderRequest, bid) { + function sanitizeKeyValues(kvs) { + return Object.keys(kvs || {}).reduce((acc, key) => { + const value = kvs[key]; + if (isArray(value)) { + acc[key] = value.map(v => { + return isPlainObject(v) ? JSON.stringify(v) : v; + }); + return acc; + } + acc[key] = value; + return acc; + }, {}); + } + + const siteKvs = sanitizeKeyValues(getKvsFromOrtb(bidderRequest || {}, 'site.ext.data')); + const userKvs = sanitizeKeyValues(getKvsFromOrtb(bidderRequest || {}, 'user.ext.data')); + const impKvs = sanitizeKeyValues(deepAccess(bid, 'ortb2Imp.ext.data')); + if (isEmpty(siteKvs) && isEmpty(userKvs) && isEmpty(impKvs)) { return; } if (bidTargeting.kv && !Array.isArray(bidTargeting.kv)) { @@ -270,6 +286,9 @@ const targetingTool = (function() { if (!isEmpty(userKvs)) { bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(userKvs)); } + if (!isEmpty(impKvs)) { + bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(impKvs)); + } } } })(); @@ -359,7 +378,7 @@ export const spec = { } const bidTargeting = { ...bid.params.targeting || {} }; - targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); + targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest, bid); const mediaTypes = bid.mediaTypes || {}; const validMediaTypes = SUPPORTED_MEDIA_TYPES.filter(mt => { return mediaTypes[mt]; diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 1e642092a30..9bedf6789e0 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -11,13 +11,18 @@ import { getWinDimensions } from '../../../src/utils.js'; import { getGlobalVarName } from '../../../src/buildOptions.js'; -describe('adnuntiusBidAdapter', function () { +describe('adnuntiusBidAdapter', function() { const sandbox = sinon.createSandbox(); 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: getUnixTimestampFromNow() }, { exp: getUnixTimestampFromNow(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: getUnixTimestampFromNow(1) }, { key: 'valid', value: 'also-valid', exp: getUnixTimestampFromNow(1) }, { key: 'expired', value: 'fwefew', exp: getUnixTimestampFromNow() }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: getUnixTimestampFromNow(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow() }] + const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow() }, { exp: getUnixTimestampFromNow(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: getUnixTimestampFromNow(1) }, { key: 'valid', value: 'also-valid', exp: getUnixTimestampFromNow(1) }, { key: 'expired', value: 'fwefew', exp: getUnixTimestampFromNow() }, { + key: 'usi', + value: 'should be skipped because timestamp', + exp: getUnixTimestampFromNow(), + network: 'adnuntius' + }, { key: 'usi', value: usi, exp: getUnixTimestampFromNow(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow() }] let storage; before(() => { @@ -35,7 +40,7 @@ describe('adnuntiusBidAdapter', function () { resetExpectedUrls(); }); - afterEach(function () { + afterEach(function() { config.resetConfig(); config.setBidderConfig({ bidders: [] }); localStorage.removeItem('adn.metaData'); @@ -81,8 +86,8 @@ describe('adnuntiusBidAdapter', function () { const expectation = sortedExpectations[i]; const actual = sortedActuals[i]; - const expectationAsString = expectation[0] + ":" + expectation[1]; - const actualAsString = actual[0] + ":" + actual[1]; + const expectationAsString = expectation[0] + ':' + expectation[1]; + const actualAsString = actual[0] + ':' + actual[1]; expect(expectationAsString).to.equal(actualAsString); } expect(sortedExpectations.length).to.equal(sortedActuals.length); @@ -369,7 +374,7 @@ describe('adnuntiusBidAdapter', function () { 'layoutId': 'buyers_network_image_layout_1', 'layoutName': 'Image', 'layoutExternalReference': '', - 'html': "\n\n\n \n \n \n\n\n
\n
\"\"/
\n
\n \n\n", + 'html': '\n\n\n \n \n \n\n\n
\n
\n
\n \n\n', 'renderTemplate': '' } ]; @@ -610,7 +615,7 @@ describe('adnuntiusBidAdapter', function () { { 'auId': '0000000000000551', 'targetId': 'adn-0000000000000551', - 'html': "\n\n\n \n \n \n\n\n
\n
\"\"/
\n
\n \n\n", + 'html': '\n\n\n \n \n \n\n\n
\n
\n
\n \n\n', 'matchedAdCount': 1, 'responseId': 'adn-rsp--229633088', 'deals': deals, @@ -874,20 +879,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() { const winDimensions = getWinDimensions(); const viewport = winDimensions.innerWidth + 'x' + winDimensions.innerHeight; const prebidVersion = window[getGlobalVarName()].version; @@ -958,18 +963,18 @@ describe('adnuntiusBidAdapter', function () { })); }); - it('should pass for different end points in config', function () { + it('should pass for different end points in config', function() { config.setConfig({ env: 'localhost', protocol: 'http' }) - const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { })); + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, LOCALHOST_URL); }); - it('Test specifying deal IDs', function () { + it('Test specifying deal IDs', function() { const dealIdRequest = deepClone(bidderRequests); dealIdRequest[0].params.dealId = 'simplestringdeal'; dealIdRequest[0].params.inventory = { @@ -992,7 +997,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","dealId":[{"id":"123","bidfloor":12,"bidfloorcur":"USD"}],"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); @@ -1011,7 +1016,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request2[0].url, ENDPOINT_URL_BASE); }); - it('Test request changes for voided au ids', function () { + it('Test request changes for voided au ids', function() { storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow(1) }, { auId: '0000000000000023', exp: getUnixTimestampFromNow(1) }] }])); const bRequests = bidderRequests.concat([{ bidId: 'adn-11118b6bc', @@ -1063,7 +1068,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); @@ -1079,7 +1084,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, ENDPOINT_URL); }); - it('Test multiformat requests', function () { + it('Test multiformat requests', function() { const request = spec.buildRequests(multiBidderRequest, {}); expect(request.length).to.equal(1); expect(request.data) @@ -1096,7 +1101,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, ENDPOINT_URL); }); - it('should pass segments if available in config and merge from targeting', function () { + it('should pass segments if available in config and merge from targeting', function() { const ortb2 = { user: { data: [{ @@ -1128,7 +1133,8 @@ describe('adnuntiusBidAdapter', function () { }).length; } - it('should pass site data ext as key values to ad server', function () { + it('should pass site data ext as key values to ad server', function() { + delete bidderRequests[0].ortb2Imp; const ortb2 = { site: { ext: { @@ -1160,7 +1166,8 @@ describe('adnuntiusBidAdapter', function () { delete bidderRequests[0].params.targeting; }); - it('should pass site.ext.data and user.ext.data as key values to ad server with targeting in different format', function () { + it('should pass site.ext.data and user.ext.data as key values to ad server with targeting in different format', function() { + delete bidderRequests[0].ortb2Imp; const ortb2 = { user: { ext: { @@ -1186,23 +1193,68 @@ describe('adnuntiusBidAdapter', function () { { '9090': ['take it over'] } ] }; + bidderRequests[0].ortb2Imp = { + ext: { + data: { + 'fromImp': 'imp-value', + '9090': 'from-imp' + } + } + }; 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(countMatches(data.adUnits[0].kv, { 'from': 'user' })).to.equal(1); - expect(countMatches(data.adUnits[0].kv, { '9090': 'from-user' })).to.equal(1); expect(countMatches(data.adUnits[0].kv, { '9090': ['take it over'] })).to.equal(1); expect(countMatches(data.adUnits[0].kv, { 'merge': ['this'] })).to.equal(1); expect(countMatches(data.adUnits[0].kv, { '9090': 'should-be-retained' })).to.equal(1); expect(countMatches(data.adUnits[0].kv, { '45678': 'true' })).to.equal(1); expect(countMatches(data.adUnits[0].kv, { '12345': 'true' })).to.equal(1); - expect(data.adUnits[0].kv.length).to.equal(7); + expect(countMatches(data.adUnits[0].kv, { '9090': 'from-user' })).to.equal(1); + expect(countMatches(data.adUnits[0].kv, { 'from': 'user' })).to.equal(1); + expect(countMatches(data.adUnits[0].kv, { '9090': 'from-imp' })).to.equal(1); + expect(countMatches(data.adUnits[0].kv, { 'fromImp': 'imp-value' })).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(9); delete bidderRequests[0].params.targeting; + delete bidderRequests[0].ortb2Imp; }); - it('should pass site data ext as key values to ad server even if no kv targeting specified in params.targeting', function () { + it('should pass values from ortb2Imp.ext.data', function() { + delete bidderRequests[0].params.targeting; + bidderRequests[0].ortb2Imp = { + ext: { + data: { + 'arrayVal': ['a', 'b'], + 'anotherVal': ['c', { 'fred': 'said' }], + 'objectVal': { + 'nested': 'nope' + }, + 'stringVal': 'ok' + } + } + }; + + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url'); + const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, { 'arrayVal': ['a', 'b'] })).to.equal(1); + const anotherVal = (data.adUnits[0].kv.find(kv => kv.anotherVal) || {}).anotherVal; + expect(anotherVal).to.be.an('array').with.lengthOf(2); + expect(anotherVal[0]).to.equal('c'); + expect(anotherVal[1]).to.be.a('string'); + expect(anotherVal[1]).to.contain('fred'); + expect(anotherVal[1]).to.contain('said'); + expect(countMatches(data.adUnits[0].kv, { 'objectVal': { 'nested': 'nope' } })).to.equal(1); + expect(countMatches(data.adUnits[0].kv, { 'stringVal': 'ok' })).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(4); + + delete bidderRequests[0].ortb2Imp; + }); + + it('should pass site data ext as key values to ad server even if no kv targeting specified in params.targeting', function() { + delete bidderRequests[0].ortb2Imp; const ortb2 = { site: { ext: { @@ -1226,11 +1278,11 @@ describe('adnuntiusBidAdapter', function () { delete bidderRequests[0].params.targeting; }); - it('should skip passing site ext if missing', function () { + it('should skip passing site ext if missing', function() { + delete bidderRequests[0].ortb2Imp; const ortb2 = { site: { - ext: { - } + ext: {} } }; @@ -1242,7 +1294,8 @@ describe('adnuntiusBidAdapter', function () { expect(data.adUnits[0]).to.not.have.property('kv'); }); - it('should skip passing site ext data if missing', function () { + it('should skip passing site ext data if missing', function() { + delete bidderRequests[0].ortb2Imp; const ortb2 = { site: { ext: { @@ -1259,7 +1312,7 @@ describe('adnuntiusBidAdapter', function () { expect(data.adUnits[0]).to.not.have.property('kv'); }); - 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: [{ @@ -1282,22 +1335,22 @@ describe('adnuntiusBidAdapter', function () { }); }); - describe('user privacy', function () { - it('should send GDPR Consent data if gdprApplies', function () { + describe('user privacy', function() { + it('should send GDPR Consent data if gdprApplies', function() { const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + it('should not send GDPR Consent data if gdprApplies equals undefined', function() { const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, ENDPOINT_URL); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config', function() { const ortb2 = { user: { data: [{ @@ -1317,7 +1370,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, 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: [{ @@ -1339,7 +1392,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, 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 @@ -1352,7 +1405,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, ENDPOINT_URL); }); - it('should user in user', function () { + it('should user in user', function() { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -1378,7 +1431,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, `${ENDPOINT_URL_BASE}&userId=${usi}&eids=%5B%7B%22source%22%3A%22a%22%2C%22uids%22%3A%5B%7B%22id%22%3A%22123%22%2C%22atype%22%3A1%7D%5D%7D%2C%7B%22source%22%3A%22b%22%2C%22uids%22%3A%5B%7B%22id%22%3A%22456%22%2C%22atype%22%3A3%2C%22ext%22%3A%7B%22some%22%3A%221%22%7D%7D%5D%7D%5D`); - ortb2.user.id = "ortb2userid" + ortb2.user.id = 'ortb2userid' request = config.runWithBidder('adnuntius', () => spec.buildRequests(req, { bids: req, ortb2: ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); @@ -1404,7 +1457,7 @@ describe('adnuntiusBidAdapter', function () { expectUrlsEqual(request[0].url, `${ENDPOINT_URL_BASE}&userId=different_user_id&eids=` + encodeURIComponent(JSON.stringify(eids))); }); - it('should handle no user specified', function () { + it('should handle no user specified', function() { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -1425,15 +1478,15 @@ describe('adnuntiusBidAdapter', function () { }); }); - describe('user privacy', function () { - it('should send GDPR Consent data if gdprApplies', function () { + describe('user privacy', function() { + it('should send GDPR Consent data if gdprApplies', function() { const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: true, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, ENDPOINT_URL_CONSENT); }); - it('should not send GDPR Consent data if gdprApplies equals undefined', function () { + it('should not send GDPR Consent data if gdprApplies equals undefined', function() { const request = spec.buildRequests(bidderRequests, { gdprConsent: { gdprApplies: undefined, consentString: 'consentString' } }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url'); @@ -1441,8 +1494,8 @@ describe('adnuntiusBidAdapter', function () { }); }); - 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: { @@ -1458,8 +1511,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', @@ -1471,7 +1524,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', @@ -1484,8 +1537,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'] }); @@ -1502,7 +1555,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'], }); @@ -1525,7 +1578,7 @@ describe('adnuntiusBidAdapter', function () { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(5); }); - it('Should allow a minimum of 0 deals.', function () { + it('Should allow a minimum of 0 deals.', function() { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -1548,7 +1601,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: { @@ -1563,7 +1616,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url') expectUrlsEqual(request[0].url, 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: { @@ -1576,7 +1629,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expectUrlsEqual(request[0].url, 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: { @@ -1592,8 +1645,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: { @@ -1668,7 +1721,7 @@ describe('adnuntiusBidAdapter', function () { expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); }); - it('should return valid response when passed valid multiformat server response', function () { + it('should return valid response when passed valid multiformat server response', function() { config.setBidderConfig({ bidders: ['adnuntius'], config: { @@ -1714,7 +1767,7 @@ describe('adnuntiusBidAdapter', function () { expect(interpretedResponse[2].ttl).to.equal(360); }); - 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: [ { @@ -1732,7 +1785,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: [ { @@ -1769,8 +1822,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] @@ -1806,8 +1859,8 @@ describe('adnuntiusBidAdapter', function () { }); }); - describe('Native ads handling', function () { - it('should pass requests on correctly', function () { + describe('Native ads handling', function() { + it('should pass requests on correctly', function() { const request = spec.buildRequests(nativeBidderRequest.bid, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -1815,7 +1868,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"ortb":{"assets":[{"id":1,"required":1,"img":{"type":3,"w":250,"h":250}}]}},"dimensions":[[200,200],[300,300]]}]}'); }); - it('should return valid response when passed valid server response', function () { + it('should return valid response when passed valid server response', function() { const interpretedResponse = spec.interpretResponse(nativeResponse, nativeBidderRequest); const ad = nativeResponse.body.adUnits[0].ads[0] expect(interpretedResponse).to.have.lengthOf(1); @@ -1834,7 +1887,7 @@ describe('adnuntiusBidAdapter', function () { expect(JSON.stringify(interpretedResponse[0].native.ortb)).to.equal('{"link":{"url":"https://whatever.com"},"assets":[{"id":1,"required":1,"img":{"url":"http://something.com/something.png"}}]}'); }); - it('should pass legacy requests on correctly', function () { + it('should pass legacy requests on correctly', function() { const request = spec.buildRequests(legacyNativeBidderRequest.bid, {}); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); @@ -1842,7 +1895,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].data).to.equal('{"adUnits":[{"auId":"0000000000000551","targetId":"adn-0000000000000551","adType":"NATIVE","nativeRequest":{"ortb":{"ver":"1.2","assets":[{"id":0,"required":1,"img":{"type":3,"w":250,"h":250}}],"eventtrackers":[{"event":1,"methods":[1]},{"event":2,"methods":[1]}]}},"dimensions":[[200,200],[300,300]]}]}'); }); - it('should return valid legacy response when passed valid server response', function () { + it('should return valid legacy response when passed valid server response', function() { const interpretedResponse = spec.interpretResponse(nativeResponse, legacyNativeBidderRequest); const ad = nativeResponse.body.adUnits[0].ads[0] expect(interpretedResponse).to.have.lengthOf(1); From 47c39ed5bcd09eb69f5248ca8ad2dc26e8d68c54 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Fri, 13 Mar 2026 12:08:44 -0700 Subject: [PATCH 31/50] Various modules: revert GPT targeting changes (#14591) * Various modules: revert GPT targeting changes * keep intentiq changes --- src/secureCreatives.js | 8 ++---- src/targeting.ts | 3 +-- test/spec/unit/pbjs_api_spec.js | 36 -------------------------- test/spec/unit/secureCreatives_spec.js | 1 - 4 files changed, 3 insertions(+), 45 deletions(-) diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 4c81217b215..f87b3af3ac4 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -210,12 +210,8 @@ export function resizeRemoteCreative({ instl, element, adId, adUnitCode, width, function getDfpElementId(adId) { const slot = window.googletag.pubads().getSlots().find(slot => { - const targetingMap = slot.getConfig('targeting'); - const keys = Object.keys(targetingMap); - - return keys.find(key => { - const values = targetingMap[key]; - return values.includes(adId); + return slot.getTargetingKeys().find(key => { + return slot.getTargeting(key).includes(adId); }); }); return slot ? slot.getSlotElementId() : null; diff --git a/src/targeting.ts b/src/targeting.ts index d035bb1713e..1dcaef1b554 100644 --- a/src/targeting.ts +++ b/src/targeting.ts @@ -322,8 +322,7 @@ export function newTargeting(auctionManager) { targetingSet[targetId][key] = value; }); logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingSet[targetId]); - const targetingMap = Object.assign({}, resetMap, targetingSet[targetId]); - slot.setConfig({ targeting: targetingMap } as any); + slot.updateTargetingFromMap(Object.assign({}, resetMap, targetingSet[targetId])) lock.lock(targetingSet[targetId]); }) }) diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index bbc4d3cc424..90d62cfdea3 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -81,24 +81,6 @@ var Slot = function Slot(elementId, pathId) { return Object.getOwnPropertyNames(this.targeting); }, - getConfig: function getConfig(key) { - if (key === 'targeting') { - return this.targeting; - } - }, - - setConfig: function setConfig(config) { - if (config?.targeting) { - Object.keys(config.targeting).forEach((key) => { - if (config.targeting[key] == null) { - delete this.targeting[key]; - } else { - this.setTargeting(key, config.targeting[key]); - } - }); - } - }, - clearTargeting: function clearTargeting() { this.targeting = {}; return this; @@ -135,24 +117,6 @@ var createSlotArrayScenario2 = function createSlotArrayScenario2() { window.googletag = { _slots: [], _targeting: {}, - getConfig: function (key) { - if (key === 'targeting') { - return this._targeting; - } - }, - setConfig: function (config) { - if (config?.targeting) { - Object.keys(config.targeting).forEach((key) => { - if (config.targeting[key] == null) { - delete this._targeting[key]; - } else { - this._targeting[key] = Array.isArray(config.targeting[key]) - ? config.targeting[key] - : [config.targeting[key]]; - } - }); - } - }, pubads: function () { var self = this; return { diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index d451424772d..a803af27589 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -541,7 +541,6 @@ describe('secureCreatives', () => { value = Array.isArray(value) ? value : [value]; targeting[key] = value; }), - getConfig: sinon.stub().callsFake((key) => key === 'targeting' ? targeting : null), getTargetingKeys: sinon.stub().callsFake(() => Object.keys(targeting)), getTargeting: sinon.stub().callsFake((key) => targeting[key] || []) } From 22ac6546234f7e0721cf7e13449979163423485b Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Sat, 14 Mar 2026 22:03:23 +0000 Subject: [PATCH 32/50] Prebid 11.1.0 release --- metadata/modules/33acrossBidAdapter.json | 2 +- metadata/modules/33acrossIdSystem.json | 2 +- metadata/modules/aceexBidAdapter.json | 2 +- metadata/modules/acuityadsBidAdapter.json | 2 +- metadata/modules/adagioBidAdapter.json | 2 +- metadata/modules/adagioRtdProvider.json | 2 +- metadata/modules/adbroBidAdapter.json | 2 +- metadata/modules/addefendBidAdapter.json | 2 +- metadata/modules/adfBidAdapter.json | 2 +- metadata/modules/adfusionBidAdapter.json | 2 +- metadata/modules/adheseBidAdapter.json | 2 +- metadata/modules/adipoloBidAdapter.json | 2 +- metadata/modules/adkernelAdnBidAdapter.json | 2 +- metadata/modules/adkernelBidAdapter.json | 10 +++++----- metadata/modules/admaticBidAdapter.json | 4 ++-- metadata/modules/admixerBidAdapter.json | 2 +- metadata/modules/admixerIdSystem.json | 2 +- metadata/modules/adnowBidAdapter.json | 2 +- metadata/modules/adnuntiusBidAdapter.json | 2 +- metadata/modules/adnuntiusRtdProvider.json | 2 +- metadata/modules/adoceanBidAdapter.json | 2 +- metadata/modules/adotBidAdapter.json | 2 +- metadata/modules/adponeBidAdapter.json | 2 +- metadata/modules/adqueryBidAdapter.json | 2 +- metadata/modules/adqueryIdSystem.json | 2 +- metadata/modules/adrinoBidAdapter.json | 2 +- metadata/modules/ads_interactiveBidAdapter.json | 2 +- metadata/modules/adtargetBidAdapter.json | 2 +- metadata/modules/adtelligentBidAdapter.json | 6 +++--- metadata/modules/adtelligentIdSystem.json | 2 +- metadata/modules/aduptechBidAdapter.json | 2 +- metadata/modules/adyoulikeBidAdapter.json | 2 +- metadata/modules/airgridRtdProvider.json | 2 +- metadata/modules/alkimiBidAdapter.json | 2 +- metadata/modules/allegroBidAdapter.json | 2 +- metadata/modules/amxBidAdapter.json | 2 +- metadata/modules/amxIdSystem.json | 2 +- metadata/modules/aniviewBidAdapter.json | 2 +- metadata/modules/anonymisedRtdProvider.json | 2 +- metadata/modules/apesterBidAdapter.json | 2 +- metadata/modules/appStockSSPBidAdapter.json | 2 +- metadata/modules/appierBidAdapter.json | 2 +- metadata/modules/appnexusBidAdapter.json | 8 ++++---- metadata/modules/appushBidAdapter.json | 2 +- metadata/modules/apsBidAdapter.json | 2 +- metadata/modules/apstreamBidAdapter.json | 2 +- metadata/modules/audiencerunBidAdapter.json | 2 +- metadata/modules/axisBidAdapter.json | 2 +- metadata/modules/azerionedgeRtdProvider.json | 2 +- metadata/modules/beachfrontBidAdapter.json | 2 +- metadata/modules/beopBidAdapter.json | 2 +- metadata/modules/betweenBidAdapter.json | 2 +- metadata/modules/bidmaticBidAdapter.json | 2 +- metadata/modules/bidtheatreBidAdapter.json | 2 +- metadata/modules/bliinkBidAdapter.json | 2 +- metadata/modules/blockthroughBidAdapter.json | 2 +- metadata/modules/blueBidAdapter.json | 2 +- metadata/modules/bmsBidAdapter.json | 2 +- metadata/modules/boldwinBidAdapter.json | 2 +- metadata/modules/bridBidAdapter.json | 2 +- metadata/modules/browsiBidAdapter.json | 2 +- metadata/modules/bucksenseBidAdapter.json | 2 +- metadata/modules/carodaBidAdapter.json | 2 +- metadata/modules/categoryTranslation.json | 2 +- metadata/modules/ceeIdSystem.json | 2 +- metadata/modules/chromeAiRtdProvider.json | 2 +- metadata/modules/clickioBidAdapter.json | 2 +- metadata/modules/compassBidAdapter.json | 2 +- metadata/modules/conceptxBidAdapter.json | 2 +- metadata/modules/connatixBidAdapter.json | 2 +- metadata/modules/connectIdSystem.json | 2 +- metadata/modules/connectadBidAdapter.json | 2 +- metadata/modules/contentexchangeBidAdapter.json | 2 +- metadata/modules/conversantBidAdapter.json | 2 +- metadata/modules/copper6sspBidAdapter.json | 2 +- metadata/modules/cpmstarBidAdapter.json | 2 +- metadata/modules/criteoBidAdapter.json | 2 +- metadata/modules/criteoIdSystem.json | 2 +- metadata/modules/cwireBidAdapter.json | 2 +- metadata/modules/czechAdIdSystem.json | 2 +- metadata/modules/dailymotionBidAdapter.json | 2 +- metadata/modules/debugging.json | 2 +- metadata/modules/deepintentBidAdapter.json | 2 +- metadata/modules/defineMediaBidAdapter.json | 2 +- metadata/modules/deltaprojectsBidAdapter.json | 2 +- metadata/modules/dianomiBidAdapter.json | 2 +- metadata/modules/digitalMatterBidAdapter.json | 2 +- metadata/modules/distroscaleBidAdapter.json | 2 +- metadata/modules/docereeAdManagerBidAdapter.json | 2 +- metadata/modules/docereeBidAdapter.json | 2 +- metadata/modules/dspxBidAdapter.json | 2 +- metadata/modules/e_volutionBidAdapter.json | 2 +- metadata/modules/edge226BidAdapter.json | 2 +- metadata/modules/empowerBidAdapter.json | 2 +- metadata/modules/equativBidAdapter.json | 2 +- metadata/modules/eskimiBidAdapter.json | 2 +- metadata/modules/etargetBidAdapter.json | 2 +- metadata/modules/euidIdSystem.json | 2 +- metadata/modules/exadsBidAdapter.json | 2 +- metadata/modules/feedadBidAdapter.json | 2 +- metadata/modules/fwsspBidAdapter.json | 2 +- metadata/modules/gamoshiBidAdapter.json | 2 +- metadata/modules/gemiusIdSystem.json | 2 +- metadata/modules/glomexBidAdapter.json | 2 +- metadata/modules/goldbachBidAdapter.json | 2 +- metadata/modules/gridBidAdapter.json | 2 +- metadata/modules/gumgumBidAdapter.json | 2 +- metadata/modules/hadronIdSystem.json | 2 +- metadata/modules/hadronRtdProvider.json | 2 +- metadata/modules/harionBidAdapter.json | 2 +- metadata/modules/holidBidAdapter.json | 2 +- metadata/modules/hybridBidAdapter.json | 2 +- metadata/modules/id5IdSystem.json | 2 +- metadata/modules/identityLinkIdSystem.json | 2 +- metadata/modules/illuminBidAdapter.json | 2 +- metadata/modules/impactifyBidAdapter.json | 2 +- metadata/modules/improvedigitalBidAdapter.json | 2 +- metadata/modules/inmobiBidAdapter.json | 2 +- metadata/modules/insticatorBidAdapter.json | 2 +- metadata/modules/insuradsBidAdapter.json | 2 +- metadata/modules/intentIqIdSystem.json | 2 +- metadata/modules/invibesBidAdapter.json | 2 +- metadata/modules/ipromBidAdapter.json | 2 +- metadata/modules/ixBidAdapter.json | 2 +- metadata/modules/justIdSystem.json | 2 +- metadata/modules/justpremiumBidAdapter.json | 2 +- metadata/modules/jwplayerBidAdapter.json | 2 +- metadata/modules/kargoBidAdapter.json | 2 +- metadata/modules/kueezRtbBidAdapter.json | 2 +- metadata/modules/limelightDigitalBidAdapter.json | 4 ++-- metadata/modules/liveIntentIdSystem.json | 2 +- metadata/modules/liveIntentRtdProvider.json | 2 +- metadata/modules/livewrappedBidAdapter.json | 2 +- metadata/modules/loopmeBidAdapter.json | 2 +- metadata/modules/lotamePanoramaIdSystem.json | 2 +- metadata/modules/luponmediaBidAdapter.json | 2 +- metadata/modules/madvertiseBidAdapter.json | 2 +- metadata/modules/magniteBidAdapter.json | 2 +- metadata/modules/marsmediaBidAdapter.json | 2 +- metadata/modules/mediaConsortiumBidAdapter.json | 2 +- metadata/modules/mediaforceBidAdapter.json | 2 +- metadata/modules/mediafuseBidAdapter.json | 2 +- metadata/modules/mediagoBidAdapter.json | 2 +- metadata/modules/mediakeysBidAdapter.json | 2 +- metadata/modules/medianetBidAdapter.json | 4 ++-- metadata/modules/mediasquareBidAdapter.json | 2 +- metadata/modules/mgidBidAdapter.json | 2 +- metadata/modules/mgidRtdProvider.json | 2 +- metadata/modules/mgidXBidAdapter.json | 2 +- metadata/modules/minutemediaBidAdapter.json | 2 +- metadata/modules/missenaBidAdapter.json | 2 +- metadata/modules/mobianRtdProvider.json | 2 +- metadata/modules/mobkoiBidAdapter.json | 2 +- metadata/modules/mobkoiIdSystem.json | 2 +- metadata/modules/msftBidAdapter.json | 2 +- metadata/modules/nativeryBidAdapter.json | 2 +- metadata/modules/nativoBidAdapter.json | 2 +- metadata/modules/newspassidBidAdapter.json | 2 +- metadata/modules/nextMillenniumBidAdapter.json | 2 +- metadata/modules/nextrollBidAdapter.json | 2 +- metadata/modules/nexx360BidAdapter.json | 12 ++++++------ metadata/modules/nobidBidAdapter.json | 2 +- metadata/modules/nodalsAiRtdProvider.json | 2 +- metadata/modules/novatiqIdSystem.json | 2 +- metadata/modules/oguryBidAdapter.json | 2 +- metadata/modules/omnidexBidAdapter.json | 2 +- metadata/modules/omsBidAdapter.json | 2 +- metadata/modules/onetagBidAdapter.json | 2 +- metadata/modules/openwebBidAdapter.json | 2 +- metadata/modules/openxBidAdapter.json | 2 +- metadata/modules/operaadsBidAdapter.json | 2 +- metadata/modules/optidigitalBidAdapter.json | 2 +- metadata/modules/optoutBidAdapter.json | 2 +- metadata/modules/orbidderBidAdapter.json | 2 +- metadata/modules/outbrainBidAdapter.json | 2 +- metadata/modules/ozoneBidAdapter.json | 2 +- metadata/modules/pairIdSystem.json | 2 +- metadata/modules/panxoBidAdapter.json | 2 +- metadata/modules/performaxBidAdapter.json | 2 +- .../modules/permutiveIdentityManagerIdSystem.json | 2 +- metadata/modules/permutiveRtdProvider.json | 2 +- metadata/modules/pixfutureBidAdapter.json | 2 +- metadata/modules/playdigoBidAdapter.json | 2 +- metadata/modules/prebid-core.json | 4 ++-- metadata/modules/precisoBidAdapter.json | 2 +- metadata/modules/prismaBidAdapter.json | 2 +- metadata/modules/programmaticXBidAdapter.json | 2 +- metadata/modules/proxistoreBidAdapter.json | 2 +- metadata/modules/publinkIdSystem.json | 2 +- metadata/modules/pubmaticBidAdapter.json | 2 +- metadata/modules/pubmaticIdSystem.json | 2 +- metadata/modules/pubstackBidAdapter.json | 2 +- metadata/modules/pulsepointBidAdapter.json | 2 +- metadata/modules/r2b2BidAdapter.json | 2 +- metadata/modules/readpeakBidAdapter.json | 2 +- metadata/modules/relayBidAdapter.json | 2 +- metadata/modules/relevantdigitalBidAdapter.json | 2 +- metadata/modules/resetdigitalBidAdapter.json | 2 +- metadata/modules/responsiveAdsBidAdapter.json | 2 +- metadata/modules/revcontentBidAdapter.json | 2 +- metadata/modules/revnewBidAdapter.json | 2 +- metadata/modules/rhythmoneBidAdapter.json | 2 +- metadata/modules/richaudienceBidAdapter.json | 2 +- metadata/modules/riseBidAdapter.json | 4 ++-- metadata/modules/rixengineBidAdapter.json | 2 +- metadata/modules/rtbhouseBidAdapter.json | 2 +- metadata/modules/rubiconBidAdapter.json | 2 +- metadata/modules/scaliburBidAdapter.json | 2 +- metadata/modules/screencoreBidAdapter.json | 2 +- metadata/modules/seedingAllianceBidAdapter.json | 2 +- metadata/modules/seedtagBidAdapter.json | 2 +- metadata/modules/semantiqRtdProvider.json | 2 +- metadata/modules/sevioBidAdapter.json | 2 +- metadata/modules/sharedIdSystem.json | 2 +- metadata/modules/sharethroughBidAdapter.json | 2 +- metadata/modules/showheroes-bsBidAdapter.json | 2 +- metadata/modules/silvermobBidAdapter.json | 2 +- metadata/modules/sirdataRtdProvider.json | 2 +- metadata/modules/smaatoBidAdapter.json | 2 +- metadata/modules/smartadserverBidAdapter.json | 2 +- metadata/modules/smartxBidAdapter.json | 2 +- metadata/modules/smartyadsBidAdapter.json | 2 +- metadata/modules/smilewantedBidAdapter.json | 2 +- metadata/modules/snigelBidAdapter.json | 2 +- metadata/modules/sonaradsBidAdapter.json | 2 +- metadata/modules/sonobiBidAdapter.json | 2 +- metadata/modules/sovrnBidAdapter.json | 2 +- metadata/modules/sparteoBidAdapter.json | 2 +- metadata/modules/ssmasBidAdapter.json | 2 +- metadata/modules/sspBCBidAdapter.json | 2 +- metadata/modules/stackadaptBidAdapter.json | 2 +- metadata/modules/startioBidAdapter.json | 2 +- metadata/modules/stroeerCoreBidAdapter.json | 2 +- metadata/modules/stvBidAdapter.json | 2 +- metadata/modules/sublimeBidAdapter.json | 2 +- metadata/modules/taboolaBidAdapter.json | 2 +- metadata/modules/taboolaIdSystem.json | 2 +- metadata/modules/tadvertisingBidAdapter.json | 2 +- metadata/modules/tappxBidAdapter.json | 2 +- metadata/modules/targetVideoBidAdapter.json | 2 +- metadata/modules/teadsBidAdapter.json | 2 +- metadata/modules/teadsIdSystem.json | 2 +- metadata/modules/tealBidAdapter.json | 2 +- metadata/modules/tncIdSystem.json | 2 +- metadata/modules/topicsFpdModule.json | 2 +- metadata/modules/toponBidAdapter.json | 2 +- metadata/modules/tripleliftBidAdapter.json | 2 +- metadata/modules/ttdBidAdapter.json | 2 +- metadata/modules/twistDigitalBidAdapter.json | 2 +- metadata/modules/underdogmediaBidAdapter.json | 2 +- metadata/modules/undertoneBidAdapter.json | 2 +- metadata/modules/unifiedIdSystem.json | 2 +- metadata/modules/unrulyBidAdapter.json | 2 +- metadata/modules/userId.json | 2 +- metadata/modules/utiqIdSystem.json | 2 +- metadata/modules/utiqMtpIdSystem.json | 2 +- metadata/modules/validationFpdModule.json | 2 +- metadata/modules/valuadBidAdapter.json | 2 +- metadata/modules/vidazooBidAdapter.json | 2 +- metadata/modules/vidoomyBidAdapter.json | 2 +- metadata/modules/viouslyBidAdapter.json | 2 +- metadata/modules/visxBidAdapter.json | 2 +- metadata/modules/vlybyBidAdapter.json | 2 +- metadata/modules/voxBidAdapter.json | 2 +- metadata/modules/vrtcalBidAdapter.json | 2 +- metadata/modules/vuukleBidAdapter.json | 2 +- metadata/modules/weboramaRtdProvider.json | 2 +- metadata/modules/welectBidAdapter.json | 2 +- metadata/modules/yahooAdsBidAdapter.json | 2 +- metadata/modules/yaleoBidAdapter.json | 2 +- metadata/modules/yieldlabBidAdapter.json | 2 +- metadata/modules/yieldloveBidAdapter.json | 2 +- metadata/modules/yieldmoBidAdapter.json | 2 +- metadata/modules/zeotapIdPlusIdSystem.json | 2 +- metadata/modules/zeta_globalBidAdapter.json | 2 +- metadata/modules/zeta_global_sspBidAdapter.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 278 files changed, 298 insertions(+), 298 deletions(-) diff --git a/metadata/modules/33acrossBidAdapter.json b/metadata/modules/33acrossBidAdapter.json index 1c632537642..823551fb1f5 100644 --- a/metadata/modules/33acrossBidAdapter.json +++ b/metadata/modules/33acrossBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-12T23:06:53.494Z", + "timestamp": "2026-03-14T22:01:05.187Z", "disclosures": [] } }, diff --git a/metadata/modules/33acrossIdSystem.json b/metadata/modules/33acrossIdSystem.json index 60e8e6b862c..2c4f869236b 100644 --- a/metadata/modules/33acrossIdSystem.json +++ b/metadata/modules/33acrossIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://platform.33across.com/disclosures.json": { - "timestamp": "2026-03-12T23:06:53.599Z", + "timestamp": "2026-03-14T22:01:05.286Z", "disclosures": [] } }, diff --git a/metadata/modules/aceexBidAdapter.json b/metadata/modules/aceexBidAdapter.json index 8db5f6edf69..5e843cac764 100644 --- a/metadata/modules/aceexBidAdapter.json +++ b/metadata/modules/aceexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://aceex.io/tcf.json": { - "timestamp": "2026-03-12T23:06:53.601Z", + "timestamp": "2026-03-14T22:01:05.288Z", "disclosures": [] } }, diff --git a/metadata/modules/acuityadsBidAdapter.json b/metadata/modules/acuityadsBidAdapter.json index a94432ea059..0aaec8d2cf8 100644 --- a/metadata/modules/acuityadsBidAdapter.json +++ b/metadata/modules/acuityadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.acuityads.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:06:53.671Z", + "timestamp": "2026-03-14T22:01:05.335Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioBidAdapter.json b/metadata/modules/adagioBidAdapter.json index 0c193144608..5281067bde6 100644 --- a/metadata/modules/adagioBidAdapter.json +++ b/metadata/modules/adagioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:06:53.712Z", + "timestamp": "2026-03-14T22:01:05.367Z", "disclosures": [] } }, diff --git a/metadata/modules/adagioRtdProvider.json b/metadata/modules/adagioRtdProvider.json index 52ce021decc..d0a8381c614 100644 --- a/metadata/modules/adagioRtdProvider.json +++ b/metadata/modules/adagioRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adagio.io/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:06:53.808Z", + "timestamp": "2026-03-14T22:01:05.416Z", "disclosures": [] } }, diff --git a/metadata/modules/adbroBidAdapter.json b/metadata/modules/adbroBidAdapter.json index 5808a21d09e..f1f21fc2a1f 100644 --- a/metadata/modules/adbroBidAdapter.json +++ b/metadata/modules/adbroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tag.adbro.me/privacy/devicestorage.json": { - "timestamp": "2026-03-12T23:06:53.808Z", + "timestamp": "2026-03-14T22:01:05.416Z", "disclosures": [] } }, diff --git a/metadata/modules/addefendBidAdapter.json b/metadata/modules/addefendBidAdapter.json index a109b242581..1008ff63422 100644 --- a/metadata/modules/addefendBidAdapter.json +++ b/metadata/modules/addefendBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.addefend.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:54.626Z", + "timestamp": "2026-03-14T22:01:06.169Z", "disclosures": [] } }, diff --git a/metadata/modules/adfBidAdapter.json b/metadata/modules/adfBidAdapter.json index ce395bc4380..2b0cbc54873 100644 --- a/metadata/modules/adfBidAdapter.json +++ b/metadata/modules/adfBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://site.adform.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T23:06:55.310Z", + "timestamp": "2026-03-14T22:01:06.841Z", "disclosures": [] } }, diff --git a/metadata/modules/adfusionBidAdapter.json b/metadata/modules/adfusionBidAdapter.json index 226f4fa1177..2a22045409a 100644 --- a/metadata/modules/adfusionBidAdapter.json +++ b/metadata/modules/adfusionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spicyrtb.com/static/iab-disclosure.json": { - "timestamp": "2026-03-12T23:06:55.310Z", + "timestamp": "2026-03-14T22:01:06.841Z", "disclosures": [] } }, diff --git a/metadata/modules/adheseBidAdapter.json b/metadata/modules/adheseBidAdapter.json index 71485261a0d..4850025b7c1 100644 --- a/metadata/modules/adheseBidAdapter.json +++ b/metadata/modules/adheseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adhese.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:55.582Z", + "timestamp": "2026-03-14T22:01:07.200Z", "disclosures": [] } }, diff --git a/metadata/modules/adipoloBidAdapter.json b/metadata/modules/adipoloBidAdapter.json index 5c5722b49a8..e3c0253bd0a 100644 --- a/metadata/modules/adipoloBidAdapter.json +++ b/metadata/modules/adipoloBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adipolo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:06:55.845Z", + "timestamp": "2026-03-14T22:01:07.460Z", "disclosures": [] } }, diff --git a/metadata/modules/adkernelAdnBidAdapter.json b/metadata/modules/adkernelAdnBidAdapter.json index 0951e076316..d4eff406316 100644 --- a/metadata/modules/adkernelAdnBidAdapter.json +++ b/metadata/modules/adkernelAdnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:55.988Z", + "timestamp": "2026-03-14T22:01:07.594Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", diff --git a/metadata/modules/adkernelBidAdapter.json b/metadata/modules/adkernelBidAdapter.json index 844009c1ed8..9380f7b78fe 100644 --- a/metadata/modules/adkernelBidAdapter.json +++ b/metadata/modules/adkernelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.adkernel.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:56.128Z", + "timestamp": "2026-03-14T22:01:07.640Z", "disclosures": [ { "identifier": "adk_rtb_conv_id", @@ -17,19 +17,19 @@ ] }, "https://data.converge-digital.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:56.128Z", + "timestamp": "2026-03-14T22:01:07.640Z", "disclosures": [] }, "https://spinx.biz/tcf-spinx.json": { - "timestamp": "2026-03-12T23:06:56.172Z", + "timestamp": "2026-03-14T22:01:07.684Z", "disclosures": [] }, "https://gdpr.memob.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:56.902Z", + "timestamp": "2026-03-14T22:01:08.426Z", "disclosures": [] }, "https://appmonsta.ai/DeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:06:56.923Z", + "timestamp": "2026-03-14T22:01:08.449Z", "disclosures": [] } }, diff --git a/metadata/modules/admaticBidAdapter.json b/metadata/modules/admaticBidAdapter.json index e147ca71841..8e3f8af8ef7 100644 --- a/metadata/modules/admaticBidAdapter.json +++ b/metadata/modules/admaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.admatic.de/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T23:06:57.505Z", + "timestamp": "2026-03-14T22:01:08.751Z", "disclosures": [ { "identifier": "px_pbjs", @@ -12,7 +12,7 @@ ] }, "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:57.505Z", + "timestamp": "2026-03-14T22:01:08.751Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/admixerBidAdapter.json b/metadata/modules/admixerBidAdapter.json index 28b3d0ebd86..04359e7862e 100644 --- a/metadata/modules/admixerBidAdapter.json +++ b/metadata/modules/admixerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-12T23:06:57.505Z", + "timestamp": "2026-03-14T22:01:08.752Z", "disclosures": [] } }, diff --git a/metadata/modules/admixerIdSystem.json b/metadata/modules/admixerIdSystem.json index 43ccf69c9e9..901e303a8b7 100644 --- a/metadata/modules/admixerIdSystem.json +++ b/metadata/modules/admixerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admixer.com/tcf.json": { - "timestamp": "2026-03-12T23:06:57.936Z", + "timestamp": "2026-03-14T22:01:09.129Z", "disclosures": [] } }, diff --git a/metadata/modules/adnowBidAdapter.json b/metadata/modules/adnowBidAdapter.json index ccd488d4c0e..0d5b8e7a5da 100644 --- a/metadata/modules/adnowBidAdapter.json +++ b/metadata/modules/adnowBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adnow.com/vdsod.json": { - "timestamp": "2026-03-12T23:06:57.937Z", + "timestamp": "2026-03-14T22:01:09.130Z", "disclosures": [ { "identifier": "SC_unique_*", diff --git a/metadata/modules/adnuntiusBidAdapter.json b/metadata/modules/adnuntiusBidAdapter.json index 4407be983f7..7853de97ee5 100644 --- a/metadata/modules/adnuntiusBidAdapter.json +++ b/metadata/modules/adnuntiusBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:58.190Z", + "timestamp": "2026-03-14T22:01:09.365Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adnuntiusRtdProvider.json b/metadata/modules/adnuntiusRtdProvider.json index 0b8bfe9f47a..325673348e5 100644 --- a/metadata/modules/adnuntiusRtdProvider.json +++ b/metadata/modules/adnuntiusRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.adnuntius.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:58.526Z", + "timestamp": "2026-03-14T22:01:09.679Z", "disclosures": [ { "identifier": "adn.metaData", diff --git a/metadata/modules/adoceanBidAdapter.json b/metadata/modules/adoceanBidAdapter.json index 0169ff6d3ea..cf3f853a8cf 100644 --- a/metadata/modules/adoceanBidAdapter.json +++ b/metadata/modules/adoceanBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-12T23:06:58.526Z", + "timestamp": "2026-03-14T22:01:09.680Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/adotBidAdapter.json b/metadata/modules/adotBidAdapter.json index a3830e0e97f..16d18a7e5ee 100644 --- a/metadata/modules/adotBidAdapter.json +++ b/metadata/modules/adotBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.adotmob.com/tcf/tcf.json": { - "timestamp": "2026-03-12T23:06:58.993Z", + "timestamp": "2026-03-14T22:01:10.148Z", "disclosures": [] } }, diff --git a/metadata/modules/adponeBidAdapter.json b/metadata/modules/adponeBidAdapter.json index 3de63e96da5..1c2087b3fd5 100644 --- a/metadata/modules/adponeBidAdapter.json +++ b/metadata/modules/adponeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.adpone.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:59.037Z", + "timestamp": "2026-03-14T22:01:10.184Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryBidAdapter.json b/metadata/modules/adqueryBidAdapter.json index 605e58304ac..a0e90191b29 100644 --- a/metadata/modules/adqueryBidAdapter.json +++ b/metadata/modules/adqueryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-12T23:06:59.058Z", + "timestamp": "2026-03-14T22:01:10.212Z", "disclosures": [] } }, diff --git a/metadata/modules/adqueryIdSystem.json b/metadata/modules/adqueryIdSystem.json index 300397ce37d..927f651bb9c 100644 --- a/metadata/modules/adqueryIdSystem.json +++ b/metadata/modules/adqueryIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.adquery.io/tcf/adQuery.json": { - "timestamp": "2026-03-12T23:06:59.412Z", + "timestamp": "2026-03-14T22:01:10.553Z", "disclosures": [] } }, diff --git a/metadata/modules/adrinoBidAdapter.json b/metadata/modules/adrinoBidAdapter.json index 6c0cfae110e..be0fbf1c17b 100644 --- a/metadata/modules/adrinoBidAdapter.json +++ b/metadata/modules/adrinoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.adrino.cloud/iab/device-storage.json": { - "timestamp": "2026-03-12T23:06:59.412Z", + "timestamp": "2026-03-14T22:01:10.553Z", "disclosures": [] } }, diff --git a/metadata/modules/ads_interactiveBidAdapter.json b/metadata/modules/ads_interactiveBidAdapter.json index 1648b192b69..5770a1dbd48 100644 --- a/metadata/modules/ads_interactiveBidAdapter.json +++ b/metadata/modules/ads_interactiveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adsinteractive.com/vendor.json": { - "timestamp": "2026-03-12T23:06:59.485Z", + "timestamp": "2026-03-14T22:01:10.635Z", "disclosures": [] } }, diff --git a/metadata/modules/adtargetBidAdapter.json b/metadata/modules/adtargetBidAdapter.json index 958000a562f..9d0b6c4de36 100644 --- a/metadata/modules/adtargetBidAdapter.json +++ b/metadata/modules/adtargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtarget.com.tr/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:59.773Z", + "timestamp": "2026-03-14T22:01:10.923Z", "disclosures": [ { "identifier": "adt_pbjs", diff --git a/metadata/modules/adtelligentBidAdapter.json b/metadata/modules/adtelligentBidAdapter.json index 42e44bb8c21..e6ddfa5f318 100644 --- a/metadata/modules/adtelligentBidAdapter.json +++ b/metadata/modules/adtelligentBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:06:59.773Z", + "timestamp": "2026-03-14T22:01:10.924Z", "disclosures": [] }, "https://www.selectmedia.asia/gdpr/devicestorage.json": { - "timestamp": "2026-03-02T14:45:18.568Z", + "timestamp": "2026-03-14T22:01:10.941Z", "disclosures": [ { "identifier": "waterFallCacheAnsKey_*", @@ -81,7 +81,7 @@ ] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:01.416Z", + "timestamp": "2026-03-14T22:01:11.077Z", "disclosures": [] } }, diff --git a/metadata/modules/adtelligentIdSystem.json b/metadata/modules/adtelligentIdSystem.json index abd10f50638..483d850de86 100644 --- a/metadata/modules/adtelligentIdSystem.json +++ b/metadata/modules/adtelligentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adtelligent.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:01.500Z", + "timestamp": "2026-03-14T22:01:11.152Z", "disclosures": [] } }, diff --git a/metadata/modules/aduptechBidAdapter.json b/metadata/modules/aduptechBidAdapter.json index ebd9ec15438..c2b05a5ca7c 100644 --- a/metadata/modules/aduptechBidAdapter.json +++ b/metadata/modules/aduptechBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.d.adup-tech.com/gdpr/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:01.500Z", + "timestamp": "2026-03-14T22:01:11.155Z", "disclosures": [] } }, diff --git a/metadata/modules/adyoulikeBidAdapter.json b/metadata/modules/adyoulikeBidAdapter.json index 42d7c6320a4..f7ab278cf36 100644 --- a/metadata/modules/adyoulikeBidAdapter.json +++ b/metadata/modules/adyoulikeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adyoulike.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T23:08:01.520Z", + "timestamp": "2026-03-14T22:01:11.376Z", "disclosures": [] } }, diff --git a/metadata/modules/airgridRtdProvider.json b/metadata/modules/airgridRtdProvider.json index 1f45b7ea126..38287067545 100644 --- a/metadata/modules/airgridRtdProvider.json +++ b/metadata/modules/airgridRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.wearemiq.com/privacy-and-compliance/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:01.960Z", + "timestamp": "2026-03-14T22:01:11.841Z", "disclosures": [] } }, diff --git a/metadata/modules/alkimiBidAdapter.json b/metadata/modules/alkimiBidAdapter.json index 447f221d6f5..3b79fd3bc8d 100644 --- a/metadata/modules/alkimiBidAdapter.json +++ b/metadata/modules/alkimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d1xjh92lb8fey3.cloudfront.net/tcf/alkimi_exchange_tcf.json": { - "timestamp": "2026-03-12T23:08:01.984Z", + "timestamp": "2026-03-14T22:01:11.877Z", "disclosures": [] } }, diff --git a/metadata/modules/allegroBidAdapter.json b/metadata/modules/allegroBidAdapter.json index 245d497dc9c..e2061f1c026 100644 --- a/metadata/modules/allegroBidAdapter.json +++ b/metadata/modules/allegroBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.allegrostatic.com/dsp-tcf-external/device-storage.json": { - "timestamp": "2026-03-12T23:08:02.265Z", + "timestamp": "2026-03-14T22:01:12.178Z", "disclosures": [] } }, diff --git a/metadata/modules/amxBidAdapter.json b/metadata/modules/amxBidAdapter.json index 9c78f63d985..a21f00f877a 100644 --- a/metadata/modules/amxBidAdapter.json +++ b/metadata/modules/amxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-12T23:08:02.720Z", + "timestamp": "2026-03-14T22:01:12.556Z", "disclosures": [ { "identifier": "amuid2", diff --git a/metadata/modules/amxIdSystem.json b/metadata/modules/amxIdSystem.json index 382757827e6..a6ee4857873 100644 --- a/metadata/modules/amxIdSystem.json +++ b/metadata/modules/amxIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.a-mo.net/tcf/device-storage.json": { - "timestamp": "2026-03-12T23:08:02.761Z", + "timestamp": "2026-03-14T22:01:12.616Z", "disclosures": [ { "identifier": "amuid2", diff --git a/metadata/modules/aniviewBidAdapter.json b/metadata/modules/aniviewBidAdapter.json index 6009cadb356..f0657a14619 100644 --- a/metadata/modules/aniviewBidAdapter.json +++ b/metadata/modules/aniviewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.aniview.com/gdpr/gdpr.json": { - "timestamp": "2026-03-12T23:08:02.761Z", + "timestamp": "2026-03-14T22:01:12.617Z", "disclosures": [ { "identifier": "av_*", diff --git a/metadata/modules/anonymisedRtdProvider.json b/metadata/modules/anonymisedRtdProvider.json index 185498f7564..1bf8a30eb7d 100644 --- a/metadata/modules/anonymisedRtdProvider.json +++ b/metadata/modules/anonymisedRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn1.anonymised.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:02.902Z", + "timestamp": "2026-03-14T22:01:12.709Z", "disclosures": [ { "identifier": "oidc.user*", diff --git a/metadata/modules/apesterBidAdapter.json b/metadata/modules/apesterBidAdapter.json index fca85e6881b..e8431ff497d 100644 --- a/metadata/modules/apesterBidAdapter.json +++ b/metadata/modules/apesterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apester.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:02.966Z", + "timestamp": "2026-03-14T22:01:14.502Z", "disclosures": [] } }, diff --git a/metadata/modules/appStockSSPBidAdapter.json b/metadata/modules/appStockSSPBidAdapter.json index 4a1476147e9..d4af6014060 100644 --- a/metadata/modules/appStockSSPBidAdapter.json +++ b/metadata/modules/appStockSSPBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://app-stock.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:03.098Z", + "timestamp": "2026-03-14T22:01:14.601Z", "disclosures": [] } }, diff --git a/metadata/modules/appierBidAdapter.json b/metadata/modules/appierBidAdapter.json index c978541c082..0cb5c1dc07e 100644 --- a/metadata/modules/appierBidAdapter.json +++ b/metadata/modules/appierBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.appier.com/deviceStorage2025.json": { - "timestamp": "2026-03-12T23:08:03.121Z", + "timestamp": "2026-03-14T22:01:14.628Z", "disclosures": [ { "identifier": "_atrk_ssid", diff --git a/metadata/modules/appnexusBidAdapter.json b/metadata/modules/appnexusBidAdapter.json index 0f3d645b194..c1dadaed616 100644 --- a/metadata/modules/appnexusBidAdapter.json +++ b/metadata/modules/appnexusBidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:03.673Z", + "timestamp": "2026-03-14T22:01:16.114Z", "disclosures": [] }, "https://beintoo-support.b-cdn.net/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:03.187Z", + "timestamp": "2026-03-14T22:01:14.730Z", "disclosures": [] }, "https://projectagora.net/1032_deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:03.200Z", + "timestamp": "2026-03-14T22:01:15.705Z", "disclosures": [] }, "https://adzymic.com/tcf.json": { - "timestamp": "2026-03-12T23:08:03.673Z", + "timestamp": "2026-03-14T22:01:16.114Z", "disclosures": [] } }, diff --git a/metadata/modules/appushBidAdapter.json b/metadata/modules/appushBidAdapter.json index 771db595f7f..01e0ebcf3a0 100644 --- a/metadata/modules/appushBidAdapter.json +++ b/metadata/modules/appushBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.thebiding.com/disclosures.json": { - "timestamp": "2026-03-12T23:08:03.701Z", + "timestamp": "2026-03-14T22:01:16.140Z", "disclosures": [] } }, diff --git a/metadata/modules/apsBidAdapter.json b/metadata/modules/apsBidAdapter.json index 6726d7e847b..416ffe8696b 100644 --- a/metadata/modules/apsBidAdapter.json +++ b/metadata/modules/apsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://m.media-amazon.com/images/G/01/adprefs/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:03.768Z", + "timestamp": "2026-03-14T22:01:16.210Z", "disclosures": [ { "identifier": "vendor-id", diff --git a/metadata/modules/apstreamBidAdapter.json b/metadata/modules/apstreamBidAdapter.json index e3920836179..e02f46fe432 100644 --- a/metadata/modules/apstreamBidAdapter.json +++ b/metadata/modules/apstreamBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sak.userreport.com/tcf.json": { - "timestamp": "2026-03-12T23:08:03.784Z", + "timestamp": "2026-03-14T22:01:16.229Z", "disclosures": [ { "identifier": "apr_dsu", diff --git a/metadata/modules/audiencerunBidAdapter.json b/metadata/modules/audiencerunBidAdapter.json index b27007c3b8c..12cd862dcef 100644 --- a/metadata/modules/audiencerunBidAdapter.json +++ b/metadata/modules/audiencerunBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.audiencerun.com/tcf.json": { - "timestamp": "2026-03-12T23:08:03.796Z", + "timestamp": "2026-03-14T22:01:16.249Z", "disclosures": [] } }, diff --git a/metadata/modules/axisBidAdapter.json b/metadata/modules/axisBidAdapter.json index ac71c567d26..7b4cf74d42c 100644 --- a/metadata/modules/axisBidAdapter.json +++ b/metadata/modules/axisBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://axis-marketplace.com/tcf.json": { - "timestamp": "2026-03-12T23:08:03.834Z", + "timestamp": "2026-03-14T22:01:16.297Z", "disclosures": [] } }, diff --git a/metadata/modules/azerionedgeRtdProvider.json b/metadata/modules/azerionedgeRtdProvider.json index 299180fb5b1..3c3f592335d 100644 --- a/metadata/modules/azerionedgeRtdProvider.json +++ b/metadata/modules/azerionedgeRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-12T23:08:03.875Z", + "timestamp": "2026-03-14T22:01:16.344Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/beachfrontBidAdapter.json b/metadata/modules/beachfrontBidAdapter.json index 233c2264fec..8cbc30e6581 100644 --- a/metadata/modules/beachfrontBidAdapter.json +++ b/metadata/modules/beachfrontBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-12T23:08:04.133Z", + "timestamp": "2026-03-14T22:01:16.369Z", "disclosures": [] } }, diff --git a/metadata/modules/beopBidAdapter.json b/metadata/modules/beopBidAdapter.json index 8d6cb757d03..49afa1d7fcf 100644 --- a/metadata/modules/beopBidAdapter.json +++ b/metadata/modules/beopBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://beop.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:04.161Z", + "timestamp": "2026-03-14T22:01:16.490Z", "disclosures": [] } }, diff --git a/metadata/modules/betweenBidAdapter.json b/metadata/modules/betweenBidAdapter.json index b962ec40078..18b2eb8ccf6 100644 --- a/metadata/modules/betweenBidAdapter.json +++ b/metadata/modules/betweenBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.betweenx.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:04.274Z", + "timestamp": "2026-03-14T22:01:16.594Z", "disclosures": [] } }, diff --git a/metadata/modules/bidmaticBidAdapter.json b/metadata/modules/bidmaticBidAdapter.json index 31e722c93aa..1c8a8efcbe5 100644 --- a/metadata/modules/bidmaticBidAdapter.json +++ b/metadata/modules/bidmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bidmatic.io/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:05.063Z", + "timestamp": "2026-03-14T22:01:49.175Z", "disclosures": [] } }, diff --git a/metadata/modules/bidtheatreBidAdapter.json b/metadata/modules/bidtheatreBidAdapter.json index 84a33bc0755..ca0e7996ca6 100644 --- a/metadata/modules/bidtheatreBidAdapter.json +++ b/metadata/modules/bidtheatreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.bidtheatre.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:05.109Z", + "timestamp": "2026-03-14T22:01:49.225Z", "disclosures": [] } }, diff --git a/metadata/modules/bliinkBidAdapter.json b/metadata/modules/bliinkBidAdapter.json index 535b23dc168..5ec1afdc7b5 100644 --- a/metadata/modules/bliinkBidAdapter.json +++ b/metadata/modules/bliinkBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bliink.io/disclosures.json": { - "timestamp": "2026-03-12T23:08:05.406Z", + "timestamp": "2026-03-14T22:01:49.524Z", "disclosures": [] } }, diff --git a/metadata/modules/blockthroughBidAdapter.json b/metadata/modules/blockthroughBidAdapter.json index 40a23c294c8..e04ba439c5e 100644 --- a/metadata/modules/blockthroughBidAdapter.json +++ b/metadata/modules/blockthroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://blockthrough.com/tcf_disclosures.json": { - "timestamp": "2026-03-12T23:08:05.710Z", + "timestamp": "2026-03-14T22:01:49.844Z", "disclosures": [ { "identifier": "BT_AA_DETECTION", diff --git a/metadata/modules/blueBidAdapter.json b/metadata/modules/blueBidAdapter.json index fcb39e75aac..153b192f3dc 100644 --- a/metadata/modules/blueBidAdapter.json +++ b/metadata/modules/blueBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://getblue.io/iab/iab.json": { - "timestamp": "2026-03-12T23:08:05.834Z", + "timestamp": "2026-03-14T22:01:49.946Z", "disclosures": [] } }, diff --git a/metadata/modules/bmsBidAdapter.json b/metadata/modules/bmsBidAdapter.json index 0ee8844b0ec..0a9f5f2d60c 100644 --- a/metadata/modules/bmsBidAdapter.json +++ b/metadata/modules/bmsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bluems.com/iab.json": { - "timestamp": "2026-03-12T23:08:06.234Z", + "timestamp": "2026-03-14T22:01:50.302Z", "disclosures": [] } }, diff --git a/metadata/modules/boldwinBidAdapter.json b/metadata/modules/boldwinBidAdapter.json index bf2900f86e7..04384e3c493 100644 --- a/metadata/modules/boldwinBidAdapter.json +++ b/metadata/modules/boldwinBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://magav.videowalldirect.com/iab/videowalldirectiab.json": { - "timestamp": "2026-03-12T23:08:06.249Z", + "timestamp": "2026-03-14T22:01:50.317Z", "disclosures": [] } }, diff --git a/metadata/modules/bridBidAdapter.json b/metadata/modules/bridBidAdapter.json index 650a7044376..cc3d804f9fe 100644 --- a/metadata/modules/bridBidAdapter.json +++ b/metadata/modules/bridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-12T23:08:06.269Z", + "timestamp": "2026-03-14T22:01:50.336Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/browsiBidAdapter.json b/metadata/modules/browsiBidAdapter.json index c07c991f104..6a25a069e78 100644 --- a/metadata/modules/browsiBidAdapter.json +++ b/metadata/modules/browsiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.browsiprod.com/ads/tcf.json": { - "timestamp": "2026-03-12T23:08:06.406Z", + "timestamp": "2026-03-14T22:01:50.476Z", "disclosures": [] } }, diff --git a/metadata/modules/bucksenseBidAdapter.json b/metadata/modules/bucksenseBidAdapter.json index c3e1aacbce5..3b972e155f5 100644 --- a/metadata/modules/bucksenseBidAdapter.json +++ b/metadata/modules/bucksenseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://j.bksnimages.com/iab/devsto02.json": { - "timestamp": "2026-03-12T23:08:06.427Z", + "timestamp": "2026-03-14T22:01:50.530Z", "disclosures": [] } }, diff --git a/metadata/modules/carodaBidAdapter.json b/metadata/modules/carodaBidAdapter.json index 8a355bfa97b..f2cd0dd3d15 100644 --- a/metadata/modules/carodaBidAdapter.json +++ b/metadata/modules/carodaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn2.caroda.io/tcfvds/2022-05-17/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:06.539Z", + "timestamp": "2026-03-14T22:01:50.644Z", "disclosures": [] } }, diff --git a/metadata/modules/categoryTranslation.json b/metadata/modules/categoryTranslation.json index bc271ef89af..bdf15dcb863 100644 --- a/metadata/modules/categoryTranslation.json +++ b/metadata/modules/categoryTranslation.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/categoryTranslation.json": { - "timestamp": "2026-03-12T23:06:53.491Z", + "timestamp": "2026-03-14T22:01:05.184Z", "disclosures": null } }, diff --git a/metadata/modules/ceeIdSystem.json b/metadata/modules/ceeIdSystem.json index 28a33f4a7b3..7a44080862b 100644 --- a/metadata/modules/ceeIdSystem.json +++ b/metadata/modules/ceeIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:06.830Z", + "timestamp": "2026-03-14T22:01:50.959Z", "disclosures": null } }, diff --git a/metadata/modules/chromeAiRtdProvider.json b/metadata/modules/chromeAiRtdProvider.json index 96cb1e5eefa..1037dfaf711 100644 --- a/metadata/modules/chromeAiRtdProvider.json +++ b/metadata/modules/chromeAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/chromeAiRtdProvider.json": { - "timestamp": "2026-03-12T23:08:07.158Z", + "timestamp": "2026-03-14T22:01:51.286Z", "disclosures": [ { "identifier": "chromeAi_detected_data", diff --git a/metadata/modules/clickioBidAdapter.json b/metadata/modules/clickioBidAdapter.json index 1f09a2fec63..35d6b21bf15 100644 --- a/metadata/modules/clickioBidAdapter.json +++ b/metadata/modules/clickioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://o.clickiocdn.com/tcf_storage_info.json": { - "timestamp": "2026-03-12T23:08:07.160Z", + "timestamp": "2026-03-14T22:01:51.287Z", "disclosures": [] } }, diff --git a/metadata/modules/compassBidAdapter.json b/metadata/modules/compassBidAdapter.json index f4b61452680..3e97174d9e9 100644 --- a/metadata/modules/compassBidAdapter.json +++ b/metadata/modules/compassBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T23:08:07.581Z", + "timestamp": "2026-03-14T22:01:51.731Z", "disclosures": [] } }, diff --git a/metadata/modules/conceptxBidAdapter.json b/metadata/modules/conceptxBidAdapter.json index bf2f61d605e..2576eb4d687 100644 --- a/metadata/modules/conceptxBidAdapter.json +++ b/metadata/modules/conceptxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cncptx.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:07.600Z", + "timestamp": "2026-03-14T22:01:51.749Z", "disclosures": [] } }, diff --git a/metadata/modules/connatixBidAdapter.json b/metadata/modules/connatixBidAdapter.json index 07abebbeb49..21753dc78e6 100644 --- a/metadata/modules/connatixBidAdapter.json +++ b/metadata/modules/connatixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://connatix.com/iab-tcf-disclosure.json": { - "timestamp": "2026-03-12T23:08:07.618Z", + "timestamp": "2026-03-14T22:01:51.769Z", "disclosures": [ { "identifier": "cnx_userId", diff --git a/metadata/modules/connectIdSystem.json b/metadata/modules/connectIdSystem.json index 34b44de146a..d717b5070c1 100644 --- a/metadata/modules/connectIdSystem.json +++ b/metadata/modules/connectIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:07.698Z", + "timestamp": "2026-03-14T22:01:51.851Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/connectadBidAdapter.json b/metadata/modules/connectadBidAdapter.json index ff0a94c231f..88634bfb4e5 100644 --- a/metadata/modules/connectadBidAdapter.json +++ b/metadata/modules/connectadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.connectad.io/tcf_storage_info.json": { - "timestamp": "2026-03-12T23:08:07.718Z", + "timestamp": "2026-03-14T22:01:51.873Z", "disclosures": [] } }, diff --git a/metadata/modules/contentexchangeBidAdapter.json b/metadata/modules/contentexchangeBidAdapter.json index bca3ce6c453..bf878caad40 100644 --- a/metadata/modules/contentexchangeBidAdapter.json +++ b/metadata/modules/contentexchangeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://hb.contentexchange.me/template/device_storage.json": { - "timestamp": "2026-03-12T23:08:07.747Z", + "timestamp": "2026-03-14T22:01:52.316Z", "disclosures": [] } }, diff --git a/metadata/modules/conversantBidAdapter.json b/metadata/modules/conversantBidAdapter.json index af3cec9cab8..8cae159b7ab 100644 --- a/metadata/modules/conversantBidAdapter.json +++ b/metadata/modules/conversantBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:08.082Z", + "timestamp": "2026-03-14T22:01:52.660Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/copper6sspBidAdapter.json b/metadata/modules/copper6sspBidAdapter.json index 69becfed522..4e7981bb6bd 100644 --- a/metadata/modules/copper6sspBidAdapter.json +++ b/metadata/modules/copper6sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.copper6.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:08.099Z", + "timestamp": "2026-03-14T22:01:52.678Z", "disclosures": [] } }, diff --git a/metadata/modules/cpmstarBidAdapter.json b/metadata/modules/cpmstarBidAdapter.json index 178952284b5..ba4f27d0ab4 100644 --- a/metadata/modules/cpmstarBidAdapter.json +++ b/metadata/modules/cpmstarBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-12T23:08:08.142Z", + "timestamp": "2026-03-14T22:01:52.763Z", "disclosures": [] } }, diff --git a/metadata/modules/criteoBidAdapter.json b/metadata/modules/criteoBidAdapter.json index b68ba2dbb5c..7b862f48b6a 100644 --- a/metadata/modules/criteoBidAdapter.json +++ b/metadata/modules/criteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T23:08:08.195Z", + "timestamp": "2026-03-14T22:01:52.818Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/criteoIdSystem.json b/metadata/modules/criteoIdSystem.json index 21751576441..9dadb955104 100644 --- a/metadata/modules/criteoIdSystem.json +++ b/metadata/modules/criteoIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.criteo.com/iab-europe/tcfv2/disclosure.json": { - "timestamp": "2026-03-12T23:08:08.215Z", + "timestamp": "2026-03-14T22:01:52.839Z", "disclosures": [ { "identifier": "criteo_fast_bid", diff --git a/metadata/modules/cwireBidAdapter.json b/metadata/modules/cwireBidAdapter.json index eb75e268896..eb9f73c1fb3 100644 --- a/metadata/modules/cwireBidAdapter.json +++ b/metadata/modules/cwireBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.cwi.re/artifacts/iab/iab.json": { - "timestamp": "2026-03-12T23:08:08.215Z", + "timestamp": "2026-03-14T22:01:52.839Z", "disclosures": [] } }, diff --git a/metadata/modules/czechAdIdSystem.json b/metadata/modules/czechAdIdSystem.json index 3eb5954423e..da769f7913c 100644 --- a/metadata/modules/czechAdIdSystem.json +++ b/metadata/modules/czechAdIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cpex.cz/storagedisclosure.json": { - "timestamp": "2026-03-12T23:08:08.553Z", + "timestamp": "2026-03-14T22:01:52.864Z", "disclosures": [] } }, diff --git a/metadata/modules/dailymotionBidAdapter.json b/metadata/modules/dailymotionBidAdapter.json index 88f077fb1ad..e09257d90ce 100644 --- a/metadata/modules/dailymotionBidAdapter.json +++ b/metadata/modules/dailymotionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://statics.dmcdn.net/a/vds.json": { - "timestamp": "2026-03-12T23:08:08.856Z", + "timestamp": "2026-03-14T22:01:53.267Z", "disclosures": [ { "identifier": "uid_dm", diff --git a/metadata/modules/debugging.json b/metadata/modules/debugging.json index fe45a2d970c..328bddc340e 100644 --- a/metadata/modules/debugging.json +++ b/metadata/modules/debugging.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-12T23:06:53.490Z", + "timestamp": "2026-03-14T22:01:05.183Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/deepintentBidAdapter.json b/metadata/modules/deepintentBidAdapter.json index 753e53cd5e1..9e6cc8c3bb8 100644 --- a/metadata/modules/deepintentBidAdapter.json +++ b/metadata/modules/deepintentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.deepintent.com/iabeurope_vendor_disclosures.json": { - "timestamp": "2026-03-12T23:08:08.881Z", + "timestamp": "2026-03-14T22:01:53.372Z", "disclosures": null } }, diff --git a/metadata/modules/defineMediaBidAdapter.json b/metadata/modules/defineMediaBidAdapter.json index e3071aeffb7..52ebdc1fc99 100644 --- a/metadata/modules/defineMediaBidAdapter.json +++ b/metadata/modules/defineMediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://definemedia.de/tcf/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T23:08:09.128Z", + "timestamp": "2026-03-14T22:01:53.532Z", "disclosures": [ { "identifier": "conative$dataGathering$Adex", diff --git a/metadata/modules/deltaprojectsBidAdapter.json b/metadata/modules/deltaprojectsBidAdapter.json index a0edb8a0ffe..e27020bdca8 100644 --- a/metadata/modules/deltaprojectsBidAdapter.json +++ b/metadata/modules/deltaprojectsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.de17a.com/policy/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:10.238Z", + "timestamp": "2026-03-14T22:01:53.981Z", "disclosures": [] } }, diff --git a/metadata/modules/dianomiBidAdapter.json b/metadata/modules/dianomiBidAdapter.json index bd44b87363a..6ee324e79db 100644 --- a/metadata/modules/dianomiBidAdapter.json +++ b/metadata/modules/dianomiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.dianomi.com/device_storage.json": { - "timestamp": "2026-03-12T23:08:10.665Z", + "timestamp": "2026-03-14T22:01:54.413Z", "disclosures": [] } }, diff --git a/metadata/modules/digitalMatterBidAdapter.json b/metadata/modules/digitalMatterBidAdapter.json index 98d5b253e34..01b0a47b636 100644 --- a/metadata/modules/digitalMatterBidAdapter.json +++ b/metadata/modules/digitalMatterBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://digitalmatter.ai/disclosures.json": { - "timestamp": "2026-03-12T23:08:10.666Z", + "timestamp": "2026-03-14T22:01:54.413Z", "disclosures": [] } }, diff --git a/metadata/modules/distroscaleBidAdapter.json b/metadata/modules/distroscaleBidAdapter.json index 66d555911f0..142b0647ede 100644 --- a/metadata/modules/distroscaleBidAdapter.json +++ b/metadata/modules/distroscaleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.jsrdn.com/tcf/tcf-vendor-disclosure.json": { - "timestamp": "2026-03-12T23:08:11.035Z", + "timestamp": "2026-03-14T22:01:54.780Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeAdManagerBidAdapter.json b/metadata/modules/docereeAdManagerBidAdapter.json index f964ac6f40a..fa9cb23f5f3 100644 --- a/metadata/modules/docereeAdManagerBidAdapter.json +++ b/metadata/modules/docereeAdManagerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:11.085Z", + "timestamp": "2026-03-14T22:01:55.075Z", "disclosures": [] } }, diff --git a/metadata/modules/docereeBidAdapter.json b/metadata/modules/docereeBidAdapter.json index 2227437a8b1..fcf92c31563 100644 --- a/metadata/modules/docereeBidAdapter.json +++ b/metadata/modules/docereeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://doceree.com/.well-known/iab/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:11.837Z", + "timestamp": "2026-03-14T22:01:55.828Z", "disclosures": [] } }, diff --git a/metadata/modules/dspxBidAdapter.json b/metadata/modules/dspxBidAdapter.json index 2b04a0126e4..2de17defd7b 100644 --- a/metadata/modules/dspxBidAdapter.json +++ b/metadata/modules/dspxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/os.json": { - "timestamp": "2026-03-12T23:08:11.839Z", + "timestamp": "2026-03-14T22:01:55.830Z", "disclosures": [] } }, diff --git a/metadata/modules/e_volutionBidAdapter.json b/metadata/modules/e_volutionBidAdapter.json index 2251fa970d1..16ecd4a10a2 100644 --- a/metadata/modules/e_volutionBidAdapter.json +++ b/metadata/modules/e_volutionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://e-volution.ai/file.json": { - "timestamp": "2026-03-12T23:08:12.572Z", + "timestamp": "2026-03-14T22:01:56.484Z", "disclosures": [] } }, diff --git a/metadata/modules/edge226BidAdapter.json b/metadata/modules/edge226BidAdapter.json index 5a609aed2d1..62050c87076 100644 --- a/metadata/modules/edge226BidAdapter.json +++ b/metadata/modules/edge226BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.serveteck.com/cdn_storage/tcf/tcf.json?a=1.io": { - "timestamp": "2026-03-12T23:08:12.922Z", + "timestamp": "2026-03-14T22:01:56.828Z", "disclosures": [] } }, diff --git a/metadata/modules/empowerBidAdapter.json b/metadata/modules/empowerBidAdapter.json index 60b672222b5..d8cc897f838 100644 --- a/metadata/modules/empowerBidAdapter.json +++ b/metadata/modules/empowerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.empower.net/vendor/vendor.json": { - "timestamp": "2026-03-12T23:08:12.982Z", + "timestamp": "2026-03-14T22:01:56.884Z", "disclosures": [] } }, diff --git a/metadata/modules/equativBidAdapter.json b/metadata/modules/equativBidAdapter.json index ee2f88eca70..5dfbad9e9a3 100644 --- a/metadata/modules/equativBidAdapter.json +++ b/metadata/modules/equativBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-12T23:08:13.007Z", + "timestamp": "2026-03-14T22:01:57.159Z", "disclosures": [] } }, diff --git a/metadata/modules/eskimiBidAdapter.json b/metadata/modules/eskimiBidAdapter.json index 2dd4b209a26..11a1fc49317 100644 --- a/metadata/modules/eskimiBidAdapter.json +++ b/metadata/modules/eskimiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://dsp-media.eskimi.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:13.050Z", + "timestamp": "2026-03-14T22:01:57.203Z", "disclosures": [] } }, diff --git a/metadata/modules/etargetBidAdapter.json b/metadata/modules/etargetBidAdapter.json index 0f1570f176d..0a3c97223f9 100644 --- a/metadata/modules/etargetBidAdapter.json +++ b/metadata/modules/etargetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.etarget.sk/cookies3.json": { - "timestamp": "2026-03-12T23:08:13.072Z", + "timestamp": "2026-03-14T22:01:57.231Z", "disclosures": [] } }, diff --git a/metadata/modules/euidIdSystem.json b/metadata/modules/euidIdSystem.json index 52bd5e93c0f..c04ec332848 100644 --- a/metadata/modules/euidIdSystem.json +++ b/metadata/modules/euidIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T23:08:13.610Z", + "timestamp": "2026-03-14T22:01:57.808Z", "disclosures": [] } }, diff --git a/metadata/modules/exadsBidAdapter.json b/metadata/modules/exadsBidAdapter.json index 657b485c3e9..7d9f84855c0 100644 --- a/metadata/modules/exadsBidAdapter.json +++ b/metadata/modules/exadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://a.native7.com/tcf/deviceStorage.php": { - "timestamp": "2026-03-12T23:08:13.810Z", + "timestamp": "2026-03-14T22:01:58.009Z", "disclosures": [ { "identifier": "pn-zone-*", diff --git a/metadata/modules/feedadBidAdapter.json b/metadata/modules/feedadBidAdapter.json index 1818e4e43c7..dca3b326fd4 100644 --- a/metadata/modules/feedadBidAdapter.json +++ b/metadata/modules/feedadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://api.feedad.com/tcf-device-disclosures.json": { - "timestamp": "2026-03-12T23:08:14.068Z", + "timestamp": "2026-03-14T22:01:58.194Z", "disclosures": [ { "identifier": "__fad_data", diff --git a/metadata/modules/fwsspBidAdapter.json b/metadata/modules/fwsspBidAdapter.json index 465fd96af22..31c9d286492 100644 --- a/metadata/modules/fwsspBidAdapter.json +++ b/metadata/modules/fwsspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.fwmrm.net/g/devicedisclosure.json": { - "timestamp": "2026-03-12T23:08:14.177Z", + "timestamp": "2026-03-14T22:01:58.315Z", "disclosures": [] } }, diff --git a/metadata/modules/gamoshiBidAdapter.json b/metadata/modules/gamoshiBidAdapter.json index 349f992b216..d392c55e551 100644 --- a/metadata/modules/gamoshiBidAdapter.json +++ b/metadata/modules/gamoshiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.gamoshi.io/disclosures-client-storage.json": { - "timestamp": "2026-03-12T23:08:14.510Z", + "timestamp": "2026-03-14T22:01:58.534Z", "disclosures": [] } }, diff --git a/metadata/modules/gemiusIdSystem.json b/metadata/modules/gemiusIdSystem.json index 72c6f91797d..cbff60090cd 100644 --- a/metadata/modules/gemiusIdSystem.json +++ b/metadata/modules/gemiusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gemius.com/media/documents/Gemius_SA_Vendor_Device_Storage.json": { - "timestamp": "2026-03-12T23:08:14.600Z", + "timestamp": "2026-03-14T22:01:58.649Z", "disclosures": [ { "identifier": "__gsyncs_gdpr", diff --git a/metadata/modules/glomexBidAdapter.json b/metadata/modules/glomexBidAdapter.json index 45f9a187dc0..85a3afd0138 100644 --- a/metadata/modules/glomexBidAdapter.json +++ b/metadata/modules/glomexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:14.604Z", + "timestamp": "2026-03-14T22:01:58.651Z", "disclosures": [ { "identifier": "glomexUser", diff --git a/metadata/modules/goldbachBidAdapter.json b/metadata/modules/goldbachBidAdapter.json index 868942a038d..0711b6a6db5 100644 --- a/metadata/modules/goldbachBidAdapter.json +++ b/metadata/modules/goldbachBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gb-next.ch/TcfGoldbachDeviceStorage.json": { - "timestamp": "2026-03-12T23:08:14.621Z", + "timestamp": "2026-03-14T22:01:58.673Z", "disclosures": [ { "identifier": "dakt_2_session_id", diff --git a/metadata/modules/gridBidAdapter.json b/metadata/modules/gridBidAdapter.json index a733083c5c4..07539dd6c09 100644 --- a/metadata/modules/gridBidAdapter.json +++ b/metadata/modules/gridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.themediagrid.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:14.648Z", + "timestamp": "2026-03-14T22:01:58.700Z", "disclosures": [] } }, diff --git a/metadata/modules/gumgumBidAdapter.json b/metadata/modules/gumgumBidAdapter.json index 4821ce19b0c..367f4632a58 100644 --- a/metadata/modules/gumgumBidAdapter.json +++ b/metadata/modules/gumgumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://marketing.gumgum.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:14.788Z", + "timestamp": "2026-03-14T22:01:58.789Z", "disclosures": [] } }, diff --git a/metadata/modules/hadronIdSystem.json b/metadata/modules/hadronIdSystem.json index 3a6b1822dd6..5cf1d5d8ca5 100644 --- a/metadata/modules/hadronIdSystem.json +++ b/metadata/modules/hadronIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-12T23:08:14.856Z", + "timestamp": "2026-03-14T22:01:58.839Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/hadronRtdProvider.json b/metadata/modules/hadronRtdProvider.json index 47e76dbe42d..2f69e177575 100644 --- a/metadata/modules/hadronRtdProvider.json +++ b/metadata/modules/hadronRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://p.ad.gt/static/iab_tcf.json": { - "timestamp": "2026-03-12T23:08:16.142Z", + "timestamp": "2026-03-14T22:01:59.001Z", "disclosures": [ { "identifier": "au/sid", diff --git a/metadata/modules/harionBidAdapter.json b/metadata/modules/harionBidAdapter.json index 27b5783e2f0..56500317adf 100644 --- a/metadata/modules/harionBidAdapter.json +++ b/metadata/modules/harionBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://markappmedia.site/vendor.json": { - "timestamp": "2026-03-12T23:08:16.142Z", + "timestamp": "2026-03-14T22:01:59.001Z", "disclosures": [] } }, diff --git a/metadata/modules/holidBidAdapter.json b/metadata/modules/holidBidAdapter.json index 15af3f082fd..e7f39170a7e 100644 --- a/metadata/modules/holidBidAdapter.json +++ b/metadata/modules/holidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ads.holid.io/devicestorage.json": { - "timestamp": "2026-03-12T23:08:16.527Z", + "timestamp": "2026-03-14T22:01:59.345Z", "disclosures": [ { "identifier": "uids", diff --git a/metadata/modules/hybridBidAdapter.json b/metadata/modules/hybridBidAdapter.json index 346e450bf43..37cf38619ce 100644 --- a/metadata/modules/hybridBidAdapter.json +++ b/metadata/modules/hybridBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:16.773Z", + "timestamp": "2026-03-14T22:01:59.581Z", "disclosures": [] } }, diff --git a/metadata/modules/id5IdSystem.json b/metadata/modules/id5IdSystem.json index 5715a40c52e..84b13e339ef 100644 --- a/metadata/modules/id5IdSystem.json +++ b/metadata/modules/id5IdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://id5-sync.com/tcf/disclosures.json": { - "timestamp": "2026-03-12T23:08:17.007Z", + "timestamp": "2026-03-14T22:01:59.807Z", "disclosures": [ { "identifier": "id5id", diff --git a/metadata/modules/identityLinkIdSystem.json b/metadata/modules/identityLinkIdSystem.json index 15fecdf12a0..9fdccd08fc5 100644 --- a/metadata/modules/identityLinkIdSystem.json +++ b/metadata/modules/identityLinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.ats.rlcdn.com/device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:17.290Z", + "timestamp": "2026-03-14T22:02:00.099Z", "disclosures": [ { "identifier": "_lr_retry_request", diff --git a/metadata/modules/illuminBidAdapter.json b/metadata/modules/illuminBidAdapter.json index 46cbf55a0e3..af522c3fa5e 100644 --- a/metadata/modules/illuminBidAdapter.json +++ b/metadata/modules/illuminBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://admanmedia.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:17.307Z", + "timestamp": "2026-03-14T22:02:00.124Z", "disclosures": [] } }, diff --git a/metadata/modules/impactifyBidAdapter.json b/metadata/modules/impactifyBidAdapter.json index 50ea1b92708..29d9564995d 100644 --- a/metadata/modules/impactifyBidAdapter.json +++ b/metadata/modules/impactifyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.impactify.io/tcfvendors.json": { - "timestamp": "2026-03-12T23:08:17.607Z", + "timestamp": "2026-03-14T22:02:00.414Z", "disclosures": [ { "identifier": "_im*", diff --git a/metadata/modules/improvedigitalBidAdapter.json b/metadata/modules/improvedigitalBidAdapter.json index 0f3990eeb45..cb84525d993 100644 --- a/metadata/modules/improvedigitalBidAdapter.json +++ b/metadata/modules/improvedigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sellers.improvedigital.com/tcf-cookies.json": { - "timestamp": "2026-03-12T23:08:17.901Z", + "timestamp": "2026-03-14T22:02:00.713Z", "disclosures": [ { "identifier": "tuuid", diff --git a/metadata/modules/inmobiBidAdapter.json b/metadata/modules/inmobiBidAdapter.json index d6b15298b8e..d4c0b639e9d 100644 --- a/metadata/modules/inmobiBidAdapter.json +++ b/metadata/modules/inmobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publisher.inmobi.com/public/disclosure": { - "timestamp": "2026-03-12T23:08:17.901Z", + "timestamp": "2026-03-14T22:02:00.713Z", "disclosures": [] } }, diff --git a/metadata/modules/insticatorBidAdapter.json b/metadata/modules/insticatorBidAdapter.json index caeb36fffd7..ed9d069e59c 100644 --- a/metadata/modules/insticatorBidAdapter.json +++ b/metadata/modules/insticatorBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.insticator.com/iab/device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:17.926Z", + "timestamp": "2026-03-14T22:02:00.831Z", "disclosures": [ { "identifier": "visitorGeo", diff --git a/metadata/modules/insuradsBidAdapter.json b/metadata/modules/insuradsBidAdapter.json index c4385d7bcc0..16588e6a6d7 100644 --- a/metadata/modules/insuradsBidAdapter.json +++ b/metadata/modules/insuradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.insurads.com/tcf-vdsod.json": { - "timestamp": "2026-03-12T23:08:17.954Z", + "timestamp": "2026-03-14T22:02:00.894Z", "disclosures": [ { "identifier": "___iat_ses", diff --git a/metadata/modules/intentIqIdSystem.json b/metadata/modules/intentIqIdSystem.json index 98d6db601de..8cacf476f6b 100644 --- a/metadata/modules/intentIqIdSystem.json +++ b/metadata/modules/intentIqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://agent.intentiq.com/GDPR/gdpr.json": { - "timestamp": "2026-03-12T23:08:18.067Z", + "timestamp": "2026-03-14T22:02:01.202Z", "disclosures": [] } }, diff --git a/metadata/modules/invibesBidAdapter.json b/metadata/modules/invibesBidAdapter.json index bbd35015280..bf0778356e8 100644 --- a/metadata/modules/invibesBidAdapter.json +++ b/metadata/modules/invibesBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.invibes.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:18.118Z", + "timestamp": "2026-03-14T22:02:01.261Z", "disclosures": [ { "identifier": "ivvcap", diff --git a/metadata/modules/ipromBidAdapter.json b/metadata/modules/ipromBidAdapter.json index 917784a694c..d628e552649 100644 --- a/metadata/modules/ipromBidAdapter.json +++ b/metadata/modules/ipromBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://core.iprom.net/info/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:18.439Z", + "timestamp": "2026-03-14T22:02:01.596Z", "disclosures": [] } }, diff --git a/metadata/modules/ixBidAdapter.json b/metadata/modules/ixBidAdapter.json index 877820657a9..70550fa3f5f 100644 --- a/metadata/modules/ixBidAdapter.json +++ b/metadata/modules/ixBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.indexexchange.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:18.915Z", + "timestamp": "2026-03-14T22:02:02.085Z", "disclosures": [ { "identifier": "ix_features", diff --git a/metadata/modules/justIdSystem.json b/metadata/modules/justIdSystem.json index b272ca58355..7b0f158f03c 100644 --- a/metadata/modules/justIdSystem.json +++ b/metadata/modules/justIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audience-solutions.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:19.038Z", + "timestamp": "2026-03-14T22:02:02.363Z", "disclosures": [ { "identifier": "__jtuid", diff --git a/metadata/modules/justpremiumBidAdapter.json b/metadata/modules/justpremiumBidAdapter.json index 4cb4c9861b1..727d51b2211 100644 --- a/metadata/modules/justpremiumBidAdapter.json +++ b/metadata/modules/justpremiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.justpremium.com/devicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:19.557Z", + "timestamp": "2026-03-14T22:02:02.891Z", "disclosures": [] } }, diff --git a/metadata/modules/jwplayerBidAdapter.json b/metadata/modules/jwplayerBidAdapter.json index 670a86dbe39..720a59c48c9 100644 --- a/metadata/modules/jwplayerBidAdapter.json +++ b/metadata/modules/jwplayerBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.jwplayer.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:19.594Z", + "timestamp": "2026-03-14T22:02:03.137Z", "disclosures": [] } }, diff --git a/metadata/modules/kargoBidAdapter.json b/metadata/modules/kargoBidAdapter.json index 95a52ff1316..965e233a12e 100644 --- a/metadata/modules/kargoBidAdapter.json +++ b/metadata/modules/kargoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://storage.cloud.kargo.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:19.748Z", + "timestamp": "2026-03-14T22:02:03.342Z", "disclosures": [ { "identifier": "krg_crb", diff --git a/metadata/modules/kueezRtbBidAdapter.json b/metadata/modules/kueezRtbBidAdapter.json index 8baa9919489..71fcf092a85 100644 --- a/metadata/modules/kueezRtbBidAdapter.json +++ b/metadata/modules/kueezRtbBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://en.kueez.com/tcf.json": { - "timestamp": "2026-03-12T23:08:19.768Z", + "timestamp": "2026-03-14T22:02:03.367Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/limelightDigitalBidAdapter.json b/metadata/modules/limelightDigitalBidAdapter.json index fc448abd578..dd78b521fdb 100644 --- a/metadata/modules/limelightDigitalBidAdapter.json +++ b/metadata/modules/limelightDigitalBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://policy.iion.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:19.834Z", + "timestamp": "2026-03-14T22:02:03.408Z", "disclosures": [] }, "https://orangeclickmedia.com/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:19.881Z", + "timestamp": "2026-03-14T22:02:03.456Z", "disclosures": [] } }, diff --git a/metadata/modules/liveIntentIdSystem.json b/metadata/modules/liveIntentIdSystem.json index 696af417708..d896e5432bf 100644 --- a/metadata/modules/liveIntentIdSystem.json +++ b/metadata/modules/liveIntentIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:19.882Z", + "timestamp": "2026-03-14T22:02:03.457Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/liveIntentRtdProvider.json b/metadata/modules/liveIntentRtdProvider.json index 11b01c3ba8b..5beaf313598 100644 --- a/metadata/modules/liveIntentRtdProvider.json +++ b/metadata/modules/liveIntentRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://b-code.liadm.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:19.893Z", + "timestamp": "2026-03-14T22:02:03.469Z", "disclosures": [ { "identifier": "_lc2_fpi", diff --git a/metadata/modules/livewrappedBidAdapter.json b/metadata/modules/livewrappedBidAdapter.json index 331625f658a..c1b0155cebc 100644 --- a/metadata/modules/livewrappedBidAdapter.json +++ b/metadata/modules/livewrappedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://content.lwadm.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:19.893Z", + "timestamp": "2026-03-14T22:02:03.470Z", "disclosures": [ { "identifier": "uid", diff --git a/metadata/modules/loopmeBidAdapter.json b/metadata/modules/loopmeBidAdapter.json index 51d96fa187e..a1ce0cf0696 100644 --- a/metadata/modules/loopmeBidAdapter.json +++ b/metadata/modules/loopmeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://co.loopme.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:19.905Z", + "timestamp": "2026-03-14T22:02:03.813Z", "disclosures": [] } }, diff --git a/metadata/modules/lotamePanoramaIdSystem.json b/metadata/modules/lotamePanoramaIdSystem.json index 4670c6cdf58..e0dbb7d8223 100644 --- a/metadata/modules/lotamePanoramaIdSystem.json +++ b/metadata/modules/lotamePanoramaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tags.crwdcntrl.net/privacy/tcf-purposes.json": { - "timestamp": "2026-03-12T23:08:20.945Z", + "timestamp": "2026-03-14T22:02:03.926Z", "disclosures": [ { "identifier": "_cc_id", diff --git a/metadata/modules/luponmediaBidAdapter.json b/metadata/modules/luponmediaBidAdapter.json index 17d05c419a5..c62987cbce7 100644 --- a/metadata/modules/luponmediaBidAdapter.json +++ b/metadata/modules/luponmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://luponmedia.com/vendor_device_storage.json": { - "timestamp": "2026-03-12T23:08:20.956Z", + "timestamp": "2026-03-14T22:02:03.989Z", "disclosures": [] } }, diff --git a/metadata/modules/madvertiseBidAdapter.json b/metadata/modules/madvertiseBidAdapter.json index 7b0849b4c21..5bf1d51d8e7 100644 --- a/metadata/modules/madvertiseBidAdapter.json +++ b/metadata/modules/madvertiseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserver.bluestack.app/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:21.375Z", + "timestamp": "2026-03-14T22:02:04.404Z", "disclosures": [] } }, diff --git a/metadata/modules/magniteBidAdapter.json b/metadata/modules/magniteBidAdapter.json index 647f67af714..789c5c69182 100644 --- a/metadata/modules/magniteBidAdapter.json +++ b/metadata/modules/magniteBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T23:08:21.727Z", + "timestamp": "2026-03-14T22:02:04.752Z", "disclosures": [] } }, diff --git a/metadata/modules/marsmediaBidAdapter.json b/metadata/modules/marsmediaBidAdapter.json index c1472bc9153..9262e678022 100644 --- a/metadata/modules/marsmediaBidAdapter.json +++ b/metadata/modules/marsmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mars.media/apis/tcf-v2.json": { - "timestamp": "2026-03-12T23:08:21.962Z", + "timestamp": "2026-03-14T22:02:04.988Z", "disclosures": [] } }, diff --git a/metadata/modules/mediaConsortiumBidAdapter.json b/metadata/modules/mediaConsortiumBidAdapter.json index b5e34ebeed4..2089c0230fa 100644 --- a/metadata/modules/mediaConsortiumBidAdapter.json +++ b/metadata/modules/mediaConsortiumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.hubvisor.io/assets/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:22.137Z", + "timestamp": "2026-03-14T22:02:05.099Z", "disclosures": [ { "identifier": "hbv:turbo-cmp", diff --git a/metadata/modules/mediaforceBidAdapter.json b/metadata/modules/mediaforceBidAdapter.json index 1843a229116..e02a1f722a5 100644 --- a/metadata/modules/mediaforceBidAdapter.json +++ b/metadata/modules/mediaforceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://comparisons.org/privacy.json": { - "timestamp": "2026-03-12T23:08:22.153Z", + "timestamp": "2026-03-14T22:02:05.227Z", "disclosures": [] } }, diff --git a/metadata/modules/mediafuseBidAdapter.json b/metadata/modules/mediafuseBidAdapter.json index e30b3e64011..d2ef46f4aa0 100644 --- a/metadata/modules/mediafuseBidAdapter.json +++ b/metadata/modules/mediafuseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:22.169Z", + "timestamp": "2026-03-14T22:02:05.267Z", "disclosures": [] } }, diff --git a/metadata/modules/mediagoBidAdapter.json b/metadata/modules/mediagoBidAdapter.json index 645b720d610..1a799beb444 100644 --- a/metadata/modules/mediagoBidAdapter.json +++ b/metadata/modules/mediagoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.mediago.io/js/tcf.json": { - "timestamp": "2026-03-12T23:08:22.170Z", + "timestamp": "2026-03-14T22:02:05.268Z", "disclosures": [] } }, diff --git a/metadata/modules/mediakeysBidAdapter.json b/metadata/modules/mediakeysBidAdapter.json index 614413ecf7b..9481792d601 100644 --- a/metadata/modules/mediakeysBidAdapter.json +++ b/metadata/modules/mediakeysBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.eu-west-3.amazonaws.com/adserving.resourcekeys.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:22.193Z", + "timestamp": "2026-03-14T22:02:05.334Z", "disclosures": [] } }, diff --git a/metadata/modules/medianetBidAdapter.json b/metadata/modules/medianetBidAdapter.json index f259e397a4e..f1fa7d445cd 100644 --- a/metadata/modules/medianetBidAdapter.json +++ b/metadata/modules/medianetBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.media.net/tcfv2/gvl/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:22.474Z", + "timestamp": "2026-03-14T22:02:05.620Z", "disclosures": [ { "identifier": "_mNExInsl", @@ -246,7 +246,7 @@ ] }, "https://trustedstack.com/tcf/gvl/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:22.604Z", + "timestamp": "2026-03-14T22:02:05.660Z", "disclosures": [ { "identifier": "usp_status", diff --git a/metadata/modules/mediasquareBidAdapter.json b/metadata/modules/mediasquareBidAdapter.json index 4d650651c3e..70a4396e8ed 100644 --- a/metadata/modules/mediasquareBidAdapter.json +++ b/metadata/modules/mediasquareBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediasquare.fr/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:22.644Z", + "timestamp": "2026-03-14T22:02:05.693Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidBidAdapter.json b/metadata/modules/mgidBidAdapter.json index db30999407d..4c6d5f80f47 100644 --- a/metadata/modules/mgidBidAdapter.json +++ b/metadata/modules/mgidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T23:08:23.161Z", + "timestamp": "2026-03-14T22:02:06.247Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidRtdProvider.json b/metadata/modules/mgidRtdProvider.json index 3dad2be7b2c..9361aac4544 100644 --- a/metadata/modules/mgidRtdProvider.json +++ b/metadata/modules/mgidRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T23:08:23.213Z", + "timestamp": "2026-03-14T22:02:06.312Z", "disclosures": [] } }, diff --git a/metadata/modules/mgidXBidAdapter.json b/metadata/modules/mgidXBidAdapter.json index d3f1cb262e9..d6ae6f2a58e 100644 --- a/metadata/modules/mgidXBidAdapter.json +++ b/metadata/modules/mgidXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.mgid.com/assets/devicestorage.json": { - "timestamp": "2026-03-12T23:08:23.213Z", + "timestamp": "2026-03-14T22:02:06.312Z", "disclosures": [] } }, diff --git a/metadata/modules/minutemediaBidAdapter.json b/metadata/modules/minutemediaBidAdapter.json index 384513bb788..97c5b22cad6 100644 --- a/metadata/modules/minutemediaBidAdapter.json +++ b/metadata/modules/minutemediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://disclosures.mmctsvc.com/device-storage.json": { - "timestamp": "2026-03-12T23:08:23.214Z", + "timestamp": "2026-03-14T22:02:06.313Z", "disclosures": [] } }, diff --git a/metadata/modules/missenaBidAdapter.json b/metadata/modules/missenaBidAdapter.json index 89eb9062040..51d17ed2547 100644 --- a/metadata/modules/missenaBidAdapter.json +++ b/metadata/modules/missenaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.missena.io/iab.json": { - "timestamp": "2026-03-12T23:08:23.229Z", + "timestamp": "2026-03-14T22:02:06.331Z", "disclosures": [] } }, diff --git a/metadata/modules/mobianRtdProvider.json b/metadata/modules/mobianRtdProvider.json index 51f881c92e5..afa8e75fc0e 100644 --- a/metadata/modules/mobianRtdProvider.json +++ b/metadata/modules/mobianRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.outcomes.net/tcf.json": { - "timestamp": "2026-03-12T23:08:23.278Z", + "timestamp": "2026-03-14T22:02:06.402Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiBidAdapter.json b/metadata/modules/mobkoiBidAdapter.json index a583908e83d..ea620e11a0b 100644 --- a/metadata/modules/mobkoiBidAdapter.json +++ b/metadata/modules/mobkoiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:23.293Z", + "timestamp": "2026-03-14T22:02:06.419Z", "disclosures": [] } }, diff --git a/metadata/modules/mobkoiIdSystem.json b/metadata/modules/mobkoiIdSystem.json index fa8f77eb08f..bb900b76877 100644 --- a/metadata/modules/mobkoiIdSystem.json +++ b/metadata/modules/mobkoiIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.maximus.mobkoi.com/tcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:23.308Z", + "timestamp": "2026-03-14T22:02:06.437Z", "disclosures": [] } }, diff --git a/metadata/modules/msftBidAdapter.json b/metadata/modules/msftBidAdapter.json index 0da54ed3895..751e4040f2b 100644 --- a/metadata/modules/msftBidAdapter.json +++ b/metadata/modules/msftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://acdn.adnxs.com/gvl/1d/xandrdevicestoragedisclosures.json": { - "timestamp": "2026-03-12T23:08:23.308Z", + "timestamp": "2026-03-14T22:02:06.437Z", "disclosures": [] } }, diff --git a/metadata/modules/nativeryBidAdapter.json b/metadata/modules/nativeryBidAdapter.json index a347669741f..14409dbb1f4 100644 --- a/metadata/modules/nativeryBidAdapter.json +++ b/metadata/modules/nativeryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnimg.nativery.com/widget/js/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:23.309Z", + "timestamp": "2026-03-14T22:02:06.438Z", "disclosures": [] } }, diff --git a/metadata/modules/nativoBidAdapter.json b/metadata/modules/nativoBidAdapter.json index 1299b651c55..9c7e5071724 100644 --- a/metadata/modules/nativoBidAdapter.json +++ b/metadata/modules/nativoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab.nativo.com/tcf-disclosures.json": { - "timestamp": "2026-03-12T23:08:23.635Z", + "timestamp": "2026-03-14T22:02:06.752Z", "disclosures": [] } }, diff --git a/metadata/modules/newspassidBidAdapter.json b/metadata/modules/newspassidBidAdapter.json index 2053f96f14a..3ab54085739 100644 --- a/metadata/modules/newspassidBidAdapter.json +++ b/metadata/modules/newspassidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.aditude.com/storageaccess.json": { - "timestamp": "2026-03-12T23:08:23.650Z", + "timestamp": "2026-03-14T22:02:06.780Z", "disclosures": [] } }, diff --git a/metadata/modules/nextMillenniumBidAdapter.json b/metadata/modules/nextMillenniumBidAdapter.json index 6fe74538d4a..c4def05e807 100644 --- a/metadata/modules/nextMillenniumBidAdapter.json +++ b/metadata/modules/nextMillenniumBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://nextmillennium.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:23.650Z", + "timestamp": "2026-03-14T22:02:06.781Z", "disclosures": [] } }, diff --git a/metadata/modules/nextrollBidAdapter.json b/metadata/modules/nextrollBidAdapter.json index 2c7214929d9..a135f7c6e68 100644 --- a/metadata/modules/nextrollBidAdapter.json +++ b/metadata/modules/nextrollBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.adroll.com/shares/device_storage.json": { - "timestamp": "2026-03-12T23:08:23.692Z", + "timestamp": "2026-03-14T22:02:06.828Z", "disclosures": [ { "identifier": "__adroll_fpc", diff --git a/metadata/modules/nexx360BidAdapter.json b/metadata/modules/nexx360BidAdapter.json index 0fea9796603..52c98b840bf 100644 --- a/metadata/modules/nexx360BidAdapter.json +++ b/metadata/modules/nexx360BidAdapter.json @@ -2,19 +2,19 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:24.482Z", + "timestamp": "2026-03-14T22:02:07.684Z", "disclosures": [] }, "https://static.first-id.fr/tcf/cookie.json": { - "timestamp": "2026-03-12T23:08:23.940Z", + "timestamp": "2026-03-14T22:02:07.085Z", "disclosures": [] }, "https://i.plug.it/banners/js/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:23.956Z", + "timestamp": "2026-03-14T22:02:07.119Z", "disclosures": [] }, "https://player.glomex.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:24.080Z", + "timestamp": "2026-03-14T22:02:07.243Z", "disclosures": [ { "identifier": "glomexUser", @@ -46,7 +46,7 @@ ] }, "https://gdpr.pubx.ai/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T23:08:24.080Z", + "timestamp": "2026-03-14T22:02:07.243Z", "disclosures": [ { "identifier": "pubx:defaults", @@ -61,7 +61,7 @@ ] }, "https://yieldbird.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:24.094Z", + "timestamp": "2026-03-14T22:02:07.310Z", "disclosures": [] } }, diff --git a/metadata/modules/nobidBidAdapter.json b/metadata/modules/nobidBidAdapter.json index 532bc1a1274..39b9f6375b2 100644 --- a/metadata/modules/nobidBidAdapter.json +++ b/metadata/modules/nobidBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://public.servenobid.com/gdpr_tcf/vendor_device_storage_operational_disclosures.json": { - "timestamp": "2026-03-12T23:08:24.483Z", + "timestamp": "2026-03-14T22:02:07.685Z", "disclosures": [] } }, diff --git a/metadata/modules/nodalsAiRtdProvider.json b/metadata/modules/nodalsAiRtdProvider.json index 41ecd2dbbb8..9a70208c6fe 100644 --- a/metadata/modules/nodalsAiRtdProvider.json +++ b/metadata/modules/nodalsAiRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.nodals.ai/vendor.json": { - "timestamp": "2026-03-12T23:08:24.494Z", + "timestamp": "2026-03-14T22:02:07.697Z", "disclosures": [ { "identifier": "localStorage", diff --git a/metadata/modules/novatiqIdSystem.json b/metadata/modules/novatiqIdSystem.json index 0a48587fd13..7b3e39972ed 100644 --- a/metadata/modules/novatiqIdSystem.json +++ b/metadata/modules/novatiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://novatiq.com/privacy/iab/novatiq.json": { - "timestamp": "2026-03-12T23:08:26.306Z", + "timestamp": "2026-03-14T22:02:09.124Z", "disclosures": [ { "identifier": "novatiq", diff --git a/metadata/modules/oguryBidAdapter.json b/metadata/modules/oguryBidAdapter.json index c9583ef6b1b..c06a56938c3 100644 --- a/metadata/modules/oguryBidAdapter.json +++ b/metadata/modules/oguryBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://privacy.ogury.co/disclosure.json": { - "timestamp": "2026-03-12T23:08:26.889Z", + "timestamp": "2026-03-14T22:02:09.450Z", "disclosures": [] } }, diff --git a/metadata/modules/omnidexBidAdapter.json b/metadata/modules/omnidexBidAdapter.json index 2848970e619..07c03e06a12 100644 --- a/metadata/modules/omnidexBidAdapter.json +++ b/metadata/modules/omnidexBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.omni-dex.io/devicestorage.json": { - "timestamp": "2026-03-12T23:08:26.949Z", + "timestamp": "2026-03-14T22:02:09.497Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/omsBidAdapter.json b/metadata/modules/omsBidAdapter.json index da2d3c212c6..6dae74d1b1e 100644 --- a/metadata/modules/omsBidAdapter.json +++ b/metadata/modules/omsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.marphezis.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T23:08:27.002Z", + "timestamp": "2026-03-14T22:02:09.582Z", "disclosures": [] } }, diff --git a/metadata/modules/onetagBidAdapter.json b/metadata/modules/onetagBidAdapter.json index 559390ec04c..058ec1b6ae9 100644 --- a/metadata/modules/onetagBidAdapter.json +++ b/metadata/modules/onetagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://onetag-cdn.com/privacy/tcf_storage.json": { - "timestamp": "2026-03-12T23:08:27.002Z", + "timestamp": "2026-03-14T22:02:09.583Z", "disclosures": [ { "identifier": "onetag_sid", diff --git a/metadata/modules/openwebBidAdapter.json b/metadata/modules/openwebBidAdapter.json index 5bf9951f472..f6ccc348df9 100644 --- a/metadata/modules/openwebBidAdapter.json +++ b/metadata/modules/openwebBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-12T23:08:27.271Z", + "timestamp": "2026-03-14T22:02:09.841Z", "disclosures": [] } }, diff --git a/metadata/modules/openxBidAdapter.json b/metadata/modules/openxBidAdapter.json index 3384063c1cb..1a0fa0a700d 100644 --- a/metadata/modules/openxBidAdapter.json +++ b/metadata/modules/openxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.openx.com/device-storage.json": { - "timestamp": "2026-03-12T23:08:27.301Z", + "timestamp": "2026-03-14T22:02:09.883Z", "disclosures": [] } }, diff --git a/metadata/modules/operaadsBidAdapter.json b/metadata/modules/operaadsBidAdapter.json index cdce5936aa0..f085497cfd7 100644 --- a/metadata/modules/operaadsBidAdapter.json +++ b/metadata/modules/operaadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://res.adx.opera.com/dsd.json": { - "timestamp": "2026-03-12T23:08:27.331Z", + "timestamp": "2026-03-14T22:02:10.004Z", "disclosures": [] } }, diff --git a/metadata/modules/optidigitalBidAdapter.json b/metadata/modules/optidigitalBidAdapter.json index 163d763b5ac..683c535c10a 100644 --- a/metadata/modules/optidigitalBidAdapter.json +++ b/metadata/modules/optidigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://scripts.opti-digital.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:27.351Z", + "timestamp": "2026-03-14T22:02:10.171Z", "disclosures": [] } }, diff --git a/metadata/modules/optoutBidAdapter.json b/metadata/modules/optoutBidAdapter.json index 69155fcc870..949e55b93a5 100644 --- a/metadata/modules/optoutBidAdapter.json +++ b/metadata/modules/optoutBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://adserving.optoutadvertising.com/dsd": { - "timestamp": "2026-03-12T23:08:27.391Z", + "timestamp": "2026-03-14T22:02:10.226Z", "disclosures": [] } }, diff --git a/metadata/modules/orbidderBidAdapter.json b/metadata/modules/orbidderBidAdapter.json index 6d44e6e8128..1ce96df724d 100644 --- a/metadata/modules/orbidderBidAdapter.json +++ b/metadata/modules/orbidderBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://orbidder.otto.de/disclosure/dsd.json": { - "timestamp": "2026-03-12T23:08:27.643Z", + "timestamp": "2026-03-14T22:02:10.495Z", "disclosures": [] } }, diff --git a/metadata/modules/outbrainBidAdapter.json b/metadata/modules/outbrainBidAdapter.json index c71b6d9ca56..4e6331002a9 100644 --- a/metadata/modules/outbrainBidAdapter.json +++ b/metadata/modules/outbrainBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.outbrain.com/privacy/wp-json/privacy/v2/devicestorage.json": { - "timestamp": "2026-03-12T23:08:27.934Z", + "timestamp": "2026-03-14T22:02:10.805Z", "disclosures": [ { "identifier": "dicbo_id", diff --git a/metadata/modules/ozoneBidAdapter.json b/metadata/modules/ozoneBidAdapter.json index 767945d7c82..6ce9ad5f1ac 100644 --- a/metadata/modules/ozoneBidAdapter.json +++ b/metadata/modules/ozoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://prebid.the-ozone-project.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:28.179Z", + "timestamp": "2026-03-14T22:02:11.034Z", "disclosures": [] } }, diff --git a/metadata/modules/pairIdSystem.json b/metadata/modules/pairIdSystem.json index f6b33be6f43..30d67c661d2 100644 --- a/metadata/modules/pairIdSystem.json +++ b/metadata/modules/pairIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.gstatic.com/iabtcf/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:28.359Z", + "timestamp": "2026-03-14T22:02:11.199Z", "disclosures": [ { "identifier": "__gads", diff --git a/metadata/modules/panxoBidAdapter.json b/metadata/modules/panxoBidAdapter.json index 2feff926c6d..657a437ac4e 100644 --- a/metadata/modules/panxoBidAdapter.json +++ b/metadata/modules/panxoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.panxo.ai/tcf/device-storage.json": { - "timestamp": "2026-03-12T23:08:28.376Z", + "timestamp": "2026-03-14T22:02:11.220Z", "disclosures": [ { "identifier": "panxo_uid", diff --git a/metadata/modules/performaxBidAdapter.json b/metadata/modules/performaxBidAdapter.json index 21d4a5a2e0a..e18a9d4fe98 100644 --- a/metadata/modules/performaxBidAdapter.json +++ b/metadata/modules/performaxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.performax.cz/device_storage.json": { - "timestamp": "2026-03-12T23:08:28.562Z", + "timestamp": "2026-03-14T22:02:11.434Z", "disclosures": [ { "identifier": "px2uid", diff --git a/metadata/modules/permutiveIdentityManagerIdSystem.json b/metadata/modules/permutiveIdentityManagerIdSystem.json index 995eec3b97e..1d85e6e5923 100644 --- a/metadata/modules/permutiveIdentityManagerIdSystem.json +++ b/metadata/modules/permutiveIdentityManagerIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-12T23:08:28.975Z", + "timestamp": "2026-03-14T22:02:11.848Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/permutiveRtdProvider.json b/metadata/modules/permutiveRtdProvider.json index 1ee8c15c8bb..60bc352fc0b 100644 --- a/metadata/modules/permutiveRtdProvider.json +++ b/metadata/modules/permutiveRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.permutive.app/tcf/tcf.json": { - "timestamp": "2026-03-12T23:08:29.157Z", + "timestamp": "2026-03-14T22:02:12.070Z", "disclosures": [ { "identifier": "_pdfps", diff --git a/metadata/modules/pixfutureBidAdapter.json b/metadata/modules/pixfutureBidAdapter.json index 774bed2c22b..962b2c6a6a7 100644 --- a/metadata/modules/pixfutureBidAdapter.json +++ b/metadata/modules/pixfutureBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.pixfuture.com/vendor-disclosures.json": { - "timestamp": "2026-03-12T23:08:29.162Z", + "timestamp": "2026-03-14T22:02:12.070Z", "disclosures": [] } }, diff --git a/metadata/modules/playdigoBidAdapter.json b/metadata/modules/playdigoBidAdapter.json index e6b20e6efdc..004eaa59c14 100644 --- a/metadata/modules/playdigoBidAdapter.json +++ b/metadata/modules/playdigoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://playdigo.com/file.json": { - "timestamp": "2026-03-12T23:08:29.253Z", + "timestamp": "2026-03-14T22:02:12.136Z", "disclosures": [] } }, diff --git a/metadata/modules/prebid-core.json b/metadata/modules/prebid-core.json index 814e273947c..832a6d38cf8 100644 --- a/metadata/modules/prebid-core.json +++ b/metadata/modules/prebid-core.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/probes.json": { - "timestamp": "2026-03-12T23:06:53.489Z", + "timestamp": "2026-03-14T22:01:05.183Z", "disclosures": [ { "identifier": "_rdc*", @@ -23,7 +23,7 @@ ] }, "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/debugging.json": { - "timestamp": "2026-03-12T23:06:53.490Z", + "timestamp": "2026-03-14T22:01:05.183Z", "disclosures": [ { "identifier": "__*_debugging__", diff --git a/metadata/modules/precisoBidAdapter.json b/metadata/modules/precisoBidAdapter.json index a3b84473e7e..c36ccb1501c 100644 --- a/metadata/modules/precisoBidAdapter.json +++ b/metadata/modules/precisoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://preciso.net/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:29.427Z", + "timestamp": "2026-03-14T22:02:12.308Z", "disclosures": [ { "identifier": "XXXXX_viewnew", diff --git a/metadata/modules/prismaBidAdapter.json b/metadata/modules/prismaBidAdapter.json index 91464af95ef..fac8ccdb714 100644 --- a/metadata/modules/prismaBidAdapter.json +++ b/metadata/modules/prismaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://fast.nexx360.io/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:29.668Z", + "timestamp": "2026-03-14T22:02:12.522Z", "disclosures": [] } }, diff --git a/metadata/modules/programmaticXBidAdapter.json b/metadata/modules/programmaticXBidAdapter.json index 7c89c1f8793..335a7394f3a 100644 --- a/metadata/modules/programmaticXBidAdapter.json +++ b/metadata/modules/programmaticXBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://progrtb.com/tcf-vendor-disclosures.json": { - "timestamp": "2026-03-12T23:08:29.668Z", + "timestamp": "2026-03-14T22:02:12.522Z", "disclosures": [] } }, diff --git a/metadata/modules/proxistoreBidAdapter.json b/metadata/modules/proxistoreBidAdapter.json index 428f24795a4..f351ac36c56 100644 --- a/metadata/modules/proxistoreBidAdapter.json +++ b/metadata/modules/proxistoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://abs.proxistore.com/assets/json/proxistore_device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:29.716Z", + "timestamp": "2026-03-14T22:02:12.576Z", "disclosures": [] } }, diff --git a/metadata/modules/publinkIdSystem.json b/metadata/modules/publinkIdSystem.json index 39fa4ce12ce..e71f1f4cb21 100644 --- a/metadata/modules/publinkIdSystem.json +++ b/metadata/modules/publinkIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s-usweb.dotomi.com/assets/js/taggy-js/2.18.9/device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:30.247Z", + "timestamp": "2026-03-14T22:02:12.952Z", "disclosures": [ { "identifier": "dtm_status", diff --git a/metadata/modules/pubmaticBidAdapter.json b/metadata/modules/pubmaticBidAdapter.json index b765455d5c5..94f71bfdbfe 100644 --- a/metadata/modules/pubmaticBidAdapter.json +++ b/metadata/modules/pubmaticBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:30.247Z", + "timestamp": "2026-03-14T22:02:12.953Z", "disclosures": [] } }, diff --git a/metadata/modules/pubmaticIdSystem.json b/metadata/modules/pubmaticIdSystem.json index 0e11b84000d..310dca55b6b 100644 --- a/metadata/modules/pubmaticIdSystem.json +++ b/metadata/modules/pubmaticIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pubmatic.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:30.281Z", + "timestamp": "2026-03-14T22:02:12.974Z", "disclosures": [] } }, diff --git a/metadata/modules/pubstackBidAdapter.json b/metadata/modules/pubstackBidAdapter.json index c79764d4b69..a0e92337802 100644 --- a/metadata/modules/pubstackBidAdapter.json +++ b/metadata/modules/pubstackBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.pbstck.com/privacy_policies/device_storage_disclosures.json": { - "timestamp": "2026-03-12T23:08:30.307Z", + "timestamp": "2026-03-14T22:02:13.015Z", "disclosures": [] } }, diff --git a/metadata/modules/pulsepointBidAdapter.json b/metadata/modules/pulsepointBidAdapter.json index 348f5e96539..03c8fa2cd67 100644 --- a/metadata/modules/pulsepointBidAdapter.json +++ b/metadata/modules/pulsepointBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bh.contextweb.com/tcf/vendorInfo.json": { - "timestamp": "2026-03-12T23:08:30.308Z", + "timestamp": "2026-03-14T22:02:13.016Z", "disclosures": [] } }, diff --git a/metadata/modules/r2b2BidAdapter.json b/metadata/modules/r2b2BidAdapter.json index 698ef82d928..d674b5e77ac 100644 --- a/metadata/modules/r2b2BidAdapter.json +++ b/metadata/modules/r2b2BidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://delivery.r2b2.io/cookie_disclosure": { - "timestamp": "2026-03-12T23:08:30.324Z", + "timestamp": "2026-03-14T22:02:13.033Z", "disclosures": [ { "identifier": "AdTrack-hide-*", diff --git a/metadata/modules/readpeakBidAdapter.json b/metadata/modules/readpeakBidAdapter.json index 61fca3ddb3b..8c581cc2e92 100644 --- a/metadata/modules/readpeakBidAdapter.json +++ b/metadata/modules/readpeakBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static.readpeak.com/tcf/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:30.745Z", + "timestamp": "2026-03-14T22:02:13.492Z", "disclosures": [ { "identifier": "rp_uidfp", diff --git a/metadata/modules/relayBidAdapter.json b/metadata/modules/relayBidAdapter.json index 118f8ee3809..505ea7f747f 100644 --- a/metadata/modules/relayBidAdapter.json +++ b/metadata/modules/relayBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://relay42.com/hubfs/raw_assets/public/IAB.json": { - "timestamp": "2026-03-12T23:08:30.788Z", + "timestamp": "2026-03-14T22:02:13.832Z", "disclosures": null } }, diff --git a/metadata/modules/relevantdigitalBidAdapter.json b/metadata/modules/relevantdigitalBidAdapter.json index 717f504aa72..674247e719e 100644 --- a/metadata/modules/relevantdigitalBidAdapter.json +++ b/metadata/modules/relevantdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.relevant-digital.com/resources/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:31.502Z", + "timestamp": "2026-03-14T22:02:14.478Z", "disclosures": [] } }, diff --git a/metadata/modules/resetdigitalBidAdapter.json b/metadata/modules/resetdigitalBidAdapter.json index b8db18e8e0e..dddacf422bd 100644 --- a/metadata/modules/resetdigitalBidAdapter.json +++ b/metadata/modules/resetdigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resetdigital.co/GDPR-TCF.json": { - "timestamp": "2026-03-12T23:08:31.656Z", + "timestamp": "2026-03-14T22:02:14.777Z", "disclosures": [] } }, diff --git a/metadata/modules/responsiveAdsBidAdapter.json b/metadata/modules/responsiveAdsBidAdapter.json index b5b76647d7b..b7687be0b8a 100644 --- a/metadata/modules/responsiveAdsBidAdapter.json +++ b/metadata/modules/responsiveAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://publish.responsiveads.com/tcf/tcf-v2.json": { - "timestamp": "2026-03-12T23:08:31.692Z", + "timestamp": "2026-03-14T22:02:14.824Z", "disclosures": [] } }, diff --git a/metadata/modules/revcontentBidAdapter.json b/metadata/modules/revcontentBidAdapter.json index b1033f2976b..e16cb5ea621 100644 --- a/metadata/modules/revcontentBidAdapter.json +++ b/metadata/modules/revcontentBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sothebys.revcontent.com/static/device_storage.json": { - "timestamp": "2026-03-12T23:08:31.707Z", + "timestamp": "2026-03-14T22:02:14.860Z", "disclosures": [ { "identifier": "__ID", diff --git a/metadata/modules/revnewBidAdapter.json b/metadata/modules/revnewBidAdapter.json index 60c89b747ee..6c83c177af3 100644 --- a/metadata/modules/revnewBidAdapter.json +++ b/metadata/modules/revnewBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mediafuse.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:31.723Z", + "timestamp": "2026-03-14T22:02:14.879Z", "disclosures": [] } }, diff --git a/metadata/modules/rhythmoneBidAdapter.json b/metadata/modules/rhythmoneBidAdapter.json index d77ddb44dc7..d57b3c0c37a 100644 --- a/metadata/modules/rhythmoneBidAdapter.json +++ b/metadata/modules/rhythmoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:31.783Z", + "timestamp": "2026-03-14T22:02:14.948Z", "disclosures": [] } }, diff --git a/metadata/modules/richaudienceBidAdapter.json b/metadata/modules/richaudienceBidAdapter.json index 3e057285d43..f5f6da4f899 100644 --- a/metadata/modules/richaudienceBidAdapter.json +++ b/metadata/modules/richaudienceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdnj.richaudience.com/52a26ab9400b2a9f5aabfa20acf3196g.json": { - "timestamp": "2026-03-12T23:08:32.102Z", + "timestamp": "2026-03-14T22:02:15.182Z", "disclosures": [] } }, diff --git a/metadata/modules/riseBidAdapter.json b/metadata/modules/riseBidAdapter.json index acb704c0a08..a2aafc0e329 100644 --- a/metadata/modules/riseBidAdapter.json +++ b/metadata/modules/riseBidAdapter.json @@ -2,11 +2,11 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://d2pm7iglz0b6eq.cloudfront.net/RiseDeviceStorage.json": { - "timestamp": "2026-03-12T23:08:32.161Z", + "timestamp": "2026-03-14T22:02:15.255Z", "disclosures": [] }, "https://spotim-prd-static-assets.s3.amazonaws.com/iab/device-storage.json": { - "timestamp": "2026-03-12T23:08:32.161Z", + "timestamp": "2026-03-14T22:02:15.255Z", "disclosures": [] } }, diff --git a/metadata/modules/rixengineBidAdapter.json b/metadata/modules/rixengineBidAdapter.json index 3aaf42a9f17..7cbe170097c 100644 --- a/metadata/modules/rixengineBidAdapter.json +++ b/metadata/modules/rixengineBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.algorix.co/gdpr-disclosure.json": { - "timestamp": "2026-03-12T23:08:32.162Z", + "timestamp": "2026-03-14T22:02:15.256Z", "disclosures": [] } }, diff --git a/metadata/modules/rtbhouseBidAdapter.json b/metadata/modules/rtbhouseBidAdapter.json index c0469a2e18f..9d194a92ea1 100644 --- a/metadata/modules/rtbhouseBidAdapter.json +++ b/metadata/modules/rtbhouseBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://rtbhouse.com/DeviceStorage.json": { - "timestamp": "2026-03-12T23:08:32.183Z", + "timestamp": "2026-03-14T22:02:15.326Z", "disclosures": [ { "identifier": "_rtbh.*", diff --git a/metadata/modules/rubiconBidAdapter.json b/metadata/modules/rubiconBidAdapter.json index 0667103ba03..65f0bcf2768 100644 --- a/metadata/modules/rubiconBidAdapter.json +++ b/metadata/modules/rubiconBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.rubiconproject.com/dvplus/devicestoragedisclosure.json": { - "timestamp": "2026-03-12T23:08:32.640Z", + "timestamp": "2026-03-14T22:02:15.622Z", "disclosures": [] } }, diff --git a/metadata/modules/scaliburBidAdapter.json b/metadata/modules/scaliburBidAdapter.json index 2c8cb6075eb..0c3ee7b2a64 100644 --- a/metadata/modules/scaliburBidAdapter.json +++ b/metadata/modules/scaliburBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://legal.overwolf.com/docs/overwolf/website/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:32.640Z", + "timestamp": "2026-03-14T22:02:15.622Z", "disclosures": [ { "identifier": "scluid", diff --git a/metadata/modules/screencoreBidAdapter.json b/metadata/modules/screencoreBidAdapter.json index 9a577aa57a0..079c53814c3 100644 --- a/metadata/modules/screencoreBidAdapter.json +++ b/metadata/modules/screencoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://screencore.io/tcf.json": { - "timestamp": "2026-03-12T23:08:32.653Z", + "timestamp": "2026-03-14T22:02:15.638Z", "disclosures": [] } }, diff --git a/metadata/modules/seedingAllianceBidAdapter.json b/metadata/modules/seedingAllianceBidAdapter.json index d60004aa12f..da6a087f60e 100644 --- a/metadata/modules/seedingAllianceBidAdapter.json +++ b/metadata/modules/seedingAllianceBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s.nativendo.de/cdn/asset/tcf/purpose-specific-storage-and-access-information.json": { - "timestamp": "2026-03-12T23:08:32.698Z", + "timestamp": "2026-03-14T22:02:15.682Z", "disclosures": [] } }, diff --git a/metadata/modules/seedtagBidAdapter.json b/metadata/modules/seedtagBidAdapter.json index 851af0ba8bf..1430504b5e3 100644 --- a/metadata/modules/seedtagBidAdapter.json +++ b/metadata/modules/seedtagBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.seedtag.com/vendor.json": { - "timestamp": "2026-03-12T23:08:32.722Z", + "timestamp": "2026-03-14T22:02:15.732Z", "disclosures": [] } }, diff --git a/metadata/modules/semantiqRtdProvider.json b/metadata/modules/semantiqRtdProvider.json index cad8ee98bea..85ec5fe5316 100644 --- a/metadata/modules/semantiqRtdProvider.json +++ b/metadata/modules/semantiqRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-12T23:08:32.722Z", + "timestamp": "2026-03-14T22:02:15.732Z", "disclosures": [] } }, diff --git a/metadata/modules/sevioBidAdapter.json b/metadata/modules/sevioBidAdapter.json index 319c63422fd..b72e58a0347 100644 --- a/metadata/modules/sevioBidAdapter.json +++ b/metadata/modules/sevioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sevio.com/tcf.json": { - "timestamp": "2026-03-12T23:08:32.815Z", + "timestamp": "2026-03-14T22:02:15.817Z", "disclosures": [] } }, diff --git a/metadata/modules/sharedIdSystem.json b/metadata/modules/sharedIdSystem.json index f54e17d525e..19ebfc6653e 100644 --- a/metadata/modules/sharedIdSystem.json +++ b/metadata/modules/sharedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-12T23:08:32.981Z", + "timestamp": "2026-03-14T22:02:15.941Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/sharethroughBidAdapter.json b/metadata/modules/sharethroughBidAdapter.json index 26a1c5a5805..e930f3bdbf6 100644 --- a/metadata/modules/sharethroughBidAdapter.json +++ b/metadata/modules/sharethroughBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://assets.sharethrough.com/gvl.json": { - "timestamp": "2026-03-12T23:08:32.982Z", + "timestamp": "2026-03-14T22:02:15.942Z", "disclosures": [] } }, diff --git a/metadata/modules/showheroes-bsBidAdapter.json b/metadata/modules/showheroes-bsBidAdapter.json index fb503b4c6e3..377e97140e2 100644 --- a/metadata/modules/showheroes-bsBidAdapter.json +++ b/metadata/modules/showheroes-bsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://static-origin.showheroes.com/gvl_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:33.004Z", + "timestamp": "2026-03-14T22:02:15.968Z", "disclosures": [] } }, diff --git a/metadata/modules/silvermobBidAdapter.json b/metadata/modules/silvermobBidAdapter.json index eeea13e4f81..91c504f7441 100644 --- a/metadata/modules/silvermobBidAdapter.json +++ b/metadata/modules/silvermobBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://silvermob.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:33.468Z", + "timestamp": "2026-03-14T22:02:16.412Z", "disclosures": [] } }, diff --git a/metadata/modules/sirdataRtdProvider.json b/metadata/modules/sirdataRtdProvider.json index e95e6c322e4..7c1d268f5dd 100644 --- a/metadata/modules/sirdataRtdProvider.json +++ b/metadata/modules/sirdataRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sirdata.eu/sirdata_device_storage_disclosure.json": { - "timestamp": "2026-03-12T23:08:33.480Z", + "timestamp": "2026-03-14T22:02:16.429Z", "disclosures": [] } }, diff --git a/metadata/modules/smaatoBidAdapter.json b/metadata/modules/smaatoBidAdapter.json index f629ce94df0..b22dad578f5 100644 --- a/metadata/modules/smaatoBidAdapter.json +++ b/metadata/modules/smaatoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://resources.smaato.com/hubfs/Smaato/IAB/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:33.781Z", + "timestamp": "2026-03-14T22:02:16.732Z", "disclosures": [] } }, diff --git a/metadata/modules/smartadserverBidAdapter.json b/metadata/modules/smartadserverBidAdapter.json index 8382a3dc5fa..48b7c70c1c0 100644 --- a/metadata/modules/smartadserverBidAdapter.json +++ b/metadata/modules/smartadserverBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://apps.smartadserver.com/device-storage-disclosures/equativDeviceStorageDisclosures.json": { - "timestamp": "2026-03-12T23:08:33.859Z", + "timestamp": "2026-03-14T22:02:16.809Z", "disclosures": [] } }, diff --git a/metadata/modules/smartxBidAdapter.json b/metadata/modules/smartxBidAdapter.json index 45c75b5553b..aff5374272d 100644 --- a/metadata/modules/smartxBidAdapter.json +++ b/metadata/modules/smartxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.smartclip.net/iab/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:33.860Z", + "timestamp": "2026-03-14T22:02:16.810Z", "disclosures": [] } }, diff --git a/metadata/modules/smartyadsBidAdapter.json b/metadata/modules/smartyadsBidAdapter.json index 70506e2e382..c35c87b17ef 100644 --- a/metadata/modules/smartyadsBidAdapter.json +++ b/metadata/modules/smartyadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smartyads.com/tcf.json": { - "timestamp": "2026-03-12T23:08:33.878Z", + "timestamp": "2026-03-14T22:02:16.846Z", "disclosures": [] } }, diff --git a/metadata/modules/smilewantedBidAdapter.json b/metadata/modules/smilewantedBidAdapter.json index 0dbc4d5ae08..0e5dfe73669 100644 --- a/metadata/modules/smilewantedBidAdapter.json +++ b/metadata/modules/smilewantedBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://smilewanted.com/vendor-device-storage-disclosures.json": { - "timestamp": "2026-03-12T23:08:33.923Z", + "timestamp": "2026-03-14T22:02:16.909Z", "disclosures": [] } }, diff --git a/metadata/modules/snigelBidAdapter.json b/metadata/modules/snigelBidAdapter.json index 6540fc1e34b..07f97e29d0a 100644 --- a/metadata/modules/snigelBidAdapter.json +++ b/metadata/modules/snigelBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.snigelweb.com/gvl/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:34.058Z", + "timestamp": "2026-03-14T22:02:17.360Z", "disclosures": [] } }, diff --git a/metadata/modules/sonaradsBidAdapter.json b/metadata/modules/sonaradsBidAdapter.json index 8c1110100c4..b227a79b25e 100644 --- a/metadata/modules/sonaradsBidAdapter.json +++ b/metadata/modules/sonaradsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bridgeupp.com/device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:34.094Z", + "timestamp": "2026-03-14T22:02:17.405Z", "disclosures": [] } }, diff --git a/metadata/modules/sonobiBidAdapter.json b/metadata/modules/sonobiBidAdapter.json index 873f8ac006c..06b3a2fd635 100644 --- a/metadata/modules/sonobiBidAdapter.json +++ b/metadata/modules/sonobiBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://sonobi.com/tcf2-device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:34.315Z", + "timestamp": "2026-03-14T22:02:17.642Z", "disclosures": [] } }, diff --git a/metadata/modules/sovrnBidAdapter.json b/metadata/modules/sovrnBidAdapter.json index c59398e0424..423e5753aa9 100644 --- a/metadata/modules/sovrnBidAdapter.json +++ b/metadata/modules/sovrnBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.sovrn.com/tcf-cookie-disclosure/disclosure.json": { - "timestamp": "2026-03-12T23:08:34.614Z", + "timestamp": "2026-03-14T22:02:17.877Z", "disclosures": [] } }, diff --git a/metadata/modules/sparteoBidAdapter.json b/metadata/modules/sparteoBidAdapter.json index 6d4b498dfa1..ad551f9d5e9 100644 --- a/metadata/modules/sparteoBidAdapter.json +++ b/metadata/modules/sparteoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:34.640Z", + "timestamp": "2026-03-14T22:02:17.899Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/ssmasBidAdapter.json b/metadata/modules/ssmasBidAdapter.json index e2b8bba3e02..02f783d187c 100644 --- a/metadata/modules/ssmasBidAdapter.json +++ b/metadata/modules/ssmasBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://semseoymas.com/iab.json": { - "timestamp": "2026-03-12T23:08:35.024Z", + "timestamp": "2026-03-14T22:02:18.173Z", "disclosures": null } }, diff --git a/metadata/modules/sspBCBidAdapter.json b/metadata/modules/sspBCBidAdapter.json index 0d7ffe8dc10..6e3ba064625 100644 --- a/metadata/modules/sspBCBidAdapter.json +++ b/metadata/modules/sspBCBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ssp.wp.pl/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:35.666Z", + "timestamp": "2026-03-14T22:02:18.816Z", "disclosures": null } }, diff --git a/metadata/modules/stackadaptBidAdapter.json b/metadata/modules/stackadaptBidAdapter.json index cb95c1af2d4..9d9d745a1b9 100644 --- a/metadata/modules/stackadaptBidAdapter.json +++ b/metadata/modules/stackadaptBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://s3.amazonaws.com/stackadapt_public/disclosures.json": { - "timestamp": "2026-03-12T23:08:35.666Z", + "timestamp": "2026-03-14T22:02:18.817Z", "disclosures": [ { "identifier": "sa-camp-*", diff --git a/metadata/modules/startioBidAdapter.json b/metadata/modules/startioBidAdapter.json index 9bd22e20ca2..c77c2eccdbd 100644 --- a/metadata/modules/startioBidAdapter.json +++ b/metadata/modules/startioBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://info.startappservice.com/tcf/start.io_domains.json": { - "timestamp": "2026-03-12T23:08:35.695Z", + "timestamp": "2026-03-14T22:02:18.857Z", "disclosures": [] } }, diff --git a/metadata/modules/stroeerCoreBidAdapter.json b/metadata/modules/stroeerCoreBidAdapter.json index 52c97b0c17a..9e59f452069 100644 --- a/metadata/modules/stroeerCoreBidAdapter.json +++ b/metadata/modules/stroeerCoreBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.stroeer.de/StroeerSSP_deviceStorage.json": { - "timestamp": "2026-03-12T23:08:35.710Z", + "timestamp": "2026-03-14T22:02:18.876Z", "disclosures": [] } }, diff --git a/metadata/modules/stvBidAdapter.json b/metadata/modules/stvBidAdapter.json index 3bdb29742c1..fb3a6050139 100644 --- a/metadata/modules/stvBidAdapter.json +++ b/metadata/modules/stvBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.adtech.app/gen/deviceStorageDisclosure/stv.json": { - "timestamp": "2026-03-12T23:08:35.856Z", + "timestamp": "2026-03-14T22:02:19.202Z", "disclosures": [] } }, diff --git a/metadata/modules/sublimeBidAdapter.json b/metadata/modules/sublimeBidAdapter.json index da12e9c4075..ddeaaa5dca5 100644 --- a/metadata/modules/sublimeBidAdapter.json +++ b/metadata/modules/sublimeBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://gdpr.ayads.co/cookiepolicy.json": { - "timestamp": "2026-03-12T23:08:36.483Z", + "timestamp": "2026-03-14T22:02:19.869Z", "disclosures": [ { "identifier": "dnt", diff --git a/metadata/modules/taboolaBidAdapter.json b/metadata/modules/taboolaBidAdapter.json index 0a707f7db5f..b3f833e3876 100644 --- a/metadata/modules/taboolaBidAdapter.json +++ b/metadata/modules/taboolaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-12T23:08:36.739Z", + "timestamp": "2026-03-14T22:02:20.135Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/taboolaIdSystem.json b/metadata/modules/taboolaIdSystem.json index 644320f8aa4..6bed602a7de 100644 --- a/metadata/modules/taboolaIdSystem.json +++ b/metadata/modules/taboolaIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://accessrequest.taboola.com/iab-tcf-v2-disclosure.json": { - "timestamp": "2026-03-12T23:08:36.964Z", + "timestamp": "2026-03-14T22:02:20.348Z", "disclosures": [ { "identifier": "trc_cookie_storage", diff --git a/metadata/modules/tadvertisingBidAdapter.json b/metadata/modules/tadvertisingBidAdapter.json index 7c43b990245..7ecc943ebc3 100644 --- a/metadata/modules/tadvertisingBidAdapter.json +++ b/metadata/modules/tadvertisingBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tcf.emetriq.de/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:36.964Z", + "timestamp": "2026-03-14T22:02:20.348Z", "disclosures": [] } }, diff --git a/metadata/modules/tappxBidAdapter.json b/metadata/modules/tappxBidAdapter.json index 071896b3a49..0ac1d4fd3fa 100644 --- a/metadata/modules/tappxBidAdapter.json +++ b/metadata/modules/tappxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://tappx.com/devicestorage.json": { - "timestamp": "2026-03-12T23:08:37.058Z", + "timestamp": "2026-03-14T22:02:20.368Z", "disclosures": [] } }, diff --git a/metadata/modules/targetVideoBidAdapter.json b/metadata/modules/targetVideoBidAdapter.json index cbc4b5e3a72..84ee2c09d9a 100644 --- a/metadata/modules/targetVideoBidAdapter.json +++ b/metadata/modules/targetVideoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://target-video.com/vendors-device-storage-and-operational-disclosures.json": { - "timestamp": "2026-03-12T23:08:37.087Z", + "timestamp": "2026-03-14T22:02:20.397Z", "disclosures": [ { "identifier": "brid_location", diff --git a/metadata/modules/teadsBidAdapter.json b/metadata/modules/teadsBidAdapter.json index 4b3118c201f..af918455c33 100644 --- a/metadata/modules/teadsBidAdapter.json +++ b/metadata/modules/teadsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.087Z", + "timestamp": "2026-03-14T22:02:20.397Z", "disclosures": [] } }, diff --git a/metadata/modules/teadsIdSystem.json b/metadata/modules/teadsIdSystem.json index eb2202852a4..ba3b6b67ec1 100644 --- a/metadata/modules/teadsIdSystem.json +++ b/metadata/modules/teadsIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://iab-cookie-disclosure.teads.tv/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.103Z", + "timestamp": "2026-03-14T22:02:20.421Z", "disclosures": [] } }, diff --git a/metadata/modules/tealBidAdapter.json b/metadata/modules/tealBidAdapter.json index e997209791f..b447dfefac5 100644 --- a/metadata/modules/tealBidAdapter.json +++ b/metadata/modules/tealBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://c.bids.ws/iab/disclosures.json": { - "timestamp": "2026-03-12T23:08:37.103Z", + "timestamp": "2026-03-14T22:02:20.421Z", "disclosures": [] } }, diff --git a/metadata/modules/tncIdSystem.json b/metadata/modules/tncIdSystem.json index 7d86176897f..f146936223f 100644 --- a/metadata/modules/tncIdSystem.json +++ b/metadata/modules/tncIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://js.tncid.app/iab-tcf-device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:37.149Z", + "timestamp": "2026-03-14T22:02:20.457Z", "disclosures": [] } }, diff --git a/metadata/modules/topicsFpdModule.json b/metadata/modules/topicsFpdModule.json index c9d5568690e..c1dd75347e8 100644 --- a/metadata/modules/topicsFpdModule.json +++ b/metadata/modules/topicsFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/topicsFpdModule.json": { - "timestamp": "2026-03-12T23:06:53.490Z", + "timestamp": "2026-03-14T22:01:05.183Z", "disclosures": [ { "identifier": "prebid:topics", diff --git a/metadata/modules/toponBidAdapter.json b/metadata/modules/toponBidAdapter.json index 4524251e34b..95264b4d75c 100644 --- a/metadata/modules/toponBidAdapter.json +++ b/metadata/modules/toponBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://mores.toponad.net/tmp/tpn/toponads_tcf_disclosure.json": { - "timestamp": "2026-03-12T23:08:37.167Z", + "timestamp": "2026-03-14T22:02:20.479Z", "disclosures": [] } }, diff --git a/metadata/modules/tripleliftBidAdapter.json b/metadata/modules/tripleliftBidAdapter.json index 03b331982d0..e27a19f128d 100644 --- a/metadata/modules/tripleliftBidAdapter.json +++ b/metadata/modules/tripleliftBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://triplelift.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.203Z", + "timestamp": "2026-03-14T22:02:20.525Z", "disclosures": [] } }, diff --git a/metadata/modules/ttdBidAdapter.json b/metadata/modules/ttdBidAdapter.json index c343c4d0e4a..09b48d5897a 100644 --- a/metadata/modules/ttdBidAdapter.json +++ b/metadata/modules/ttdBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T23:08:37.241Z", + "timestamp": "2026-03-14T22:02:20.555Z", "disclosures": [] } }, diff --git a/metadata/modules/twistDigitalBidAdapter.json b/metadata/modules/twistDigitalBidAdapter.json index 6d25355743e..b4b49827360 100644 --- a/metadata/modules/twistDigitalBidAdapter.json +++ b/metadata/modules/twistDigitalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://twistdigital.net/iab.json": { - "timestamp": "2026-03-12T23:08:37.241Z", + "timestamp": "2026-03-14T22:02:20.556Z", "disclosures": [ { "identifier": "vdzj1_{id}", diff --git a/metadata/modules/underdogmediaBidAdapter.json b/metadata/modules/underdogmediaBidAdapter.json index a7ad18fb94c..d36661239fa 100644 --- a/metadata/modules/underdogmediaBidAdapter.json +++ b/metadata/modules/underdogmediaBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.underdog.media/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.298Z", + "timestamp": "2026-03-14T22:02:20.608Z", "disclosures": [] } }, diff --git a/metadata/modules/undertoneBidAdapter.json b/metadata/modules/undertoneBidAdapter.json index f3696ffd6f9..08e33c8fda6 100644 --- a/metadata/modules/undertoneBidAdapter.json +++ b/metadata/modules/undertoneBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.undertone.com/js/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.316Z", + "timestamp": "2026-03-14T22:02:20.627Z", "disclosures": [] } }, diff --git a/metadata/modules/unifiedIdSystem.json b/metadata/modules/unifiedIdSystem.json index a24a207bf7c..c30afe5d6cf 100644 --- a/metadata/modules/unifiedIdSystem.json +++ b/metadata/modules/unifiedIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ttd-misc-public-assets.s3.us-west-2.amazonaws.com/deviceStorageDisclosureURL.json": { - "timestamp": "2026-03-12T23:08:37.326Z", + "timestamp": "2026-03-14T22:02:20.812Z", "disclosures": [] } }, diff --git a/metadata/modules/unrulyBidAdapter.json b/metadata/modules/unrulyBidAdapter.json index f314503aad4..a5652561f1c 100644 --- a/metadata/modules/unrulyBidAdapter.json +++ b/metadata/modules/unrulyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://video.unrulymedia.com/deviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:37.327Z", + "timestamp": "2026-03-14T22:02:20.813Z", "disclosures": [] } }, diff --git a/metadata/modules/userId.json b/metadata/modules/userId.json index 02321da2ddd..54aeb8f8b2e 100644 --- a/metadata/modules/userId.json +++ b/metadata/modules/userId.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/userId-optout.json": { - "timestamp": "2026-03-12T23:06:53.492Z", + "timestamp": "2026-03-14T22:01:05.186Z", "disclosures": [ { "identifier": "_pbjs_id_optout", diff --git a/metadata/modules/utiqIdSystem.json b/metadata/modules/utiqIdSystem.json index 48b958e6684..f7202c55338 100644 --- a/metadata/modules/utiqIdSystem.json +++ b/metadata/modules/utiqIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:37.327Z", + "timestamp": "2026-03-14T22:02:20.813Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/utiqMtpIdSystem.json b/metadata/modules/utiqMtpIdSystem.json index f7714f2496e..59aca40ea45 100644 --- a/metadata/modules/utiqMtpIdSystem.json +++ b/metadata/modules/utiqMtpIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/modules/utiqDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:37.327Z", + "timestamp": "2026-03-14T22:02:20.814Z", "disclosures": [ { "identifier": "utiqPass", diff --git a/metadata/modules/validationFpdModule.json b/metadata/modules/validationFpdModule.json index 53d8fa66502..e7fdb0b4a7f 100644 --- a/metadata/modules/validationFpdModule.json +++ b/metadata/modules/validationFpdModule.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.jsdelivr.net/gh/prebid/Prebid.js/metadata/disclosures/prebid/sharedId-optout.json": { - "timestamp": "2026-03-12T23:06:53.491Z", + "timestamp": "2026-03-14T22:01:05.184Z", "disclosures": [ { "identifier": "_pubcid_optout", diff --git a/metadata/modules/valuadBidAdapter.json b/metadata/modules/valuadBidAdapter.json index f70c7ecbc43..3cddb70df24 100644 --- a/metadata/modules/valuadBidAdapter.json +++ b/metadata/modules/valuadBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.valuad.cloud/tcfdevice.json": { - "timestamp": "2026-03-12T23:08:37.327Z", + "timestamp": "2026-03-14T22:02:20.814Z", "disclosures": [] } }, diff --git a/metadata/modules/vidazooBidAdapter.json b/metadata/modules/vidazooBidAdapter.json index 1d7b7cf92e7..f6d1cd6ebc8 100644 --- a/metadata/modules/vidazooBidAdapter.json +++ b/metadata/modules/vidazooBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidazoo.com/gdpr-tcf/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:37.492Z", + "timestamp": "2026-03-14T22:02:21.033Z", "disclosures": [ { "identifier": "ck48wz12sqj7", diff --git a/metadata/modules/vidoomyBidAdapter.json b/metadata/modules/vidoomyBidAdapter.json index 75176ce6615..e8220d56bc8 100644 --- a/metadata/modules/vidoomyBidAdapter.json +++ b/metadata/modules/vidoomyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vidoomy.com/storageurl/devicestoragediscurl.json": { - "timestamp": "2026-03-12T23:08:37.564Z", + "timestamp": "2026-03-14T22:02:21.092Z", "disclosures": [] } }, diff --git a/metadata/modules/viouslyBidAdapter.json b/metadata/modules/viouslyBidAdapter.json index 5280246976d..87159daf7f8 100644 --- a/metadata/modules/viouslyBidAdapter.json +++ b/metadata/modules/viouslyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://bid.bricks-co.com/.well-known/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:38.059Z", + "timestamp": "2026-03-14T22:02:21.621Z", "disclosures": [ { "identifier": "fastCMP-addtlConsent", diff --git a/metadata/modules/visxBidAdapter.json b/metadata/modules/visxBidAdapter.json index 8c3aeb35775..2db2d7789a8 100644 --- a/metadata/modules/visxBidAdapter.json +++ b/metadata/modules/visxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.yoc.com/visx/sellers/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:38.060Z", + "timestamp": "2026-03-14T22:02:21.621Z", "disclosures": [ { "identifier": "__vads", diff --git a/metadata/modules/vlybyBidAdapter.json b/metadata/modules/vlybyBidAdapter.json index 96b677c1965..5eb93b35b84 100644 --- a/metadata/modules/vlybyBidAdapter.json +++ b/metadata/modules/vlybyBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vlyby.com/conf/iab/gvl.json": { - "timestamp": "2026-03-12T23:08:38.603Z", + "timestamp": "2026-03-14T22:02:21.914Z", "disclosures": [] } }, diff --git a/metadata/modules/voxBidAdapter.json b/metadata/modules/voxBidAdapter.json index d6589dc61b9..e40b19232f2 100644 --- a/metadata/modules/voxBidAdapter.json +++ b/metadata/modules/voxBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://st.hybrid.ai/policy/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:38.805Z", + "timestamp": "2026-03-14T22:02:22.260Z", "disclosures": [] } }, diff --git a/metadata/modules/vrtcalBidAdapter.json b/metadata/modules/vrtcalBidAdapter.json index 5dda95e8954..254c2781ce5 100644 --- a/metadata/modules/vrtcalBidAdapter.json +++ b/metadata/modules/vrtcalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://vrtcal.com/docs/gdpr-tcf-disclosures.json": { - "timestamp": "2026-03-12T23:08:38.805Z", + "timestamp": "2026-03-14T22:02:22.260Z", "disclosures": [] } }, diff --git a/metadata/modules/vuukleBidAdapter.json b/metadata/modules/vuukleBidAdapter.json index c971f4c74a9..cfec24f1f1f 100644 --- a/metadata/modules/vuukleBidAdapter.json +++ b/metadata/modules/vuukleBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn.vuukle.com/data-privacy/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:38.818Z", + "timestamp": "2026-03-14T22:02:22.276Z", "disclosures": [ { "identifier": "vuukle_token", diff --git a/metadata/modules/weboramaRtdProvider.json b/metadata/modules/weboramaRtdProvider.json index 6fbb3823928..311a492a314 100644 --- a/metadata/modules/weboramaRtdProvider.json +++ b/metadata/modules/weboramaRtdProvider.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cstatic.weborama.fr/tcf/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:39.112Z", + "timestamp": "2026-03-14T22:02:22.551Z", "disclosures": [] } }, diff --git a/metadata/modules/welectBidAdapter.json b/metadata/modules/welectBidAdapter.json index 989d5ce9e23..580ef728ba0 100644 --- a/metadata/modules/welectBidAdapter.json +++ b/metadata/modules/welectBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://www.welect.de/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:39.395Z", + "timestamp": "2026-03-14T22:02:22.712Z", "disclosures": [] } }, diff --git a/metadata/modules/yahooAdsBidAdapter.json b/metadata/modules/yahooAdsBidAdapter.json index 32dc22359b5..5d0c8795bda 100644 --- a/metadata/modules/yahooAdsBidAdapter.json +++ b/metadata/modules/yahooAdsBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://meta.legal.yahoo.com/iab-tcf/v2/device-storage-disclosure.json": { - "timestamp": "2026-03-12T23:08:39.851Z", + "timestamp": "2026-03-14T22:02:23.088Z", "disclosures": [ { "identifier": "vmcid", diff --git a/metadata/modules/yaleoBidAdapter.json b/metadata/modules/yaleoBidAdapter.json index e04bbb7cefc..1baceb88c18 100644 --- a/metadata/modules/yaleoBidAdapter.json +++ b/metadata/modules/yaleoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://audienzz.com/device_storage_disclosure_vendor_783.json": { - "timestamp": "2026-03-12T23:08:39.851Z", + "timestamp": "2026-03-14T22:02:23.088Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldlabBidAdapter.json b/metadata/modules/yieldlabBidAdapter.json index 797c988da96..b439a5d0bda 100644 --- a/metadata/modules/yieldlabBidAdapter.json +++ b/metadata/modules/yieldlabBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://ad.yieldlab.net/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:39.852Z", + "timestamp": "2026-03-14T22:02:23.088Z", "disclosures": [] } }, diff --git a/metadata/modules/yieldloveBidAdapter.json b/metadata/modules/yieldloveBidAdapter.json index 4e813fbcae8..375051960d7 100644 --- a/metadata/modules/yieldloveBidAdapter.json +++ b/metadata/modules/yieldloveBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://cdn-a.yieldlove.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:39.967Z", + "timestamp": "2026-03-14T22:02:23.208Z", "disclosures": [ { "identifier": "session_id", diff --git a/metadata/modules/yieldmoBidAdapter.json b/metadata/modules/yieldmoBidAdapter.json index 901ecb4dce1..728f8d5e421 100644 --- a/metadata/modules/yieldmoBidAdapter.json +++ b/metadata/modules/yieldmoBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://devicestoragedisclosureurl.yieldmo.com/deviceStorage.json": { - "timestamp": "2026-03-12T23:08:39.982Z", + "timestamp": "2026-03-14T22:02:23.250Z", "disclosures": [] } }, diff --git a/metadata/modules/zeotapIdPlusIdSystem.json b/metadata/modules/zeotapIdPlusIdSystem.json index 7d39fc4607a..f0787ac7565 100644 --- a/metadata/modules/zeotapIdPlusIdSystem.json +++ b/metadata/modules/zeotapIdPlusIdSystem.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://spl.zeotap.com/assets/iab-disclosure.json": { - "timestamp": "2026-03-12T23:08:40.229Z", + "timestamp": "2026-03-14T22:02:23.338Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_globalBidAdapter.json b/metadata/modules/zeta_globalBidAdapter.json index b66af7e0ffe..f496360fa3b 100644 --- a/metadata/modules/zeta_globalBidAdapter.json +++ b/metadata/modules/zeta_globalBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:40.340Z", + "timestamp": "2026-03-14T22:02:23.449Z", "disclosures": [] } }, diff --git a/metadata/modules/zeta_global_sspBidAdapter.json b/metadata/modules/zeta_global_sspBidAdapter.json index 0d3fd5a6294..d623f5e60ec 100644 --- a/metadata/modules/zeta_global_sspBidAdapter.json +++ b/metadata/modules/zeta_global_sspBidAdapter.json @@ -2,7 +2,7 @@ "NOTICE": "do not edit - this file is autogenerated by `gulp update-metadata`", "disclosures": { "https://zetaglobal.com/ZetaDeviceStorageDisclosure.json": { - "timestamp": "2026-03-12T23:08:40.459Z", + "timestamp": "2026-03-14T22:02:23.538Z", "disclosures": [] } }, diff --git a/package-lock.json b/package-lock.json index f3158525443..8afe2f99fad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "11.1.1-pre", + "version": "11.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "11.1.1-pre", + "version": "11.1.0", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index c641af69015..5a51a1d2935 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "11.1.1-pre", + "version": "11.1.0", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From 3c86e10bc8cdf6f98a3b1ce529373ba38d124ec2 Mon Sep 17 00:00:00 2001 From: "Prebid.js automated release" Date: Sat, 14 Mar 2026 22:03:24 +0000 Subject: [PATCH 33/50] Increment version to 11.2.0-pre --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8afe2f99fad..dbf89d7551d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prebid.js", - "version": "11.1.0", + "version": "11.2.0-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "11.1.0", + "version": "11.2.0-pre", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.28.4", diff --git a/package.json b/package.json index 5a51a1d2935..9ecb5bb3ad0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "11.1.0", + "version": "11.2.0-pre", "description": "Header Bidding Management Library", "main": "dist/src/prebid.public.ts", "exports": { From cee4f2f93b62a46cc6a4b6d021af3fa796f770a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:06:48 -0400 Subject: [PATCH 34/50] Bump flatted from 3.3.1 to 3.4.1 (#14593) Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.1 to 3.4.1. - [Commits](https://github.com/WebReflection/flatted/compare/v3.3.1...v3.4.1) --- updated-dependencies: - dependency-name: flatted dependency-version: 3.4.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index dbf89d7551d..00196b0ec37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11732,9 +11732,10 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "dev": true, - "license": "ISC" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -29816,7 +29817,9 @@ } }, "flatted": { - "version": "3.3.1", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", "dev": true }, "follow-redirects": { From 4ffb4bf4c83dcbf274393ae4e3992b0e8b12a097 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Mar 2026 07:08:24 -0400 Subject: [PATCH 35/50] Bump undici from 6.23.0 to 6.24.1 (#14595) Bumps [undici](https://github.com/nodejs/undici) from 6.23.0 to 6.24.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 6.24.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00196b0ec37..543a1fa289e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20643,9 +20643,9 @@ } }, "node_modules/undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "dev": true, "engines": { "node": ">=18.17" @@ -35566,9 +35566,9 @@ "dev": true }, "undici": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "dev": true }, "undici-types": { From 68b05348c93f27d796ba54b43d30d43e92b1c916 Mon Sep 17 00:00:00 2001 From: Richard Andersson <72393300+holidio@users.noreply.github.com> Date: Sun, 15 Mar 2026 12:15:52 +0100 Subject: [PATCH 36/50] Holid Bid Adapter: respect auction timeout, ORTB merges, usersync robustness (#14530) * Holid: respect auction timeout, safer ortb merges, improve usersync * Holid Bid Adapter: add unit tests * chore: re-run CI --- modules/holidBidAdapter.js | 197 +++++++++++++++++----- test/spec/modules/holidBidAdapter_spec.js | 139 ++++++++++++++- 2 files changed, 288 insertions(+), 48 deletions(-) diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index 06e0e7a149e..88f39a4fe79 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -11,12 +11,91 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'holid'; const GVLID = 1177; + const ENDPOINT = 'https://helloworld.holid.io/openrtb2/auction'; const COOKIE_SYNC_ENDPOINT = 'https://null.holid.io/sync.html'; + const TIME_TO_LIVE = 300; -const TMAX = 500; + +// Keep win URLs in-memory (per page-load) const wurlMap = {}; +/** + * Resolve tmax for the outgoing ORTB request. + * Goal: respect publisher's Prebid timeout (bidderRequest.timeout) and allow an optional per-bid override, + * without hard-forcing an arbitrary 500ms. + * + * Rules: + * - If bid.params.tmax is a positive number, use it, but never exceed bidderRequest.timeout when available. + * - Else if bidderRequest.timeout is a positive number, use it. + * - Else omit tmax entirely (PBS will apply its own default / config). + */ +function resolveTmax(bid, bidderRequest) { + const auctionTimeout = Number(bidderRequest?.timeout); + const paramTmax = Number(bid?.params?.tmax); + + const hasAuctionTimeout = Number.isFinite(auctionTimeout) && auctionTimeout > 0; + const hasParamTmax = Number.isFinite(paramTmax) && paramTmax > 0; + + if (hasParamTmax && hasAuctionTimeout) { + return Math.min(paramTmax, auctionTimeout); + } + if (hasParamTmax) { + return paramTmax; + } + if (hasAuctionTimeout) { + return auctionTimeout; + } + return undefined; +} + +/** + * Merge stored request ID into request.ext.prebid.storedrequest.id (without clobbering other ext fields). + * Keeps behavior consistent with the existing adapter expectation of bid.params.adUnitID. + */ +function mergeStoredRequest(ortbRequest, bid) { + const storedId = getBidIdParameter('adUnitID', bid.params); + if (storedId) { + deepSetValue(ortbRequest, 'ext.prebid.storedrequest.id', storedId); + } +} + +/** + * Merge schain into request.source.ext.schain (without overwriting request.source / request.ext). + */ +function mergeSchain(ortbRequest, bid) { + const schain = deepAccess(bid, 'ortb2.source.ext.schain'); + if (schain) { + deepSetValue(ortbRequest, 'source.ext.schain', schain); + } +} + +/** + * Build a sync URL for our sync endpoint. + */ +function buildSyncUrl({ bidders, gdprConsent, uspConsent, type }) { + const queryParams = []; + + queryParams.push('bidders=' + bidders); + + if (gdprConsent) { + queryParams.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + queryParams.push( + 'gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '') + ); + } else { + queryParams.push('gdpr=0'); + } + + if (typeof uspConsent !== 'undefined') { + queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + queryParams.push('type=' + encodeURIComponent(type)); + + return COOKIE_SYNC_ENDPOINT + '?' + queryParams.join('&'); +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -30,19 +109,23 @@ export const spec = { // Build request payload including GDPR, GPP, and US Privacy data if available buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map((bid) => { + // Start from ortb2 (publisher modules may have populated site/user/device/ext/etc) const requestData = { ...bid.ortb2, - source: { - ext: { - schain: bid?.ortb2?.source?.ext?.schain - } - }, id: bidderRequest.bidderRequestId, imp: [getImp(bid)], - tmax: TMAX, - ...buildStoredRequest(bid), }; + // Merge (don’t overwrite) schain + storedrequest + mergeSchain(requestData, bid); + mergeStoredRequest(requestData, bid); + + // Resolve and set tmax (don’t hard-force) + const tmax = resolveTmax(bid, bidderRequest); + if (tmax) { + requestData.tmax = tmax; + } + // GDPR if (bidderRequest && bidderRequest.gdprConsent) { deepSetValue( @@ -67,7 +150,11 @@ export const spec = { // US Privacy if (bidderRequest && bidderRequest.usPrivacy) { - deepSetValue(requestData, 'regs.ext.us_privacy', bidderRequest.usPrivacy); + deepSetValue( + requestData, + 'regs.ext.us_privacy', + bidderRequest.usPrivacy + ); } // User IDs @@ -87,6 +174,7 @@ export const spec = { // Interpret response: group bids by unique impid and select the highest CPM bid per imp interpretResponse: function (serverResponse, bidRequest) { const bidResponsesMap = {}; // Maps impid -> highest bid object + if (!serverResponse.body || !serverResponse.body.seatbid) { return []; } @@ -98,20 +186,30 @@ export const spec = { // --- MINIMAL CHANGE START --- // Build meta object and propagate advertiser domains for hb_adomain const meta = deepAccess(bid, 'ext.prebid.meta', {}) || {}; + // Read ORTB adomain; normalize to array of clean strings let advertiserDomains = deepAccess(bid, 'adomain', []); advertiserDomains = Array.isArray(advertiserDomains) ? advertiserDomains .filter(Boolean) - .map(d => String(d).toLowerCase().replace(/^https?:\/\//, '').replace(/^www\./, '').trim()) + .map((d) => + String(d) + .toLowerCase() + .replace(/^https?:\/\//, '') + .replace(/^www\./, '') + .trim() + ) : []; + if (advertiserDomains.length > 0) { meta.advertiserDomains = advertiserDomains; // <-- Prebid uses this to set hb_adomain } + const networkId = deepAccess(bid, 'ext.prebid.meta.networkId'); if (networkId) { meta.networkId = networkId; } + // Keep writing back for completeness (preserves existing behavior) deepSetValue(bid, 'ext.prebid.meta', meta); // --- MINIMAL CHANGE END --- @@ -157,39 +255,41 @@ export const spec = { }, ]; - if (!serverResponse || (Array.isArray(serverResponse) && serverResponse.length === 0)) { + if ( + !serverResponse || + (Array.isArray(serverResponse) && serverResponse.length === 0) + ) { return syncs; } - const responses = Array.isArray(serverResponse) - ? serverResponse - : [serverResponse]; + const responses = Array.isArray(serverResponse) ? serverResponse : [serverResponse]; const bidders = getBidders(responses); + // Prefer iframe when allowed if (optionsType.iframeEnabled && bidders) { - const queryParams = []; - queryParams.push('bidders=' + bidders); - - if (gdprConsent) { - queryParams.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); - queryParams.push( - 'gdpr_consent=' + - encodeURIComponent(gdprConsent.consentString || '') - ); - } else { - queryParams.push('gdpr=0'); - } - - if (typeof uspConsent !== 'undefined') { - queryParams.push('us_privacy=' + encodeURIComponent(uspConsent)); - } - - queryParams.push('type=iframe'); - const strQueryParams = '?' + queryParams.join('&'); - syncs.push({ type: 'iframe', - url: COOKIE_SYNC_ENDPOINT + strQueryParams, + url: buildSyncUrl({ + bidders, + gdprConsent, + uspConsent, + type: 'iframe', + }), + }); + return syncs; + } + + // Fallback: if iframe is disabled but pixels are enabled, attempt a pixel-based sync call + // (Your sync endpoint must support this mode for it to be effective.) + if (optionsType.pixelEnabled && bidders) { + syncs.push({ + type: 'image', + url: buildSyncUrl({ + bidders, + gdprConsent, + uspConsent, + type: 'image', + }), }); } @@ -211,8 +311,8 @@ export const spec = { function getImp(bid) { const imp = buildStoredRequest(bid); imp.id = bid.bidId; // Ensure imp.id is unique to match the bid response correctly - const sizes = - bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes; + + const sizes = bid.sizes && !Array.isArray(bid.sizes[0]) ? [bid.sizes] : bid.sizes; if (deepAccess(bid, 'mediaTypes.banner')) { imp.banner = { @@ -244,13 +344,26 @@ function buildStoredRequest(bid) { } // Helper: Extract unique bidders from responses for user syncs +// Primary source: ext.responsetimemillis (PBS), fallback: seatbid[].seat function getBidders(responses) { - const bidders = responses - .map((res) => Object.keys(res.body.ext?.responsetimemillis || {})) - .flat(); + const bidderSet = new Set(); + + responses.forEach((res) => { + const rtm = deepAccess(res, 'body.ext.responsetimemillis'); + if (rtm && typeof rtm === 'object') { + Object.keys(rtm).forEach((k) => bidderSet.add(k)); + } + + const seatbid = deepAccess(res, 'body.seatbid', []); + if (Array.isArray(seatbid)) { + seatbid.forEach((sb) => { + if (sb && sb.seat) bidderSet.add(sb.seat); + }); + } + }); - if (bidders.length) { - return encodeURIComponent(JSON.stringify([...new Set(bidders)])); + if (bidderSet.size) { + return encodeURIComponent(JSON.stringify([...bidderSet])); } } diff --git a/test/spec/modules/holidBidAdapter_spec.js b/test/spec/modules/holidBidAdapter_spec.js index c3f7a873f5d..08cef8d31da 100644 --- a/test/spec/modules/holidBidAdapter_spec.js +++ b/test/spec/modules/holidBidAdapter_spec.js @@ -34,15 +34,15 @@ describe('holidBidAdapterTests', () => { } }; - describe('isBidRequestValid', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)); + const clone = (obj) => JSON.parse(JSON.stringify(obj)); + describe('isBidRequestValid', () => { it('should return true', () => { - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(clone(bidRequestData))).to.equal(true); }); it('should return false when required params are not passed', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)); + const bid = clone(bidRequestData); delete bid.params.adUnitID; expect(spec.isBidRequestValid(bid)).to.equal(false); @@ -50,7 +50,7 @@ describe('holidBidAdapterTests', () => { }); describe('buildRequests', () => { - const bid = JSON.parse(JSON.stringify(bidRequestData)); + const bid = clone(bidRequestData); const request = spec.buildRequests([bid], bidderRequest); const payload = JSON.parse(request[0].data); @@ -84,6 +84,85 @@ describe('holidBidAdapterTests', () => { }); }); + // NEW: cover tmax behavior introduced in the PR + describe('buildRequests - tmax behavior', () => { + it('should set tmax from bidderRequest.timeout when no params.tmax is provided', () => { + const bid = clone(bidRequestData); + const br = { bidderRequestId: 'test-id', timeout: 1200 }; + + const request = spec.buildRequests([bid], br); + const payload = JSON.parse(request[0].data); + + expect(payload.tmax).to.equal(1200); + }); + + it('should cap params.tmax to bidderRequest.timeout when provided', () => { + const bid = clone(bidRequestData); + bid.params.tmax = 2500; + + const br = { bidderRequestId: 'test-id', timeout: 900 }; + + const request = spec.buildRequests([bid], br); + const payload = JSON.parse(request[0].data); + + expect(payload.tmax).to.equal(900); + }); + + it('should use params.tmax when bidderRequest.timeout is missing', () => { + const bid = clone(bidRequestData); + bid.params.tmax = 750; + + const br = { bidderRequestId: 'test-id' }; + + const request = spec.buildRequests([bid], br); + const payload = JSON.parse(request[0].data); + + expect(payload.tmax).to.equal(750); + }); + }); + + // NEW: ensure ORTB fields are merged rather than clobbered + describe('buildRequests - ORTB merge safety', () => { + it('should merge storedrequest into ext.prebid without clobbering existing ext fields', () => { + const bid = clone(bidRequestData); + bid.ortb2.ext = { someExtKey: 'keep-me', prebid: { somePrebidKey: 'keep-me-too' } }; + + const br = { bidderRequestId: 'test-id', timeout: 1000 }; + const request = spec.buildRequests([bid], br); + const payload = JSON.parse(request[0].data); + + // existing ext preserved + expect(payload.ext).to.exist; + expect(payload.ext.someExtKey).to.equal('keep-me'); + expect(payload.ext.prebid).to.exist; + expect(payload.ext.prebid.somePrebidKey).to.equal('keep-me-too'); + + // storedrequest merged in + expect(payload.ext.prebid.storedrequest).to.exist; + expect(payload.ext.prebid.storedrequest.id).to.equal('12345'); + }); + + it('should merge schain into source.ext.schain without clobbering source fields', () => { + const bid = clone(bidRequestData); + bid.ortb2.source = { + tid: 'tid-123', + ext: { + other: 'keep-me', + schain: { ver: '1.0', complete: 1, nodes: [{ asi: 'example.com', sid: '123', hp: 1 }] } + } + }; + + const br = { bidderRequestId: 'test-id', timeout: 1000 }; + const request = spec.buildRequests([bid], br); + const payload = JSON.parse(request[0].data); + + expect(payload.source).to.exist; + expect(payload.source.tid).to.equal('tid-123'); + expect(payload.source.ext.other).to.equal('keep-me'); + expect(payload.source.ext.schain).to.deep.equal(bid.ortb2.source.ext.schain); + }); + }); + describe('interpretResponse', () => { // Add impid: 'bid-id' so requestId matches bidRequestData.bidId const serverResponse = { @@ -200,7 +279,6 @@ describe('holidBidAdapterTests', () => { }; const uspConsent = 'mkjvbiniwot4827obfoy8sdg8203gb'; - // Updated 'usp_consent' to 'us_privacy' to match adapter code const expectedUserSyncs = [ { type: 'image', @@ -256,5 +334,54 @@ describe('holidBidAdapterTests', () => { expect(userSyncs).to.deep.equal(expectedUserSyncs); }); + + // NEW: verify seatbid[].seat fallback is used when responsetimemillis is missing + it('should derive bidders from seatbid[].seat when responsetimemillis is missing', () => { + const optionsType = { iframeEnabled: true, pixelEnabled: true }; + const serverResponse = [ + { + body: { + seatbid: [ + { seat: 'rubicon', bid: [] }, + { seat: 'pubmatic', bid: [] }, + ] + } + } + ]; + + const userSyncs = spec.getUserSyncs(optionsType, serverResponse); + + const iframe = userSyncs.find(s => s.type === 'iframe'); + expect(iframe).to.exist; + expect(iframe.url).to.include('bidders='); + + const decoded = decodeURIComponent(iframe.url.split('bidders=')[1].split('&')[0]); + expect(decoded).to.include('rubicon'); + expect(decoded).to.include('pubmatic'); + }); + + // NEW: verify pixel-based fallback is used when iframe is disabled + it('should add an extra image sync when iframe is disabled but pixelEnabled is true', () => { + const optionsType = { iframeEnabled: false, pixelEnabled: true }; + const serverResponse = [ + { + body: { + ext: { responsetimemillis: { pubmatic: 12 } } + } + } + ]; + + const userSyncs = spec.getUserSyncs(optionsType, serverResponse); + + // base Adform pixel always exists + expect(userSyncs[0].type).to.equal('image'); + + // additional image sync from our endpoint should exist + const extraImages = userSyncs.filter(s => s.type === 'image'); + expect(extraImages.length).to.be.greaterThan(1); + const urls = extraImages.map(s => s.url).join(' '); + expect(urls).to.include('bidders='); + expect(urls).to.include('type=image'); + }); }); }); From 7ebff9bd842059d1645218cb0f9b49c1b703bfdc Mon Sep 17 00:00:00 2001 From: Daniel Baud <52867769+danielbaud@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:21:38 +0100 Subject: [PATCH 37/50] Add Alliance Gravity Bid Adapter (#14267) * feat(adapter): ts implementation * feat(adapter): removed amx support * test: added tests * fix(bid-adapter): renamed file * fix(library): folder name * update(bid-adapter): imported module * update: test creds in adUnit * fix(cookie): added guard on object slicing * fix(enrichImp): removed divId parameter * fix(tests): adapted test w/o divId --------- Co-authored-by: Mickael van der Beek --- libraries/alliance_gravityUtils/index.ts | 149 +++++ modules/alliance_gravityBidAdapter.md | 33 ++ modules/alliance_gravityBidAdapter.ts | 81 +++ .../alliance_gravityBidAdapter_spec.js | 536 ++++++++++++++++++ 4 files changed, 799 insertions(+) create mode 100644 libraries/alliance_gravityUtils/index.ts create mode 100644 modules/alliance_gravityBidAdapter.md create mode 100644 modules/alliance_gravityBidAdapter.ts create mode 100644 test/spec/modules/alliance_gravityBidAdapter_spec.js diff --git a/libraries/alliance_gravityUtils/index.ts b/libraries/alliance_gravityUtils/index.ts new file mode 100644 index 00000000000..58cd6c85f36 --- /dev/null +++ b/libraries/alliance_gravityUtils/index.ts @@ -0,0 +1,149 @@ +import { deepAccess, deepSetValue, logInfo } from '../../src/utils.js'; +import {Renderer} from '../../src/Renderer.js'; +import { INSTREAM, OUTSTREAM } from '../../src/video.js'; +import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import { BidResponse, VideoBidResponse } from '../../src/bidfactory.js'; +import { BidRequest, ORTBImp, ORTBResponse } from '../../src/prebid.public.js'; +import { AdapterResponse, ServerResponse } from '../../src/adapters/bidderFactory.js'; + +const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + if (typeof serverResponses === 'object' && + serverResponses != null && + serverResponses.length > 0 && + serverResponses[0].hasOwnProperty('body') && + serverResponses[0].body.hasOwnProperty('ext') && + serverResponses[0].body.ext.hasOwnProperty('cookies') && + typeof serverResponses[0].body.ext.cookies === 'object' && + Array.isArray(serverResponses[0].body.ext.cookies)) { + return serverResponses[0].body.ext.cookies.slice(0, 5); + } else { + return []; + } +}; + +const createOustreamRendererFunction = ( + adUnitCode: string, + width: number, + height: number +) => (bidResponse: VideoBidResponse) => { + bidResponse.renderer.push(() => { + (window as any).ANOutstreamVideo.renderAd({ + sizes: [width, height], + targetId: adUnitCode, + adResponse: bidResponse.vastXml, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + showVolume: false, + allowFullscreen: true, + skippable: false, + content: bidResponse.vastXml + } + }); + }); +}; + +export type CreateRenderPayload = { + requestId: string, + vastXml: string, + adUnitCode: string, + width: number, + height: number +} + +export const createRenderer = ( + { requestId, vastXml, adUnitCode, width, height }: CreateRenderPayload +): Renderer | undefined => { + if (!vastXml) { + logInfo('No VAST in bidResponse'); + return; + } + const installPayload = { + id: requestId, + url: OUTSTREAM_RENDERER_URL, + loaded: false, + adUnitCode: adUnitCode, + targetId: adUnitCode, + }; + const renderer = Renderer.install(installPayload); + renderer.setRender(createOustreamRendererFunction(adUnitCode, width, height)); + return renderer; +}; + +export const enrichImp = (imp:ORTBImp, bidRequest:BidRequest): ORTBImp => { + deepSetValue(imp, 'tagid', bidRequest.adUnitCode); + deepSetValue(imp, 'ext.adUnitCode', bidRequest.adUnitCode); + if (imp.video) { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.playerSize', playerSize); + deepSetValue(imp, 'video.ext.context', videoContext); + } + return imp; +} + +export function createResponse(bid:any, ortbResponse:any): BidResponse { + let mediaType: MediaType = BANNER; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) mediaType = VIDEO; + if (bid.ext.mediaType === NATIVE) mediaType = NATIVE; + const response:any = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + currency: ortbResponse.cur, + netRevenue: true, + ttl: 120, + mediaType, + meta: { + advertiserDomains: bid.adomain, + demandSource: bid.ext.ssp, + }, + }; + if (bid.dealid) response.dealid = bid.dealid; + + if (bid.ext.mediaType === BANNER) response.ad = bid.adm; + if ([INSTREAM, OUTSTREAM].includes(bid.ext.mediaType as string)) response.vastXml = bid.adm; + if (bid.ext.mediaType === OUTSTREAM && (bid.ext.adUnitCode)) { + const renderer = createRenderer({ + requestId: response.requestId, + vastXml: response.vastXml, + adUnitCode: bid.ext.adUnitCode, + width: response.width, + height: response.height + }); + if (renderer) { + response.renderer = renderer; + response.adUnitCode = bid.ext.adUnitCode; + } else { + logInfo('Could not create renderer for outstream bid'); + } + }; + + if (bid.ext.mediaType === NATIVE) { + try { + response.native = { ortb: JSON.parse(bid.adm) } + } catch (e) {} + } + return response as BidResponse; +} + +export const interpretResponse = (serverResponse: ServerResponse): AdapterResponse => { + if (!serverResponse.body) return []; + const respBody = serverResponse.body as ORTBResponse; + if (!respBody.seatbid || respBody.seatbid.length === 0) return []; + + const responses: BidResponse[] = []; + for (let i = 0; i < respBody.seatbid.length; i++) { + const seatbid = respBody.seatbid[i]; + for (let j = 0; j < seatbid.bid.length; j++) { + const bid = seatbid.bid[j]; + const response:BidResponse = createResponse(bid, respBody); + responses.push(response); + } + } + return responses; +} diff --git a/modules/alliance_gravityBidAdapter.md b/modules/alliance_gravityBidAdapter.md new file mode 100644 index 00000000000..98a9e17a4ed --- /dev/null +++ b/modules/alliance_gravityBidAdapter.md @@ -0,0 +1,33 @@ +# Overview + +``` +Module Name: Alliance Gravity Bid Adapter +Module Type: Bidder Adapter +Maintainer: produit@alliancegravity.com +``` + +# Description + +Sends bids to Alliance Gravity network + +Alliance Gravity bid adapter supports Banner, Video, Audio and Native formats + +# Test Parameters +```javascript +var adUnits = [ + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: 'alliance_gravity', + params: { + srid: "test-id" + } + }] + }, +]; +``` diff --git a/modules/alliance_gravityBidAdapter.ts b/modules/alliance_gravityBidAdapter.ts new file mode 100644 index 00000000000..7c9162d705b --- /dev/null +++ b/modules/alliance_gravityBidAdapter.ts @@ -0,0 +1,81 @@ +import { deepSetValue } from '../src/utils.js'; +import {AdapterRequest, BidderSpec, registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js' + +import { interpretResponse, enrichImp, getUserSyncs } from '../libraries/alliance_gravityUtils/index.js'; +import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; +import { BidRequest, ClientBidderRequest } from '../src/adapterManager.js'; +import { ORTBImp, ORTBRequest } from '../src/prebid.public.js'; + +const BIDDER_CODE = 'alliance_gravity'; +const REQUEST_URL = 'https://pbs.production.agrvt.com/openrtb2/auction'; +const GVLID = 501; + +const DEFAULT_GZIP_ENABLED = false; + +declare module '../src/adUnits' { + interface BidderParams { + srid: string + } +} + +const converter = ortbConverter({ + context: { + netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false + ttl: 90, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + }, + imp(buildImp, bidRequest: BidRequest, context) { + let imp:ORTBImp = buildImp(bidRequest, context); + imp = enrichImp(imp, bidRequest); + const adUnitCode = bidRequest.adUnitCode; + const slotEl:HTMLElement | null = document.getElementById(adUnitCode); + if (slotEl) { + const { width, height } = getBoundingClientRect(slotEl); + deepSetValue(imp, 'ext.dimensions.slotW', width); + deepSetValue(imp, 'ext.dimensions.slotH', height); + deepSetValue(imp, 'ext.dimensions.cssMaxW', slotEl.style?.maxWidth); + deepSetValue(imp, 'ext.dimensions.cssMaxH', slotEl.style?.maxHeight); + } + deepSetValue(imp, 'ext.prebid.storedrequest.id', bidRequest.params.srid); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + return buildRequest(imps, bidderRequest, context); + }, +}); + +const isBidRequestValid = (bid:BidRequest): boolean => { + if (!bid.params.srid || typeof bid.params.srid !== 'string' || bid.params.srid === '') { + return false; + } + return true; +}; + +const buildRequests = ( + bidRequests: BidRequest[], + bidderRequest: ClientBidderRequest, +): AdapterRequest => { + const data:ORTBRequest = converter.toORTB({bidRequests, bidderRequest}) + const adapterRequest:AdapterRequest = { + method: 'POST', + url: REQUEST_URL, + data, + options: { + endpointCompression: DEFAULT_GZIP_ENABLED + }, + } + return adapterRequest; +} + +export const spec:BidderSpec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, +}; + +registerBidder(spec); diff --git a/test/spec/modules/alliance_gravityBidAdapter_spec.js b/test/spec/modules/alliance_gravityBidAdapter_spec.js new file mode 100644 index 00000000000..6950007e583 --- /dev/null +++ b/test/spec/modules/alliance_gravityBidAdapter_spec.js @@ -0,0 +1,536 @@ +import { expect } from 'chai'; +import { spec } from 'modules/alliance_gravityBidAdapter.js'; +import sinon from 'sinon'; +const sandbox = sinon.createSandbox(); + +describe('Alliance Gravity bid adapter tests', () => { + const DEFAULT_OPTIONS = { + gdprConsent: { + gdprApplies: true, + consentString: 'COw4XqPOw4XqPAHABBFRDdCgAP_AAAAAAAYgI5wBQAKgArABaACEAGAAQgBAACUAFQAKwAXgAwABkADgAHgAQQAiABIACYAFIALAAYgAzABqADcAHAAOgAdgA9AB-AEIAIoASAAkgBLgCZAE0AJwATwAoABSgCqAFgALQAWwAuwBfADAAGCAMKAYcAxQBjQDIAGTAMsAZcAzQBnADPAGgANGAacA1ABqwDWgGuANoAbYA3QBvQDgAHFAOPAdAA6QB1ADsAHeAPKAfQA_AB_gEAAIEAQQAhIBCwCHAEPgIkAReAjUBHQCPgEgAJFASQAkwBKICVAEsAJeAS-AmIBMwCagE2gJwATsAnwBQICggFDAKIAUYApIBSwCmgFQgKiAVKAqgBVoCrgFYAK0AV0Ar4BYQCxQFjgLKAWaAs8BaIC1AFtALgAXGAucBeAC9AF9AL-AYGAw', + vendorData: {}, + }, + refererInfo: { + referer: 'https://www.prebid.org', + canonicalUrl: 'https://www.prebid.org/the/link/to/the/page', + } + }; + + describe('isBidRequestValid()', () => { + let bannerBid; + beforeEach(() => { + bannerBid = { + bidder: 'alliance_gravity', + mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, + adUnitCode: 'div-1', + transactionId: '70bdc37e-9475-4b27-8c74-4634bdc2ee66', + sizes: [[300, 250], [300, 600]], + bidId: '4906582fc87d0c', + bidderRequestId: '332fda16002dbe', + auctionId: '98932591-c822-42e3-850e-4b3cf748d063', + } + }); + + it('No srid', () => { + bannerBid.params = {}; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('Invalid srid type (number)', () => { + bannerBid.params = { srid: 1234 }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('Invalid srid type (object', () => { + bannerBid.params = { srid: {} }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('Empty srid', () => { + bannerBid.params = { srid: '' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(false); + }); + + it('Valid srid', () => { + bannerBid.params = { srid: '12345' }; + expect(spec.isBidRequestValid(bannerBid)).to.be.equal(true); + }); + }); + + describe('buildRequests()', () => { + before(() => { + const documentStub = sandbox.stub(document, 'getElementById'); + documentStub.withArgs('div-1').returns({ + offsetWidth: 200, + offsetHeight: 250, + style: { + maxWidth: '400px', + maxHeight: '350px', + }, + getBoundingClientRect() { return { width: 200, height: 250 }; } + }); + }); + describe('Multiple display bids', () => { + const sampleBids = [ + { + bidder: 'alliance_gravity', + params: { + srid: "12345" + }, + adUnitCode: 'header-ad-1234', + transactionId: '469a570d-f187-488d-b1cb-48c1a2009be9', + sizes: [[300, 250], [300, 600]], + bidId: '44a2706ac3574', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + }, + { + bidder: 'alliance_gravity', + params: { + srid: '67890', + }, + mediaTypes: { + banner: { + sizes: [[728, 90], [970, 250]] + } + }, + + adUnitCode: 'div-2-abcd', + transactionId: '6196885d-4e76-40dc-a09c-906ed232626b', + sizes: [[728, 90], [970, 250]], + bidId: '5ba94555219a03', + bidderRequestId: '359bf8a3c06b2e', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + } + ]; + const bidderRequest = { + bidderCode: 'alliance_gravity', + auctionId: '2e684815-b44e-4e04-b812-56da54adbe74', + bidderRequestId: '359bf8a3c06b2e', + gdprConsent: { + gdprApplies: true, + consentString: 'CPhdLUAPhdLUAAKAsAENCmCsAP_AAE7AAAqIJFNd_H__bW9r-f5_aft0eY1P9_r37uQzDhfNk-8F3L_W_LwX52E7NF36tq4KmR4ku1LBIUNlHMHUDUmwaokVryHsak2cpzNKJ7BEknMZOydYGF9vmxtj-QKY7_5_d3bx2D-t_9v239z3z81Xn3d53-_03LCdV5_9Dfn9fR_bc9KPt_58v8v8_____3_e__3_7997BIiAaADgAJYBnwEeAJXAXmAwQBj4DtgHcgPBAeKBIgAA.YAAAAAAAAAAA', + } + }; + it('2 display adunits', () => { + const displayBids = structuredClone(sampleBids); + displayBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + } + }; + const request = spec.buildRequests(displayBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + const expectedRequest = { + imp: [ + { + id: '44a2706ac3574', + banner: { + topframe: 0, + format: [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + ], + }, + secure: 1, + tagid: 'header-ad-1234', + ext: { + adUnitCode: 'header-ad-1234', + prebid: { + storedrequest: { + id: "12345" + } + }, + }, + }, + { + id: '5ba94555219a03', + banner: { + topframe: 0, + format: [ + { w: 728, h: 90 }, + { w: 970, h: 250 }, + ], + }, + secure: 1, + tagid: 'div-2-abcd', + ext: { + adUnitCode: 'div-2-abcd', + prebid: { + storedrequest: { + id: "67890" + } + }, + }, + }, + ], + id: requestContent.id, + test: 0 + }; + expect(requestContent).to.be.eql(expectedRequest); + }); + + if (FEATURES.VIDEO) { + it('multiformat adunit', () => { + const multiformatBids = structuredClone(sampleBids); + multiformatBids[0].mediaTypes = { + banner: { + sizes: [[300, 250], [300, 600]] + }, + video: { + context: 'outstream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + playback_method: ['auto_play_sound_off'] + } + }; + const request = spec.buildRequests(multiformatBids, bidderRequest); + const video = request.data.imp[0].video; + const expectedVideo = { + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + playbackmethod: [2], + skip: 1, + w: 640, + h: 480, + ext: { + playerSize: [640, 480], + context: 'outstream', + }, + }; + expect(video).to.eql(expectedVideo); + }); + + it('instream adunit', () => { + const videoBids = structuredClone(sampleBids); + videoBids[0].mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480], + mimes: ['video/mp4'], + protocols: [1, 2, 3, 4, 5, 6], + playbackmethod: [2], + skip: 1 + } + }; + const request = spec.buildRequests(videoBids, bidderRequest); + const requestContent = request.data; + expect(request).to.have.property('method').and.to.equal('POST'); + expect(requestContent.imp[0].video.ext.context).to.be.eql('instream'); + expect(requestContent.imp[0].video.playbackmethod[0]).to.be.eql(2); + }); + } + }); + after(() => { + sandbox.restore() + }); + }); + + describe('intepretResponse()', () => { + it('empty response', () => { + const response = { + body: '' + }; + const output = spec.interpretResponse(response); + expect(output.length).to.be.eql(0); + }); + it('banner responses with adm', () => { + const response = { + body: { + id: 'a8d3a675-a4ba-4d26-807f-c8f2fad821e0', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '4427551302944024629', + impid: '226175918ebeda', + price: 1.5, + adomain: [ + 'http://prebid.org', + ], + crid: '98493581', + ssp: 'appnexus', + h: 600, + w: 300, + adm: '
TestAd
', + cat: [ + 'IAB3-1', + ], + ext: { + adUnitCode: 'div-1', + mediaType: 'banner', + adUrl: 'https://fast.nexx360.io/cache?uuid=fdddcebc-1edf-489d-880d-1418d8bdc493', + ssp: 'appnexus', + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + id: 'de3de7c7-e1cf-4712-80a9-94eb26bfc718', + cookies: [], + }, + }, + }; + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '226175918ebeda', + cpm: 1.5, + width: 300, + height: 600, + creativeId: '98493581', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'banner', + meta: { + advertiserDomains: [ + 'http://prebid.org', + ], + demandSource: 'appnexus', + }, + ad: '
TestAd
', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('instream responses', () => { + const response = { + body: { + id: '2be64380-ba0c-405a-ab53-51f51c7bde51', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '8275140264321181514', + impid: '263cba3b8bfb72', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'instream', + ssp: 'appnexus', + adUnitCode: 'video1' + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '263cba3b8bfb72', + cpm: 5, + width: 1, + height: 1, + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + }]; + expect(output).to.eql(expectedOutput); + }); + + it('outstream responses', () => { + const response = { + body: { + id: '40c23932-135e-4602-9701-ca36f8d80c07', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '1186971142548769361', + impid: '4ce809b61a3928', + price: 5, + adomain: [ + 'appnexus.com', + ], + crid: '97517771', + h: 1, + w: 1, + adm: 'vast', + ext: { + mediaType: 'outstream', + ssp: 'appnexus', + adUnitCode: 'div-1' + }, + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectedOutput = [{ + requestId: '4ce809b61a3928', + cpm: 5, + width: 1, + height: 1, + adUnitCode: 'div-1', + creativeId: '97517771', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'video', + meta: { advertiserDomains: ['appnexus.com'], demandSource: 'appnexus' }, + vastXml: 'vast', + renderer: output[0].renderer, + }]; + expect(output).to.eql(expectedOutput); + }); + + it('native responses', () => { + const response = { + body: { + id: '3c0290c1-6e75-4ef7-9e37-17f5ebf3bfa3', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '6624930625245272225', + impid: '23e11d845514bb', + price: 10, + adomain: [ + 'prebid.org', + ], + crid: '97494204', + h: 1, + w: 1, + cat: [ + 'IAB3-1', + ], + ext: { + mediaType: 'native', + ssp: 'appnexus', + adUnitCode: '/19968336/prebid_native_example_1', + }, + adm: '{"ver":"1.2","assets":[{"id":1,"img":{"url":"https:\\/\\/vcdn.adnxs.com\\/p\\/creative-image\\/f8\\/7f\\/0f\\/13\\/f87f0f13-230c-4f05-8087-db9216e393de.jpg","w":989,"h":742,"ext":{"appnexus":{"prevent_crop":0}}}},{"id":0,"title":{"text":"This is a Prebid Native Creative"}},{"id":2,"data":{"value":"Prebid.org"}}],"link":{"url":"https:\\/\\/ams3-ib.adnxs.com\\/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA.\\/bcr=AAAAAAAA8D8=\\/pp=${AUCTION_PRICE}\\/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA\\/cca=OTMyNSNBTVMzOjYxMzU=\\/bn=97062\\/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html"},"eventtrackers":[{"event":1,"method":1,"url":"https:\\/\\/ams3-ib.adnxs.com\\/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}"}]}', + }, + ], + seat: 'appnexus', + }, + ], + ext: { + cookies: [], + }, + }, + }; + + const output = spec.interpretResponse(response); + const expectOutput = [{ + requestId: '23e11d845514bb', + cpm: 10, + width: 1, + height: 1, + creativeId: '97494204', + currency: 'USD', + netRevenue: true, + ttl: 120, + mediaType: 'native', + meta: { + advertiserDomains: [ + 'prebid.org', + ], + demandSource: 'appnexus', + }, + native: { + ortb: { + ver: '1.2', + assets: [ + { + id: 1, + img: { + url: 'https://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg', + w: 989, + h: 742, + ext: { + appnexus: { + prevent_crop: 0, + }, + }, + }, + }, + { + id: 0, + title: { + text: 'This is a Prebid Native Creative', + }, + }, + { + id: 2, + data: { + value: 'Prebid.org', + }, + }, + ], + link: { + url: 'https://ams3-ib.adnxs.com/click?AAAAAAAAJEAAAAAAAAAkQAAAAAAAACRAAAAAAAAAJEAAAAAAAAAkQKZS4ZZl5vVbR6p-A-MwnyTZ7QVkAAAAAOLoyQBtJAAAbSQAAAIAAAC8pM8FnPgWAAAAAABVU0QAVVNEAAEAAQBNXQAAAAABAgMCAAAAALoAURe69gAAAAA./bcr=AAAAAAAA8D8=/pp=${AUCTION_PRICE}/cnd=%21JBC72Aj8-LwKELzJvi4YnPFbIAQoADEAAAAAAAAkQDoJQU1TMzo2MTM1QNAwSQAAAAAAAPA_UQAAAAAAAAAAWQAAAAAAAAAAYQAAAAAAAAAAaQAAAAAAAAAAcQAAAAAAAAAAeACJAQAAAAAAAAAA/cca=OTMyNSNBTVMzOjYxMzU=/bn=97062/clickenc=http%3A%2F%2Fprebid.org%2Fdev-docs%2Fshow-native-ads.html', + }, + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://ams3-ib.adnxs.com/it?an_audit=0&referrer=https%3A%2F%2Ftest.nexx360.io%2Fadapter%2Fnative%2Ftest.html&e=wqT_3QKJCqAJBQAAAwDWAAUBCNnbl6AGEKalhbfZzPn6WxjH1PqbsJzMzyQqNgkAAAECCCRAEQEHEAAAJEAZEQkAIREJACkRCQAxEQmoMOLRpwY47UhA7UhIAlC8yb4uWJzxW2AAaM26dXim9gWAAQGKAQNVU0SSAQEG9F4BmAEBoAEBqAEBsAEAuAECwAEDyAEC0AEJ2AEA4AEA8AEAigIpdWYoJ2EnLCAyNTI5ODg1LCAwKTt1ZigncicsIDk3NDk0MjA0LCAwKTuSAvEDIS0xRDNJQWo4LUx3S0VMekp2aTRZQUNDYzhWc3dBRGdBUUFSSTdVaFE0dEduQmxnQVlQX19fXzhQYUFCd0FYZ0JnQUVCaUFFQmtBRUJtQUVCb0FFQnFBRURzQUVBdVFIenJXcWtBQUFrUU1FQjg2MXFwQUFBSkVESkFYSUtWbWViSmZJXzJRRUFBQUFBQUFEd1AtQUJBUFVCQUFBQUFKZ0NBS0FDQUxVQ0FBQUFBTDBDQUFBQUFNQUNBY2dDQWRBQ0FkZ0NBZUFDQU9nQ0FQZ0NBSUFEQVpnREFib0RDVUZOVXpNNk5qRXpOZUFEMERDSUJBQ1FCQUNZQkFIQkJBQUFBQUFBQUFBQXlRUUFBCQscQUFOZ0VBUEURlSxBQUFDSUJmY3ZxUVUBDQRBQQGoCDdFRgEKCQEMREJCUQkKAQEAeRUoAUwyKAAAWi4oALg0QVhBaEQzd0JhTEQzd0w0QmQyMG1nR0NCZ05WVTBTSUJnQ1FCZ0dZQmdDaEJnQQFONEFBQ1JBcUFZQnNnWWtDHXQARR0MAEcdDABJHQw8dUFZS5oClQEhSkJDNzJBajL1ASRuUEZiSUFRb0FEFfhUa1FEb0pRVTFUTXpvMk1UTTFRTkF3UxFRDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQwQZUFDSkEdEMjYAvfpA-ACrZhI6gIwaHR0cHM6Ly90ZXN0Lm5leHgzNjAuaW8vYWRhcHRlci9uYXRpdmUJH_CaaHRtbIADAIgDAZADAJgDFKADAaoDAMAD4KgByAMA2AMA4AMA6AMA-AMDgAQAkgQJL29wZW5ydGIymAQAqAQAsgQMCAAQABgAIAAwADgAuAQAwASA2rgiyAQA0gQOOTMyNSNBTVMzOjYxMzXaBAIIAeAEAPAEvMm-LvoEEgkAAABAPG1IQBEAAACgV8oCQIgFAZgFAKAF______8BBbABqgUkM2MwMjkwYzEtNmU3NS00ZWY3LTllMzctMTdmNWViZjNiZmEzwAUAyQWJFxTwP9IFCQkJDHgAANgFAeAFAfAFmfQh-gUECAAQAJAGAZgGALgGAMEGCSUo8D_QBvUv2gYWChAJERkBAdpg4AYM8gYCCACABwGIBwCgB0HIB6b2BdIHDRVkASYI2gcGAV1oGADgBwDqBwIIAPAHAIoIAhAAlQgAAIA_mAgB&s=ccf63f2e483a37091d2475d895e7cf7c911d1a78&pp=${AUCTION_PRICE}', + }, + ], + }, + }, + }]; + expect(output).to.eql(expectOutput); + }); + }); + + describe('getUserSyncs()', () => { + const response = { body: { cookies: [] } }; + it('Verifies user sync without cookie in bid response', () => { + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with cookies in bid response', () => { + response.body.ext = { + cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + }; + const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); + const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; + expect(syncs).to.eql(expectedSyncs); + }); + it('Verifies user sync with no bid response', () => { + var syncs = spec.getUserSyncs({}, null, DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + it('Verifies user sync with no bid body response', () => { + let syncs = spec.getUserSyncs({}, [], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + syncs = spec.getUserSyncs({}, [{}], DEFAULT_OPTIONS.gdprConsent, DEFAULT_OPTIONS.uspConsent); + expect(syncs).to.eql([]); + }); + }); +}); From 6295bfb952588465651426625a84d4c6506f1cc1 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Tue, 17 Mar 2026 11:36:00 -0700 Subject: [PATCH 38/50] gppControl modules: add missing transmitUfpd check (#14604) * gppControl modules: add missing transmitUfpd check * Update activityControls.js --------- Co-authored-by: Patrick McCann --- libraries/mspa/activityControls.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index ec7100467f4..fc049b53fff 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -3,7 +3,8 @@ import { ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD, ACTIVITY_SYNC_USER, - ACTIVITY_TRANSMIT_PRECISE_GEO + ACTIVITY_TRANSMIT_PRECISE_GEO, + ACTIVITY_TRANSMIT_UFPD } from '../../src/activities/activities.js'; import { gppDataHandler } from '../../src/adapterManager.js'; import { logInfo } from '../../src/utils.js'; @@ -105,7 +106,8 @@ export function isTransmitGeoConsentDenied(cd) { const CONSENT_RULES = { [ACTIVITY_SYNC_USER]: isConsentDenied, [ACTIVITY_ENRICH_EIDS]: isConsentDenied, - [ACTIVITY_ENRICH_UFPD]: isTransmitUfpdConsentDenied, + [ACTIVITY_ENRICH_UFPD]: isConsentDenied, + [ACTIVITY_TRANSMIT_UFPD]: isTransmitUfpdConsentDenied, [ACTIVITY_TRANSMIT_PRECISE_GEO]: isTransmitGeoConsentDenied }; From 286dfe7c64fb785c1372e755236b1d9bad0275d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 23:31:19 -0400 Subject: [PATCH 39/50] Bump fast-xml-parser from 5.4.1 to 5.5.6 (#14608) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.4.1 to 5.5.6. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.4.1...v5.5.6) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.5.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 61 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 543a1fa289e..b6b861bec77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11442,21 +11442,24 @@ ] }, "node_modules/fast-xml-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", - "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } - ] + ], + "dependencies": { + "path-expression-matcher": "^1.1.3" + } }, "node_modules/fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", + "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", "dev": true, "funding": [ { @@ -11465,7 +11468,8 @@ } ], "dependencies": { - "fast-xml-builder": "^1.0.0", + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.1.3", "strnum": "^2.1.2" }, "bin": { @@ -17565,6 +17569,21 @@ "node": ">=8" } }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "dev": true, @@ -29626,18 +29645,22 @@ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" }, "fast-xml-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", - "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "dev": true, + "requires": { + "path-expression-matcher": "^1.1.3" + } }, "fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "version": "5.5.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", + "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", "dev": true, "requires": { - "fast-xml-builder": "^1.0.0", + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.1.3", "strnum": "^2.1.2" } }, @@ -33578,6 +33601,12 @@ "version": "4.0.0", "dev": true }, + "path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "dev": true From 318483d7e04889222895764fd182cf01779b8f98 Mon Sep 17 00:00:00 2001 From: Eugene Dorfman Date: Wed, 18 Mar 2026 04:40:45 +0100 Subject: [PATCH 40/50] 51DegreesRtdProvider: populate device.hwv, improve device.model (#14598) * 51d: populate device.hwv + optionally use hardwarenameprefix as device.module * 51d update doc * fix typo --- modules/51DegreesRtdProvider.js | 4 +++ modules/51DegreesRtdProvider.md | 10 +++--- .../spec/modules/51DegreesRtdProvider_spec.js | 36 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js index f8542365dbd..d4f32758752 100644 --- a/modules/51DegreesRtdProvider.js +++ b/modules/51DegreesRtdProvider.js @@ -220,6 +220,8 @@ export const convert51DegreesDataToOrtb2 = (data51) => { * @param {string} [device.hardwarevendor] Hardware vendor * @param {string} [device.hardwaremodel] Hardware model * @param {string[]} [device.hardwarename] Hardware name + * @param {string} [device.hardwarenameprefix] Hardware name prefix (e.g. "iPhone" from "iPhone 12 Pro Max") + * @param {string} [device.hardwarenameversion] Hardware name version (e.g. "12 Pro Max" from "iPhone 12 Pro Max") * @param {string} [device.platformname] Platform name * @param {string} [device.platformversion] Platform version * @param {number} [device.screenpixelsheight] Screen height in pixels @@ -240,6 +242,7 @@ export const convert51DegreesDeviceToOrtb2 = (device) => { } const deviceModel = + device.hardwarenameprefix || device.hardwaremodel || ( device.hardwarename && device.hardwarename.length ? device.hardwarename.join(',') @@ -257,6 +260,7 @@ export const convert51DegreesDeviceToOrtb2 = (device) => { deepSetNotEmptyValue(ortb2Device, 'devicetype', ORTB_DEVICE_TYPE_MAP.get(device.devicetype)); deepSetNotEmptyValue(ortb2Device, 'make', device.hardwarevendor); deepSetNotEmptyValue(ortb2Device, 'model', deviceModel); + deepSetNotEmptyValue(ortb2Device, 'hwv', device.hardwarenameversion); deepSetNotEmptyValue(ortb2Device, 'os', device.platformname); deepSetNotEmptyValue(ortb2Device, 'osv', device.platformversion); deepSetNotEmptyValue(ortb2Device, 'h', device.screenpixelsphysicalheight || device.screenpixelsheight); diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md index db4b930c25e..b38fed3dfaf 100644 --- a/modules/51DegreesRtdProvider.md +++ b/modules/51DegreesRtdProvider.md @@ -10,7 +10,7 @@ 51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). -51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. +51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `hwv`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. The module also adds a `device.ext.fod` extension object (fod == fifty one degrees) and sets `device.ext.fod.deviceId` to a permanent device ID, which can be rapidly looked up in on-premise data, exposing over 250 properties, including device age, chipset, codec support, price, operating system and app/browser versions, age, and embedded features. @@ -18,7 +18,7 @@ It also sets `device.ext.fod.tpc` key to a binary value to indicate whether thir The module supports on-premise and cloud device detection services, with free options for both. -A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/7bL8jDGz). This is the simplest approach to trial the module. +A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/jJqVnTJR). This is the simplest approach to trial the module. An interface-compatible self-hosted service can be used with .NET, Java, Node, PHP, and Python. See [51Degrees examples](https://51degrees.com/documentation/_examples__device_detection__getting_started__web__on_premise.html). @@ -40,12 +40,14 @@ gulp build --modules=rtdModule,51DegreesRtdProvider,appnexusBidAdapter,... #### Resource Key -In order to use the module, please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/7bL8jDGz) - choose the following properties: +In order to use the module, please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/jJqVnTJR) - choose the following properties: * DeviceId * DeviceType * HardwareVendor * HardwareName +* HardwareNamePrefix +* HardwareNameVersion * HardwareModel * PlatformName * PlatformVersion @@ -111,7 +113,7 @@ pbjs.setConfig({ waitForIt: true, // should be true, otherwise the auctionDelay will be ignored params: { resourceKey: '', - // Get your resource key from https://configure.51degrees.com/7bL8jDGz + // Get your resource key from https://configure.51degrees.com/jJqVnTJR // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen endpoint // onPremiseJSUrl: 'https://localhost/51Degrees.core.js' }, diff --git a/test/spec/modules/51DegreesRtdProvider_spec.js b/test/spec/modules/51DegreesRtdProvider_spec.js index 337f0a78188..7e1840e3dc7 100644 --- a/test/spec/modules/51DegreesRtdProvider_spec.js +++ b/test/spec/modules/51DegreesRtdProvider_spec.js @@ -390,6 +390,42 @@ describe('51DegreesRtdProvider', function() { expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('model'); }); + it('prefers hardwarenameprefix over hardwaremodel for model field', function() { + const device = { ...fiftyOneDegreesDevice, hardwarenameprefix: 'iPhone' }; + expect(convert51DegreesDeviceToOrtb2(device).device).to.deep.include({ model: 'iPhone' }); + }); + + it('falls back to hardwaremodel when hardwarenameprefix is not provided', function() { + const device = { ...fiftyOneDegreesDevice }; + delete device.hardwarenameprefix; + expect(convert51DegreesDeviceToOrtb2(device).device).to.deep.include({ model: 'Macintosh' }); + }); + + it('sets hwv from hardwarenameversion when provided', function() { + const device = { ...fiftyOneDegreesDevice, hardwarenameversion: '12 Pro Max' }; + expect(convert51DegreesDeviceToOrtb2(device).device).to.deep.include({ hwv: '12 Pro Max' }); + }); + + it('does not set hwv if hardwarenameversion is not provided', function() { + const device = { ...fiftyOneDegreesDevice }; + delete device.hardwarenameversion; + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('hwv'); + }); + + it('sets model from hardwarenameprefix independently of hwv from hardwarenameversion', function() { + const deviceWithPrefix = { ...fiftyOneDegreesDevice, hardwarenameprefix: 'iPhone' }; + delete deviceWithPrefix.hardwarenameversion; + const resultWithPrefix = convert51DegreesDeviceToOrtb2(deviceWithPrefix).device; + expect(resultWithPrefix).to.deep.include({ model: 'iPhone' }); + expect(resultWithPrefix).to.not.have.any.keys('hwv'); + + const deviceWithVersion = { ...fiftyOneDegreesDevice, hardwarenameversion: '12 Pro Max' }; + delete deviceWithVersion.hardwarenameprefix; + const resultWithVersion = convert51DegreesDeviceToOrtb2(deviceWithVersion).device; + expect(resultWithVersion).to.deep.include({ hwv: '12 Pro Max' }); + expect(resultWithVersion).to.deep.include({ model: 'Macintosh' }); + }); + it('does not set the ppi if screeninchesheight is not provided', function() { const device = { ...fiftyOneDegreesDevice }; delete device.screeninchesheight; From 5b1e70fa213a1edff031eb442444ceec0a8cc04e Mon Sep 17 00:00:00 2001 From: PiekharievaK <86105196+PiekharievaK@users.noreply.github.com> Date: Wed, 18 Mar 2026 06:15:06 +0200 Subject: [PATCH 41/50] adtelligentBidAdapter: gather placement pos info (#14555) * adtelligentBidAdapter-update * lint fix --------- Co-authored-by: k-piekharieva Co-authored-by: Patrick McCann --- .../adtelligentUtils/adtelligentUtils.js | 3 +++ modules/adtelligentBidAdapter.js | 6 ++++-- .../modules/adtelligentBidAdapter_spec.js | 20 +++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/libraries/adtelligentUtils/adtelligentUtils.js b/libraries/adtelligentUtils/adtelligentUtils.js index 8b7a659c862..50911f97b6f 100644 --- a/libraries/adtelligentUtils/adtelligentUtils.js +++ b/libraries/adtelligentUtils/adtelligentUtils.js @@ -1,6 +1,7 @@ import { deepAccess, isArray } from '../../src/utils.js'; import { config } from '../../src/config.js'; import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import { getPlacementPositionUtils } from "../placementPositionInfo/placementPositionInfo.js"; export const supportedMediaTypes = [VIDEO, BANNER] @@ -50,9 +51,11 @@ export function getUserSyncsFn (syncOptions, serverResponses, syncsCache = {}) { } export function createTag(bidRequests, adapterRequest) { + const placementEnv = getPlacementPositionUtils().getPlacementEnv() const tag = { // TODO: is 'page' the right value here? Domain: deepAccess(adapterRequest, 'refererInfo.page'), + ...placementEnv }; if (config.getConfig('coppa') === true) { diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index 00c7bbf72e6..b14b0f28d82 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -9,7 +9,7 @@ import { isBidRequestValid, supportedMediaTypes } from '../libraries/adtelligentUtils/adtelligentUtils.js'; - +import { getPlacementPositionUtils } from "../libraries/placementPositionInfo/placementPositionInfo.js"; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest @@ -169,11 +169,13 @@ 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 gpid = deepAccess(bidReq, 'ortb2Imp.ext.gpid'); + const placementInfo = getPlacementPositionUtils().getPlacementInfo(bidReq) const bidReqParams = { 'CallbackId': bidReq.bidId, 'Aid': bidReq.params.aid, 'AdType': mediaType, - 'Sizes': parseSizesInput(sizes).join(',') + 'Sizes': parseSizesInput(sizes).join(','), + ...placementInfo }; bidReqParams.PlacementId = bidReq.adUnitCode; diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index eed27cdd57b..d0366a68794 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -301,7 +301,10 @@ describe('adtelligentBidAdapter', () => { Aid: 12345, Sizes: '480x360,640x480', PlacementId: 'adunit-code', - GPID: '12345/adunit-code' + GPID: '12345/adunit-code', + DistanceToView: 0, + ElementHeight: 1, + PlacementPercentView: 0, }; expect(data.BidRequests[0]).to.deep.equal(eq); }); @@ -315,7 +318,10 @@ describe('adtelligentBidAdapter', () => { Aid: 12345, Sizes: '300x250', PlacementId: 'adunit-code', - GPID: '12345/adunit-code' + GPID: '12345/adunit-code', + DistanceToView: 0, + ElementHeight: 1, + PlacementPercentView: 0, }; expect(data.BidRequests[0]).to.deep.equal(eq); @@ -329,14 +335,20 @@ describe('adtelligentBidAdapter', () => { Aid: 12345, Sizes: '300x250', PlacementId: 'adunit-code', - GPID: '12345/adunit-code' + GPID: '12345/adunit-code', + DistanceToView: 0, + ElementHeight: 1, + PlacementPercentView: 0, }, { CallbackId: '84ab500420319d', AdType: 'video', Aid: 12345, Sizes: '480x360,640x480', PlacementId: 'adunit-code', - GPID: '12345/adunit-code' + GPID: '12345/adunit-code', + DistanceToView: 0, + ElementHeight: 1, + PlacementPercentView: 0, }] expect(bidRequests.BidRequests).to.deep.equal(expectedBidReqs); From a7c82c8753b8500ea559b883dcfcd497f2d8f79e Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Wed, 18 Mar 2026 00:15:39 -0400 Subject: [PATCH 42/50] Reformat import statements in gravityBidAdapter (#14610) * Reformat import statements in gravityBidAdapter * Fix import statement formatting for Renderer * Fix formatting of cookies in user sync test * Format code for consistency in buildRequests function --- libraries/alliance_gravityUtils/index.ts | 2 +- modules/alliance_gravityBidAdapter.ts | 8 ++++---- test/spec/modules/alliance_gravityBidAdapter_spec.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/alliance_gravityUtils/index.ts b/libraries/alliance_gravityUtils/index.ts index 58cd6c85f36..52a011bbcb4 100644 --- a/libraries/alliance_gravityUtils/index.ts +++ b/libraries/alliance_gravityUtils/index.ts @@ -1,5 +1,5 @@ import { deepAccess, deepSetValue, logInfo } from '../../src/utils.js'; -import {Renderer} from '../../src/Renderer.js'; +import { Renderer } from '../../src/Renderer.js'; import { INSTREAM, OUTSTREAM } from '../../src/video.js'; import { BANNER, MediaType, NATIVE, VIDEO } from '../../src/mediaTypes.js'; import { BidResponse, VideoBidResponse } from '../../src/bidfactory.js'; diff --git a/modules/alliance_gravityBidAdapter.ts b/modules/alliance_gravityBidAdapter.ts index 7c9162d705b..d4b6d024904 100644 --- a/modules/alliance_gravityBidAdapter.ts +++ b/modules/alliance_gravityBidAdapter.ts @@ -1,7 +1,7 @@ import { deepSetValue } from '../src/utils.js'; -import {AdapterRequest, BidderSpec, registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {ortbConverter} from '../libraries/ortbConverter/converter.js' +import { AdapterRequest, BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { interpretResponse, enrichImp, getUserSyncs } from '../libraries/alliance_gravityUtils/index.js'; import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js'; @@ -56,7 +56,7 @@ const buildRequests = ( bidRequests: BidRequest[], bidderRequest: ClientBidderRequest, ): AdapterRequest => { - const data:ORTBRequest = converter.toORTB({bidRequests, bidderRequest}) + const data:ORTBRequest = converter.toORTB({ bidRequests, bidderRequest }) const adapterRequest:AdapterRequest = { method: 'POST', url: REQUEST_URL, diff --git a/test/spec/modules/alliance_gravityBidAdapter_spec.js b/test/spec/modules/alliance_gravityBidAdapter_spec.js index 6950007e583..980b0d2b023 100644 --- a/test/spec/modules/alliance_gravityBidAdapter_spec.js +++ b/test/spec/modules/alliance_gravityBidAdapter_spec.js @@ -516,7 +516,7 @@ describe('Alliance Gravity bid adapter tests', () => { }); it('Verifies user sync with cookies in bid response', () => { response.body.ext = { - cookies: [{'type': 'image', 'url': 'http://www.cookie.sync.org/'}] + cookies: [{ 'type': 'image', 'url': 'http://www.cookie.sync.org/' }] }; const syncs = spec.getUserSyncs({}, [response], DEFAULT_OPTIONS.gdprConsent); const expectedSyncs = [{ type: 'image', url: 'http://www.cookie.sync.org/' }]; From fcd64ea13f82d2cfd98d3785cac740d46fde1b11 Mon Sep 17 00:00:00 2001 From: SebRobert Date: Wed, 18 Mar 2026 05:27:43 +0100 Subject: [PATCH 43/50] BeOpBidAdapter: Refacto beopid cookie to caudid (#14584) * Change beopid cookie to caudid * Add caudid_date cookie * Post review commit --- modules/beopBidAdapter.js | 47 +++++++--- test/spec/modules/beopBidAdapter_spec.js | 111 +++++++++++++++++++++-- 2 files changed, 141 insertions(+), 17 deletions(-) diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index bf48da5aa40..61412439703 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -4,7 +4,8 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getRefererInfo } from '../src/refererDetection.js'; import { buildUrl, - deepAccess, generateUUID, getBidIdParameter, + deepAccess, + getBidIdParameter, getValue, isArray, isPlainObject, @@ -23,12 +24,33 @@ import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'beop'; const ENDPOINT_URL = 'https://hb.collectiveaudience.co/bid'; -const COOKIE_NAME = 'beopid'; +const COOKIE_NAME = 'caudid'; +const COOKIE_DATE_NAME = 'caudid_date'; const TCF_VENDOR_ID = 666; +const COOKIE_MAX_AGE_MS = 86400 * 365 * 1000; // 1 year -const validIdRegExp = /^[0-9a-fA-F]{24}$/ +const validIdRegExp = /^[0-9a-fA-F]{24}$/; + +/** + * Generates a 24-char hex string compatible with MongoDB ObjectId semantics + * (4-byte timestamp + 16 random hex chars). Used for first-party user id (caudid). + * Timestamp is padded to 8 hex chars so that a client clock in the past (or mocked Date) + * cannot produce a shorter string that would fail the 24-char validation on later requests. + * @see https://www.mongodb.com/docs/manual/reference/method/objectid/ + * @return {string} + */ +function generateObjectId() { + const timestamp = (Math.floor(Date.now() / 1000)).toString(16).padStart(8, '0'); + const randomPart = Array.from({ length: 16 }, () => + (Math.floor(Math.random() * 16)).toString(16) + ).join(''); + return (timestamp + randomPart).toLowerCase(); +} const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +/** Exported for unit tests (caudid / caudid_date cookie behavior). */ +export const __storage = storage; + export const spec = { code: BIDDER_CODE, gvlid: TCF_VENDOR_ID, @@ -68,17 +90,20 @@ export const spec = { const kwdsFromRequest = firstSlot.kwds; const keywords = getAllOrtbKeywords(bidderRequest.ortb2, kwdsFromRequest); - let beopid = ''; - if (storage.cookiesAreEnabled) { - beopid = storage.getCookie(COOKIE_NAME, undefined); - if (!beopid) { - beopid = generateUUID(); + let caudid = ''; + if (storage.cookiesAreEnabled()) { + caudid = storage.getCookie(COOKIE_NAME, undefined); + if (!caudid || !validIdRegExp.test(caudid)) { + caudid = generateObjectId(); const expirationDate = new Date(); - expirationDate.setTime(expirationDate.getTime() + 86400 * 183 * 1000); - storage.setCookie(COOKIE_NAME, beopid, expirationDate.toUTCString()); + expirationDate.setTime(expirationDate.getTime() + COOKIE_MAX_AGE_MS); + storage.setCookie(COOKIE_NAME, caudid, expirationDate.toUTCString()); + const dateValue = String(Date.now()); + storage.setCookie(COOKIE_DATE_NAME, dateValue, expirationDate.toUTCString()); } } else { storage.setCookie(COOKIE_NAME, '', 0); + storage.setCookie(COOKIE_DATE_NAME, '', 0); } const payloadObject = { @@ -91,7 +116,7 @@ export const spec = { lang: (window.navigator.language || window.navigator.languages[0]), kwds: keywords, dbg: false, - fg: beopid, + fg: caudid, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), gdpr_applies: gdpr ? gdpr.gdprApplies : false, diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index ae164b21b4c..15f10fc8719 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/beopBidAdapter.js'; +import { spec, __storage } from 'modules/beopBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; import { setConfig as setCurrencyConfig } from '../../../modules/currency.js'; @@ -380,15 +380,114 @@ describe('BeOp Bid Adapter tests', () => { describe('Ensure first party cookie is well managed', function () { const bidRequests = []; + let sandbox; - it(`should generate a new uuid`, function () { + beforeEach(function () { + sandbox = sinon.createSandbox(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + it('should set fg to a 24-char hex ObjectID (caudid) when no cookie present', function () { + sandbox.stub(__storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(__storage, 'getCookie').returns(undefined); const bid = Object.assign({}, validBid); - bidRequests.push(bid); - const request = spec.buildRequests(bidRequests, {}); + const request = spec.buildRequests([bid], {}); const payload = JSON.parse(request.data); expect(payload.fg).to.exist; - }) - }) + expect(payload.fg).to.match(/^[0-9a-f]{24}$/); + }); + + it('should set both caudid and caudid_date cookies when generating a new id', function () { + sandbox.stub(__storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(__storage, 'getCookie').returns(undefined); + const setCookieSpy = sandbox.stub(__storage, 'setCookie'); + + const bid = Object.assign({}, validBid); + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + + expect(setCookieSpy.calledTwice).to.be.true; + expect(setCookieSpy.firstCall.args[0]).to.equal('caudid'); + expect(setCookieSpy.firstCall.args[1]).to.match(/^[0-9a-f]{24}$/); + expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date'); + expect(setCookieSpy.secondCall.args[1]).to.match(/^\d+$/); + expect(Number(setCookieSpy.secondCall.args[1])).to.be.closeTo(Date.now(), 5000); + expect(payload.fg).to.equal(setCookieSpy.firstCall.args[1]); + }); + + it('should always produce 24-char caudid when clock is in the past (timestamp padding)', function () { + sandbox.stub(__storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(__storage, 'getCookie').returns(undefined); + const setCookieSpy = sandbox.stub(__storage, 'setCookie'); + // Unix epoch 0 → hex "0"; without padding that would yield 1 + 16 = 17 chars and fail validIdRegExp + sandbox.stub(Date, 'now').returns(0); + + const bid = Object.assign({}, validBid); + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + + const caudid = setCookieSpy.firstCall.args[1]; + expect(caudid).to.have.lengthOf(24); + expect(caudid).to.match(/^[0-9a-f]{24}$/); + expect(caudid.substring(0, 8)).to.equal('00000000'); + expect(payload.fg).to.equal(caudid); + }); + + it('should not set cookies when a valid caudid cookie already exists', function () { + const existingCaudid = '674a1b2c3d4e5f6789abcdef'; + sandbox.stub(__storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(__storage, 'getCookie').callsFake((name) => + name === 'caudid' ? existingCaudid : undefined + ); + const setCookieSpy = sandbox.stub(__storage, 'setCookie'); + + const bid = Object.assign({}, validBid); + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + + expect(setCookieSpy.called).to.be.false; + expect(payload.fg).to.equal(existingCaudid); + }); + + it('should regenerate caudid and set both cookies when existing caudid is invalid format', function () { + sandbox.stub(__storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(__storage, 'getCookie').callsFake((name) => + name === 'caudid' ? 'invalid-uuid-format' : undefined + ); + const setCookieSpy = sandbox.stub(__storage, 'setCookie'); + + const bid = Object.assign({}, validBid); + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + + expect(setCookieSpy.calledTwice).to.be.true; + expect(setCookieSpy.firstCall.args[0]).to.equal('caudid'); + expect(setCookieSpy.firstCall.args[1]).to.match(/^[0-9a-f]{24}$/); + expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date'); + expect(payload.fg).to.equal(setCookieSpy.firstCall.args[1]); + }); + + it('should clear both caudid and caudid_date when cookies are disabled', function () { + sandbox.stub(__storage, 'cookiesAreEnabled').returns(false); + const setCookieSpy = sandbox.stub(__storage, 'setCookie'); + + const bid = Object.assign({}, validBid); + const request = spec.buildRequests([bid], {}); + const payload = JSON.parse(request.data); + + expect(setCookieSpy.calledTwice).to.be.true; + expect(setCookieSpy.firstCall.args[0]).to.equal('caudid'); + expect(setCookieSpy.firstCall.args[1]).to.equal(''); + expect(setCookieSpy.firstCall.args[2]).to.equal(0); + expect(setCookieSpy.secondCall.args[0]).to.equal('caudid_date'); + expect(setCookieSpy.secondCall.args[1]).to.equal(''); + expect(setCookieSpy.secondCall.args[2]).to.equal(0); + expect(payload.fg).to.equal(''); + }); + }); describe('slot name normalization', function () { it('should preserve non-GPT adUnitCode unchanged (case-sensitive)', function () { const bid = Object.assign({}, validBid); From f2a63397448e393224b9355979e8b1d84c4911c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Mar 2026 23:59:35 -0400 Subject: [PATCH 44/50] Bump socket.io-parser from 4.2.4 to 4.2.6 (#14613) Bumps [socket.io-parser](https://github.com/socketio/socket.io) from 4.2.4 to 4.2.6. - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/socket.io-parser@4.2.4...socket.io-parser@4.2.6) --- updated-dependencies: - dependency-name: socket.io-parser dependency-version: 4.2.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 53 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6b861bec77..b826a2410c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19277,17 +19277,41 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.4", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "dev": true, - "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" }, "engines": { "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/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/socks": { "version": "2.8.4", "dev": true, @@ -34701,11 +34725,30 @@ } }, "socket.io-parser": { - "version": "4.2.4", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "dev": true, "requires": { "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "debug": "~4.4.1" + }, + "dependencies": { + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "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 + } } }, "socks": { From 6756249f35194dcc4b529a6c8caf40d47c33d654 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Thu, 19 Mar 2026 06:38:56 -0400 Subject: [PATCH 45/50] Add 'device.ifa' to user paths in redactor.ts (#14606) --- src/activities/redactor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/redactor.ts b/src/activities/redactor.ts index 658b1792dc0..4132c02e64d 100644 --- a/src/activities/redactor.ts +++ b/src/activities/redactor.ts @@ -19,7 +19,7 @@ export const ORTB_UFPD_PATHS = [ 'id', 'buyeruid', 'customdata' -].map(f => `user.${f}`).concat('device.ext.cdep'); +].map(f => `user.${f}`).concat('device.ext.cdep', 'device.ifa'); 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']; export const ORTB_IPV4_PATHS = ['device.ip'] From 6896fee94ac54be56c567347d6e63493b743f91a Mon Sep 17 00:00:00 2001 From: mkomorski Date: Thu, 19 Mar 2026 18:01:53 +0100 Subject: [PATCH 46/50] Core: allow vast xml without using cache (#14611) * Core: allow vast xml without using cache * renaming --- src/video.ts | 6 +++++- src/videoCache.ts | 4 ++++ test/spec/video_spec.js | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/video.ts b/src/video.ts index 14a18a6b4bd..32e9509cdaf 100644 --- a/src/video.ts +++ b/src/video.ts @@ -131,8 +131,12 @@ declare module './hook' { export const checkVideoBidSetup = hook('sync', function(bid: VideoBid, adUnit, videoMediaType, context, useCacheKey) { if (videoMediaType && (useCacheKey || context !== OUTSTREAM)) { // xml-only video bids require a prebid cache url - const { url, useLocal } = config.getConfig('cache') || {}; + const { url, useLocal, allowVastXmlOnly } = config.getConfig('cache') || {}; if ((!url && !useLocal) && bid.vastXml && !bid.vastUrl) { + if (allowVastXmlOnly === true) { + logWarn(`This bid contains only vastXml, and caching is disabled. Proceeding because cache.allowVastXmlOnly is enabled.`); + return true; + } logError(` This bid contains only vastXml and will not work when a prebid cache url is not specified. Try enabling either prebid cache with ${getGlobalVarName()}.setConfig({ cache: {url: "..."} }); diff --git a/src/videoCache.ts b/src/videoCache.ts index 46640d40370..9ca2ee84ddb 100644 --- a/src/videoCache.ts +++ b/src/videoCache.ts @@ -78,6 +78,10 @@ export interface CacheConfig { * Flag determining whether to locally save VAST XML as a blob */ useLocal?: boolean; + /** + * When true, allows VAST XML-only bids to pass even without cache.url or cache.useLocal. + */ + allowVastXmlOnly?: boolean; /** * Timeout (in milliseconds) for network requests to the cache */ diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 61761db997b..85cc96562f5 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -3,6 +3,7 @@ import { hook } from '../../src/hook.js'; import { stubAuctionIndex } from '../helpers/indexStub.js'; import * as utils from '../../src/utils.js'; import { syncOrtb2, validateOrtbFields } from '../../src/prebid.js'; +import { config } from 'src/config.js'; describe('video.js', function () { let sandbox; @@ -19,6 +20,7 @@ describe('video.js', function () { afterEach(() => { utilsMock.restore(); + config.resetConfig(); sandbox.restore(); }); @@ -284,6 +286,21 @@ describe('video.js', function () { expect(valid).to.equal(false); }); + it('validates vastXml-only bids when cache.allowVastXmlOnly is enabled', function () { + utilsMock.expects('logWarn').once(); + utilsMock.expects('logError').never(); + config.setConfig({ cache: { allowVastXmlOnly: true } }); + + const adUnits = [{ + adUnitId: 'au', + bidder: 'vastOnlyVideoBidder', + mediaTypes: { video: {} }, + }]; + + const valid = isValidVideoBid({ adUnitId: 'au', vastXml: 'vast' }, { index: stubAuctionIndex({ adUnits }) }); + expect(valid).to.equal(true); + }); + it('validates valid outstream bids', function () { const bid = { adUnitId: 'au', From 4316630c4a6f590728e5a45d30010731a3ecd2fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:43:03 -0400 Subject: [PATCH 47/50] Bump fast-xml-parser from 5.5.6 to 5.5.7 (#14619) Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 5.5.6 to 5.5.7. - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.5.6...v5.5.7) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.5.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index b826a2410c2..dc91f890c2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11457,9 +11457,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", - "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz", + "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==", "dev": true, "funding": [ { @@ -11470,7 +11470,7 @@ "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.1.3", - "strnum": "^2.1.2" + "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" @@ -19892,9 +19892,9 @@ } }, "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", "dev": true, "funding": [ { @@ -29678,14 +29678,14 @@ } }, "fast-xml-parser": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz", - "integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==", + "version": "5.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.7.tgz", + "integrity": "sha512-LteOsISQ2GEiDHZch6L9hB0+MLoYVLToR7xotrzU0opCICBkxOPgHAy1HxAvtxfJNXDJpgAsQN30mkrfpO2Prg==", "dev": true, "requires": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.1.3", - "strnum": "^2.1.2" + "strnum": "^2.2.0" } }, "fastest-levenshtein": { @@ -35145,9 +35145,9 @@ "dev": true }, "strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", "dev": true }, "supports-color": { From 6f9fd3683979470840de5eb1ccafc014b2012b1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 08:46:47 -0400 Subject: [PATCH 48/50] Bump flatted from 3.4.1 to 3.4.2 (#14621) Bumps [flatted](https://github.com/WebReflection/flatted) from 3.4.1 to 3.4.2. - [Commits](https://github.com/WebReflection/flatted/compare/v3.4.1...v3.4.2) --- updated-dependencies: - dependency-name: flatted dependency-version: 3.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc91f890c2a..18ab3c6679d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11736,9 +11736,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, "node_modules/follow-redirects": { @@ -29864,9 +29864,9 @@ } }, "flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, "follow-redirects": { From 51e56026788981e0520af473ac8b8559311a02de Mon Sep 17 00:00:00 2001 From: Leif Kramer Date: Fri, 20 Mar 2026 16:54:24 +0400 Subject: [PATCH 49/50] datamage rtd contextual provider: initial release (#14485) * Create datamage RTD integration * remove test.html files * update example.html * siplify architecture, adding back in linting, improve load * fixed caching issues and bot comments from original pr * fix issues identified by bot * replace dep GPT api calls to modern format * update btoa to support non typical url encodings, strip common tracking params * remove misc local test files * fix fetchPromise transient failur potential * reduce key value size and unusued keys in payload to gam and ortb * update Rtd spec file naming * update prebid js path in example * include datamageRtdprovider in submodules json --- .../datamageRtdProvider_example.html | 143 +++++++++ modules/.submodules.json | 3 +- modules/datamageRtdProvider.js | 286 +++++++++++++++++ modules/datamageRtdProvider.md | 90 ++++++ test/spec/modules/datamageRtdProvider_spec.js | 303 ++++++++++++++++++ 5 files changed, 824 insertions(+), 1 deletion(-) create mode 100644 integrationExamples/realTimeData/datamageRtdProvider_example.html create mode 100644 modules/datamageRtdProvider.js create mode 100644 modules/datamageRtdProvider.md create mode 100644 test/spec/modules/datamageRtdProvider_spec.js diff --git a/integrationExamples/realTimeData/datamageRtdProvider_example.html b/integrationExamples/realTimeData/datamageRtdProvider_example.html new file mode 100644 index 00000000000..636782ec77e --- /dev/null +++ b/integrationExamples/realTimeData/datamageRtdProvider_example.html @@ -0,0 +1,143 @@ + + + + + + OpsMage Prebid Test Page + + + + + + + + + + +

OpsMage Prebid Test Page

+
+
The tech world is currently buzzing over the highly anticipated market debut of fakeDSP, a trailblazing + startup poised to redefine the landscape of digital signal processing. Leveraging proprietary neuromorphic + algorithms and quantum-ready architecture, fakeDSP promises to accelerate real-time data synthesis by speeds + previously thought impossible. With early analysts calling it a definitive disruptor in both the + telecommunications and audio-engineering sectors, the company’s entrance signifies a major leap forward in how + complex signals are analyzed and reconstructed in the AI era.
+ + + \ No newline at end of file diff --git a/modules/.submodules.json b/modules/.submodules.json index 244104c93da..38cd6cccb29 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -84,6 +84,7 @@ "cleanioRtdProvider", "confiantRtdProvider", "contxtfulRtdProvider", + "datamageRtdProvider", "dgkeywordRtdProvider", "dynamicAdBoostRtdProvider", "experianRtdProvider", @@ -139,4 +140,4 @@ "adplayerproVideoProvider" ] } -} +} \ No newline at end of file diff --git a/modules/datamageRtdProvider.js b/modules/datamageRtdProvider.js new file mode 100644 index 00000000000..178eb35a47f --- /dev/null +++ b/modules/datamageRtdProvider.js @@ -0,0 +1,286 @@ +import { submodule } from '../src/hook.js'; +import { logError, logWarn, logInfo, generateUUID } from '../src/utils.js'; +import { ajaxBuilder } from '../src/ajax.js'; + +const MODULE_NAME = 'datamage'; + +let fetchPromise = null; +let lastTargeting = null; + +function _resetForTest() { + fetchPromise = null; // Clear the network promise cache + lastTargeting = null; // Clear the data targeting cache +} + +function asStringArray(v) { + if (v == null) return []; + if (Array.isArray(v)) return v.map((x) => String(x)); + return [String(v)]; +} + +function ensureSiteContentData(globalOrtb2) { + if (!globalOrtb2.site) globalOrtb2.site = {}; + if (!globalOrtb2.site.content) globalOrtb2.site.content = {}; + if (!Array.isArray(globalOrtb2.site.content.data)) globalOrtb2.site.content.data = []; + return globalOrtb2.site.content.data; +} + +function buildSegments(iabCatIds, iabCats) { + const ids = asStringArray(iabCatIds); + const names = Array.isArray(iabCats) ? iabCats.map((x) => String(x)) : []; + return ids.map((id, idx) => { + const seg = { id }; + if (names[idx]) seg.name = names[idx]; + return seg; + }); +} + +function padBase64(b64) { + const mod = b64.length % 4; + return mod ? (b64 + '='.repeat(4 - mod)) : b64; +} + +function cleanPageUrl(urlStr) { + try { + const u = new URL(urlStr); + + // 1. Strip the port (keep your existing logic) + if (u.port) u.port = ''; + + // 2. Define common tracking and analytics parameters + const trackingParams = [ + 'fbclid', // Facebook + 'igshid', // Instagram + 'gclid', // Google Ads + 'wbraid', // Google Ads (iOS) + 'gbraid', // Google Ads (iOS) + '_gl', // Google Analytics cross-domain + 'utm_source', // UTMs (Google Analytics, etc.) + 'utm_medium', + 'utm_campaign', + 'utm_term', + 'utm_content', + 'utm_id', + 'msclkid', // Microsoft/Bing Ads + 'twclid', // Twitter + 'ttclid', // TikTok + 'yclid', // Yandex + 'mc_eid', // Mailchimp + 'ScCid', // Snapchat + 's_kwcid' // Adobe Analytics + ]; + + // 3. Safely remove them from the query string + trackingParams.forEach(param => { + if (u.searchParams.has(param)) { + u.searchParams.delete(param); + } + }); + + return u.toString(); + } catch (e) { + // Fallback to the raw string if URL parsing fails + return urlStr; + } +} + +function buildApiUrl(params) { + const apiKey = params.api_key || ''; + const selector = params.selector || ''; + const rawPageUrl = (typeof window !== 'undefined' && window.location?.href) ? window.location.href : ''; + + // Use the new cleaning function here + const pageUrl = cleanPageUrl(rawPageUrl); + + let encodedUrl = ''; + try { + // Safely encode UTF-8 characters before passing to btoa() + const utf8SafeUrl = unescape(encodeURIComponent(pageUrl)); + encodedUrl = padBase64(btoa(utf8SafeUrl)); + } catch (e) { + logWarn('DataMage: Failed to base64 encode URL', e); + } + + return `https://opsmage-api.io/context/v3/get?api_key=${encodeURIComponent(apiKey)}&content_id=${encodedUrl}&prebid=true&selector=${encodeURIComponent(selector)}`; +} + +function fetchContextData(apiUrl, fetchTimeoutMs) { + if (fetchPromise) return fetchPromise; + + const ajax = ajaxBuilder(fetchTimeoutMs); + fetchPromise = new Promise((resolve, reject) => { + ajax(apiUrl, { + success: (responseText) => { + try { + resolve(JSON.parse(responseText)); + } catch (err) { + fetchPromise = null; // Clear cache on parse error to allow retry + reject(err); + } + }, + error: (err) => { + fetchPromise = null; // Clear cache on network error to allow retry + reject(err); + } + }); + }); + + return fetchPromise; +} + +/** + * Helper to parse the API payload so we don't repeat mapping logic + */ +function mapApiPayload(cc) { + const arrayKeys = ['brand_ids', 'sentiment_ids', 'location_ids', 'public_figure_ids', 'restricted_cat_ids', 'restricted_cats']; + const scalarKeys = ['ops_mage_data_id', 'res_score', 'res_score_bucket']; + + const ext = {}; + const targetingArrays = {}; + lastTargeting = {}; + + const iabCatIds = asStringArray(cc.iab_cat_ids); + + // Clean up IAB Cats by keeping only the most specific segment (after the last pipe) + const iabCats = asStringArray(cc.iab_cats).map(cat => { + const parts = cat.split('|'); + return parts[parts.length - 1]; + }); + + // Safely assign IAB keys only if they have data + if (iabCatIds.length > 0) { + targetingArrays.om_iab_cat_ids = iabCatIds; + lastTargeting.om_iab_cat_ids = iabCatIds.join(','); + } + + // NOTE: om_iab_cats is intentionally excluded from targetingArrays and lastTargeting + // to save ad server slot limits. The cleaned names are only used for the ORTB segment below. + + // Safely assign optional array keys + arrayKeys.forEach((key) => { + const vals = asStringArray(cc[key]); + if (vals.length > 0) { // Only populate if there is actual data + ext[key] = vals; + targetingArrays[`om_${key}`] = vals; + lastTargeting[`om_${key}`] = vals.join(','); + } + }); + + // Safely assign optional scalar keys + scalarKeys.forEach((key) => { + if (cc[key] != null && cc[key] !== '') { // Guard against nulls and empty strings + ext[key] = cc[key]; + targetingArrays[`om_${key}`] = [String(cc[key])]; + lastTargeting[`om_${key}`] = String(cc[key]); + } + }); + + return { ext, targetingArrays, segment: buildSegments(iabCatIds, iabCats) }; +} + +// ========================================== +// 1. PUBLISHER TARGETING (Independent of Auction) +// ========================================== +function init(rtdConfig, userConsent) { + logInfo('DATAMAGE: init() called. Fetching data for GAM...'); + + const params = (rtdConfig && rtdConfig.params) || {}; + if (!params.api_key) logWarn('DataMage: Missing api_key'); + + const apiUrl = buildApiUrl(params); + const fetchTimeoutMs = Number(params.fetch_timeout_ms ?? 2500); + + // Start network request instantly + fetchContextData(apiUrl, fetchTimeoutMs).then((resJson) => { + if (!resJson?.content_classification) { + lastTargeting = null; // Clear stale cache on empty payload + return; + } + + const { targetingArrays } = mapApiPayload(resJson.content_classification); + + window.googletag = window.googletag || { cmd: [] }; + window.googletag.cmd.push(() => { + // --- MODERN GPT API IMPLEMENTATION --- + const pageTargeting = {}; + + // 1. Build a single object containing all valid targeting pairs + Object.entries(targetingArrays).forEach(([key, value]) => { + if (value && value.length) { + pageTargeting[key] = value; + } + }); + + // 2. Apply page-level targeting in a single configuration call + if (Object.keys(pageTargeting).length > 0) { + window.googletag.setConfig({ targeting: pageTargeting }); + } + }); + }).catch(() => { + lastTargeting = null; // Clear stale cache on error + }); + + return true; +} + +// ========================================== +// 2. ADVERTISER TARGETING (Tied to Auction) +// ========================================== +function getBidRequestData(reqBidsConfigObj, callback, rtdConfig, userConsent) { + logInfo('DATAMAGE: getBidRequestData() triggered. Attaching to ORTB2...'); + + if (!reqBidsConfigObj?.ortb2Fragments?.global) { + callback(); + return; + } + + const params = (rtdConfig && rtdConfig.params) || {}; + const apiUrl = buildApiUrl(params); + const fetchTimeoutMs = Number(params.fetch_timeout_ms ?? 2500); + + reqBidsConfigObj.auctionId = reqBidsConfigObj.auctionId || generateUUID(); + + // This will instantly resolve from the cache created in init() + fetchContextData(apiUrl, fetchTimeoutMs) + .then((resJson) => { + if (!resJson?.content_classification) { + lastTargeting = null; // FIX: Clear stale cache on empty payload + return; + } + + const { ext, segment } = mapApiPayload(resJson.content_classification); + + const ortbContentDataObj = { name: 'data-mage.com', segment, ext }; + ensureSiteContentData(reqBidsConfigObj.ortb2Fragments.global).push(ortbContentDataObj); + }) + .catch((error) => { + lastTargeting = null; // FIX: Clear stale cache on error + logError('DataMage: Fetch error', error); + }) + .finally(() => callback()); // Release the auction! +} + +function getTargetingData(adUnitCodes, rtdConfig, userConsent) { + if (!lastTargeting) return {}; + + const out = {}; + + // Iterate over the array of string codes passed by Prebid + (adUnitCodes || []).forEach((code) => { + if (typeof code === 'string' && code) { + out[code] = { ...lastTargeting }; + } + }); + + return out; +} + +export const datamageRtdSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData, + getTargetingData, + _resetForTest +}; + +submodule('realTimeData', datamageRtdSubmodule); diff --git a/modules/datamageRtdProvider.md b/modules/datamageRtdProvider.md new file mode 100644 index 00000000000..c2562eaa916 --- /dev/null +++ b/modules/datamageRtdProvider.md @@ -0,0 +1,90 @@ + +# DataMage RTD Submodule + +DataMage provides real-time contextual classification (IAB Categories, Sentiment, Brands, Locations, Public Figures, Restricted Categories, and related IDs) that can be used to enrich demand signals and Google Ad Manager targeting. + +## What it does + +DataMage automatically supports two outcomes in a Prebid + GAM setup without requiring any custom glue-code on the page: + +1. **Passes data to Google Ad Manager (Direct GPT targeting)** + +* The moment Prebid initializes, DataMage fetches classification for the current page and automatically pushes the targeting keys directly to GPT using the modern `googletag.setConfig({ targeting: ... })` API at the page level. +* This ensures the data is available for all ad slots and works **even if there are no bids** or if the auction times out. + +2. **Passes data to bidders (ORTB2 enrichment)** + +* Using a memoized cache from the initial fetch, DataMage seamlessly inserts the contextual results into the bid request using OpenRTB (`ortb2Fragments.global.site.content.data`), allowing bidders to receive the enriched signals instantly. + +## Keys provided + +DataMage automatically maps and provides the following targeting keys (when available in the API response): + +* `om_iab_cat_ids` +* `om_iab_cats` +* `om_brand_ids` +* `om_sentiment_ids` +* `om_location_ids` +* `om_public_figure_ids` +* `om_restricted_cat_ids` +* `om_restricted_cats` +* `om_ops_mage_data_id` +* `om_res_score_bucket` +* `om_res_score` (only when present) + +## Prebid config + +```javascript +pbjs.setConfig({ + realTimeData: { + auctionDelay: 1000, // Gives the module time to fetch data before bids are sent, suggested minimum 1000 + dataProviders: [{ + name: "datamage", + waitForIt: true, // CRITICAL: Forces Prebid to wait for the module to fetch data before resolving the auction + params: { + api_key: "YOUR_API_KEY", + selector: "article", + fetch_timeout_ms: 2500 + } + }] + } +}); + +``` + +## GAM set up requirements + +Because DataMage automatically injects targeting globally via `setConfig`, your page implementation only requires a standard Prebid setup. + +To ensure DataMage key-values are included in your GAM requests: + +1. Call `googletag.pubads().disableInitialLoad()` before your ad requests. +2. Define your slots and call `googletag.enableServices()`. +3. Run `pbjs.requestBids(...)`. +4. Inside the `bidsBackHandler` callback: +* Call `pbjs.setTargetingForGPTAsync()` (to set standard Prebid `hb_` pricing keys). +* Call `googletag.pubads().refresh()` to trigger the GAM request. + + + +GAM will automatically combine the standard Prebid slot-level pricing keys with the page-level DataMage contextual keys. + +*Note that you will need a real API key provisioned by DataMage to use this module in production.* + +### Example: + +```javascript +pbjs.requestBids({ + bidsBackHandler: function () { + // Push standard header bidding keys to GPT + pbjs.setTargetingForGPTAsync(); + + // Refresh the ad slots. Datamage page-level keys are already injected! + googletag.cmd.push(function () { + googletag.pubads().refresh(); + }); + }, + timeout: 1500 +}); + +``` diff --git a/test/spec/modules/datamageRtdProvider_spec.js b/test/spec/modules/datamageRtdProvider_spec.js new file mode 100644 index 00000000000..fddb448b978 --- /dev/null +++ b/test/spec/modules/datamageRtdProvider_spec.js @@ -0,0 +1,303 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { datamageRtdSubmodule } from 'modules/datamageRtdProvider.js'; +import * as ajaxUtils from 'src/ajax.js'; +import * as utils from 'src/utils.js'; + +describe('datamageRtdSubmodule (DataMage RTD Provider)', function () { + let sandbox; + let ajaxBuilderStub; + let setConfigStub; + let btoaStub; + let origGoogletag; // Stores the original global to prevent breaking other tests + + function makeReqBidsConfigObj() { + return { + auctionId: 'auction-1', + ortb2Fragments: { global: {} } + }; + } + + function makeProcessedResponse(overrides = {}) { + return { + content_classification: { + ops_mage_data_id: '7d54b2d30a4e441a0f698dfae8f5b1b5', + res_score: 1, + res_score_bucket: 'high', + iab_cats: [ + 'Technology & Computing', + 'Technology & Computing|Artificial Intelligence', + 'Business & Finance' + ], + iab_cat_ids: ['596', '597', '52'], + brand_ids: ['eefd8446', 'b78b9ee2'], + sentiment_ids: ['95487831', '92bfd7eb'], + location_ids: ['60efc224'], + public_figure_ids: ['55eefb4a'], + restricted_cat_ids: [], + ...overrides + } + }; + } + + beforeEach(function () { + sandbox = sinon.createSandbox(); + + // Stub logging so they don't spam the test console + sandbox.stub(utils, 'logInfo'); + sandbox.stub(utils, 'logWarn'); + sandbox.stub(utils, 'logError'); + + // Reset module-scoped cache + datamageRtdSubmodule._resetForTest(); + + // Safely backup the original googletag object + origGoogletag = window.googletag; + + // Mock window.googletag and spy on setConfig + setConfigStub = sandbox.stub(); + window.googletag = { + cmd: { + push: function (fn) { fn(); } // Execute immediately for testing + }, + setConfig: setConfigStub + }; + + // Stub Prebid's internal ajaxBuilder + ajaxBuilderStub = sandbox.stub(ajaxUtils, 'ajaxBuilder'); + + // Keep tests deterministic + allow port-strip assertion + btoaStub = sandbox.stub(window, 'btoa').callsFake((s) => `b64(${s})`); + }); + + afterEach(function () { + sandbox.restore(); + // Restore the original googletag object so we don't break the E-Planning adapter + window.googletag = origGoogletag; + }); + + describe('init()', function () { + it('should return true and trigger GAM injection asynchronously via setConfig', function (done) { + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + const ok = datamageRtdSubmodule.init({ name: 'datamage', params: { api_key: 'x' } }, {}); + expect(ok).to.equal(true); + + // Simulate the API resolving + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.success(JSON.stringify(makeProcessedResponse())); + + // Use setTimeout to wait for the Promise chain to resolve + setTimeout(() => { + expect(setConfigStub.calledOnce).to.be.true; + + const configArg = setConfigStub.firstCall.args[0]; + expect(configArg).to.have.property('targeting'); + + const targeting = configArg.targeting; + expect(targeting).to.have.property('om_iab_cat_ids').that.deep.equals(['596', '597', '52']); + expect(targeting).to.have.property('om_brand_ids').that.deep.equals(['eefd8446', 'b78b9ee2']); + expect(targeting).to.have.property('om_res_score').that.deep.equals(['1']); + expect(targeting).to.not.have.property('om_restricted_cat_ids'); + + done(); + }, 0); + }); + }); + + describe('getBidRequestData()', function () { + it('should inject into ORTB2 when fetch resolves', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + const rtdConfig = { + name: 'datamage', + params: { api_key: 'k', selector: 'article' } + }; + + datamageRtdSubmodule.getBidRequestData(req, () => { + expect(req.ortb2Fragments.global).to.have.nested.property('site.content.data'); + const dataArr = req.ortb2Fragments.global.site.content.data; + expect(dataArr).to.be.an('array').with.length.greaterThan(0); + expect(dataArr[0]).to.have.property('name', 'data-mage.com'); + expect(dataArr[0]).to.have.property('segment'); + expect(dataArr[0].segment).to.deep.include({ id: '596', name: 'Technology & Computing' }); + done(); + }, rtdConfig, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.success(JSON.stringify(makeProcessedResponse())); + }); + + it('should only make ONE network request when init and getBidRequestData are both called (Memoization)', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + const rtdConfig = { params: { api_key: 'k' } }; + + // 1. Init fires (simulating page load) + datamageRtdSubmodule.init(rtdConfig); + + // 2. getBidRequestData fires (simulating auction start) + datamageRtdSubmodule.getBidRequestData(req, () => { + // Assert the network was only hit once despite two entry points + expect(fakeAjax.calledOnce).to.be.true; + done(); + }, rtdConfig, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.success(JSON.stringify(makeProcessedResponse())); + }); + + it('should NOT inject after network error', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + datamageRtdSubmodule.getBidRequestData(req, () => { + expect(req.ortb2Fragments.global.site?.content?.data).to.be.undefined; + expect(setConfigStub.called).to.be.false; + done(); + }, { name: 'datamage', params: { api_key: 'k' } }, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.error('Network Failed'); + }); + + it('should strip port from URL before encoding', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + datamageRtdSubmodule.getBidRequestData(req, () => { + expect(btoaStub.called).to.equal(true); + const btoaArg = btoaStub.firstCall.args[0]; + + expect(btoaArg).to.be.a('string'); + expect(btoaArg).to.not.match(/\/\/[^/]+:\d+\//); + done(); + }, { name: 'datamage', params: { api_key: 'k' } }, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.error('err'); + }); + + it('should gracefully handle btoa encoding failures without crashing the auction', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + // Force btoa to throw an error (simulating a Latin-1 DOMException for unhandled characters) + btoaStub.throws(new Error('String contains an invalid character')); + + datamageRtdSubmodule.getBidRequestData(req, () => { + // 1. Ensure the auction still releases (callback is fired) + expect(fakeAjax.calledOnce).to.be.true; + + // 2. Ensure the API URL was still built, just with an empty content_id + const ajaxUrl = fakeAjax.firstCall.args[0]; + expect(ajaxUrl).to.include('content_id='); + expect(ajaxUrl).to.not.include('content_id=b64'); // Should not contain our stub's prefix + + done(); + }, { name: 'datamage', params: { api_key: 'k' } }, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.error('err'); + }); + + it('should strip common tracking parameters from the URL before encoding', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + // 1. Store the original URL so we can restore it cleanly + const originalUrl = window.location.href; + + // 2. Use the History API to safely append query parameters without redefining the location object + window.history.replaceState({}, '', '?utm_source=fb&id=42&gclid=123'); + + datamageRtdSubmodule.getBidRequestData(req, () => { + const btoaArg = btoaStub.firstCall.args[0]; + + // 3. Ensure tracking params are gone, but valid params remain + expect(btoaArg).to.not.include('utm_source'); + expect(btoaArg).to.not.include('gclid'); + expect(btoaArg).to.include('id=42'); + + // 4. Restore the original URL so we don't pollute other tests + window.history.replaceState({}, '', originalUrl); + done(); + }, { name: 'datamage', params: { api_key: 'k' } }, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.error('err'); + }); + + it('should clear stale cache (lastTargeting) if the fetch yields no payload', function (done) { + const req1 = makeReqBidsConfigObj(); + const req2 = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + const rtdConfig = { name: 'datamage', params: { api_key: 'k' } }; + + // 1. First auction: successful fetch populates the cache + datamageRtdSubmodule.getBidRequestData(req1, () => { + // 2. Verify cache is populated + let out = datamageRtdSubmodule.getTargetingData(['div-1'], {}, {}); + expect(out['div-1']).to.have.property('om_res_score', '1'); + + // 3. Reset module state safely via the internal helper + datamageRtdSubmodule._resetForTest(); + + // 4. Second auction: simulate an empty response + datamageRtdSubmodule.getBidRequestData(req2, () => { + // 5. Verify the cache was wiped out + out = datamageRtdSubmodule.getTargetingData(['div-1'], {}, {}); + expect(out).to.deep.equal({}); + done(); + }, rtdConfig, {}); + + // Resolve the second auction with an empty payload + const callbacks2 = fakeAjax.secondCall.args[1]; + callbacks2.success(JSON.stringify({})); + }, rtdConfig, {}); + + // Resolve the first auction with a good payload + const callbacks1 = fakeAjax.firstCall.args[1]; + callbacks1.success(JSON.stringify(makeProcessedResponse())); + }); + }); + + describe('getTargetingData()', function () { + it('should return {} if no successful fetch has happened yet', function () { + const out = datamageRtdSubmodule.getTargetingData(['div-1'], {}, {}); + expect(out).to.deep.equal({}); + }); + + it('should return per-adunit legacy targeting (string-joined lists) after response resolves', function (done) { + const req = makeReqBidsConfigObj(); + let fakeAjax = sinon.stub(); + ajaxBuilderStub.returns(fakeAjax); + + datamageRtdSubmodule.getBidRequestData(req, () => { + const out = datamageRtdSubmodule.getTargetingData(['div-1', 'div-2'], {}, {}); + expect(out).to.have.property('div-1'); + expect(out).to.have.property('div-2'); + + expect(out['div-1']).to.have.property('om_iab_cat_ids', '596,597,52'); + expect(out['div-1']).to.have.property('om_brand_ids', 'eefd8446,b78b9ee2'); + expect(out['div-1']).to.have.property('om_res_score', '1'); + + done(); + }, { name: 'datamage', params: { api_key: 'k' } }, {}); + + const callbacks = fakeAjax.firstCall.args[1]; + callbacks.success(JSON.stringify(makeProcessedResponse())); + }); + }); +}); From adf5a4f2580ecaa53b53fdcedd4e52274533f3e4 Mon Sep 17 00:00:00 2001 From: Patrick McCann Date: Fri, 20 Mar 2026 09:11:52 -0400 Subject: [PATCH 50/50] Update AGENTS.md with review guidelines (#14612) Added review guidelines for PR reviews and module rules. Work in progress --- AGENTS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 5bedeec8a9d..43b12bccf9d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -58,3 +58,14 @@ This file contains instructions for the Codex agent and its friends when working - Prefer importing or mirroring conventions from these modules instead of redefining local ad-hoc shapes. - Use imported types for id, analytics, and rtd modules as well whenever possible. - Always define types for public interface to an adapter, eg each bidder parameter. + +## Review guidelines +- Use the guidelines at PR_REVIEW.md when doing PR reviews. Make all your comments and code suggestions on the PR itself instead of in linked tasks when commenting in a PR review. +- Use the module rules at https://docs.prebid.org/dev-docs/module-rules.html +- Discourage application/json calls, they cause preflight options calls with induced delays over text/plain +- Make sure people are importing from libraries and our methods whenever possible, eg on viewability or accessing navigator +- Bidder params should always only override that information coming on the request; bidders should never make someone specify something that is generally available in an ortb2 field on the request in bidder params unless they need an override. +- Bidders asking for storage access and setting an id in local storage redundant with the shared id is discouraged, they should document why they need to do this odious behavior +- Submodules need to register in submodules.json +- No one should be accessing navigator from vendor modules, if navigator needs to be accessed it should be in a common method or library +- Low priority calls should be import ajax method and use fetch keepalive; they shouldnt use trigger pixel when it can be avoided or fail to specify keepalive.